QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
qgscircularstring.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscircularstring.cpp
3  -----------------------
4  begin : September 2014
5  copyright : (C) 2014 by Marco Hugentobler
6  email : marco at sourcepole dot ch
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgscircularstring.h"
19 #include "qgsapplication.h"
20 #include "qgscoordinatetransform.h"
21 #include "qgsgeometryutils.h"
22 #include "qgslinestring.h"
23 #include "qgsmaptopixel.h"
24 #include "qgspoint.h"
25 #include "qgswkbptr.h"
26 #include "qgslogger.h"
27 #include "qgsgeometrytransformer.h"
28 #include "qgsfeedback.h"
29 
30 #include <QJsonObject>
31 #include <QPainter>
32 #include <QPainterPath>
33 #include <memory>
34 #include <nlohmann/json.hpp>
35 
37 {
39 }
40 
42 {
43  //get wkb type from first point
44  bool hasZ = p1.is3D();
45  bool hasM = p1.isMeasure();
47 
48  mX.resize( 3 );
49  mX[ 0 ] = p1.x();
50  mX[ 1 ] = p2.x();
51  mX[ 2 ] = p3.x();
52  mY.resize( 3 );
53  mY[ 0 ] = p1.y();
54  mY[ 1 ] = p2.y();
55  mY[ 2 ] = p3.y();
56  if ( hasZ )
57  {
59  mZ.resize( 3 );
60  mZ[ 0 ] = p1.z();
61  mZ[ 1 ] = p2.z();
62  mZ[ 2 ] = p3.z();
63  }
64  if ( hasM )
65  {
67  mM.resize( 3 );
68  mM[ 0 ] = p1.m();
69  mM[ 1 ] = p2.m();
70  mM[ 2 ] = p3.m();
71  }
72 }
73 
74 QgsCircularString QgsCircularString::fromTwoPointsAndCenter( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &center, const bool useShortestArc )
75 {
76  const QgsPoint midPoint = QgsGeometryUtils::segmentMidPointFromCenter( p1, p2, center, useShortestArc );
77  return QgsCircularString( p1, midPoint, p2 );
78 }
79 
80 bool QgsCircularString::equals( const QgsCurve &other ) const
81 {
82  const QgsCircularString *otherLine = dynamic_cast< const QgsCircularString * >( &other );
83  if ( !otherLine )
84  return false;
85 
86  if ( mWkbType != otherLine->mWkbType )
87  return false;
88 
89  if ( mX.count() != otherLine->mX.count() )
90  return false;
91 
92  for ( int i = 0; i < mX.count(); ++i )
93  {
94  if ( !qgsDoubleNear( mX.at( i ), otherLine->mX.at( i ) )
95  || !qgsDoubleNear( mY.at( i ), otherLine->mY.at( i ) ) )
96  return false;
97 
98  if ( is3D() && !qgsDoubleNear( mZ.at( i ), otherLine->mZ.at( i ) ) )
99  return false;
100 
101  if ( isMeasure() && !qgsDoubleNear( mM.at( i ), otherLine->mM.at( i ) ) )
102  return false;
103  }
104 
105  return true;
106 }
107 
109 {
110  auto result = qgis::make_unique< QgsCircularString >();
111  result->mWkbType = mWkbType;
112  return result.release();
113 }
114 
116 {
117  return QStringLiteral( "CircularString" );
118 }
119 
121 {
122  return 1;
123 }
124 
126 {
127  return new QgsCircularString( *this );
128 }
129 
131 {
133  mX.clear();
134  mY.clear();
135  mZ.clear();
136  mM.clear();
137  clearCache();
138 }
139 
141 {
142  QgsRectangle bbox;
143  int nPoints = numPoints();
144  for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
145  {
146  if ( i == 0 )
147  {
148  bbox = segmentBoundingBox( QgsPoint( mX[i], mY[i] ), QgsPoint( mX[i + 1], mY[i + 1] ), QgsPoint( mX[i + 2], mY[i + 2] ) );
149  }
150  else
151  {
152  QgsRectangle segmentBox = segmentBoundingBox( QgsPoint( mX[i], mY[i] ), QgsPoint( mX[i + 1], mY[i + 1] ), QgsPoint( mX[i + 2], mY[i + 2] ) );
153  bbox.combineExtentWith( segmentBox );
154  }
155  }
156 
157  if ( nPoints > 0 && nPoints % 2 == 0 )
158  {
159  if ( nPoints == 2 )
160  {
161  bbox.combineExtentWith( mX[ 0 ], mY[ 0 ] );
162  }
163  bbox.combineExtentWith( mX[ nPoints - 1 ], mY[ nPoints - 1 ] );
164  }
165  return bbox;
166 }
167 
168 QgsRectangle QgsCircularString::segmentBoundingBox( const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3 )
169 {
170  double centerX, centerY, radius;
171  QgsGeometryUtils::circleCenterRadius( pt1, pt2, pt3, radius, centerX, centerY );
172 
173  double p1Angle = QgsGeometryUtils::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
174  double p2Angle = QgsGeometryUtils::ccwAngle( pt2.y() - centerY, pt2.x() - centerX );
175  double p3Angle = QgsGeometryUtils::ccwAngle( pt3.y() - centerY, pt3.x() - centerX );
176 
177  //start point, end point and compass points in between can be on bounding box
178  QgsRectangle bbox( pt1.x(), pt1.y(), pt1.x(), pt1.y() );
179  bbox.combineExtentWith( pt3.x(), pt3.y() );
180 
181  QgsPointSequence compassPoints = compassPointsOnSegment( p1Angle, p2Angle, p3Angle, centerX, centerY, radius );
182  QgsPointSequence::const_iterator cpIt = compassPoints.constBegin();
183  for ( ; cpIt != compassPoints.constEnd(); ++cpIt )
184  {
185  bbox.combineExtentWith( cpIt->x(), cpIt->y() );
186  }
187  return bbox;
188 }
189 
190 QgsPointSequence QgsCircularString::compassPointsOnSegment( double p1Angle, double p2Angle, double p3Angle, double centerX, double centerY, double radius )
191 {
192  QgsPointSequence pointList;
193 
194  QgsPoint nPoint( centerX, centerY + radius );
195  QgsPoint ePoint( centerX + radius, centerY );
196  QgsPoint sPoint( centerX, centerY - radius );
197  QgsPoint wPoint( centerX - radius, centerY );
198 
199  if ( p3Angle >= p1Angle )
200  {
201  if ( p2Angle > p1Angle && p2Angle < p3Angle )
202  {
203  if ( p1Angle <= 90 && p3Angle >= 90 )
204  {
205  pointList.append( nPoint );
206  }
207  if ( p1Angle <= 180 && p3Angle >= 180 )
208  {
209  pointList.append( wPoint );
210  }
211  if ( p1Angle <= 270 && p3Angle >= 270 )
212  {
213  pointList.append( sPoint );
214  }
215  }
216  else
217  {
218  pointList.append( ePoint );
219  if ( p1Angle >= 90 || p3Angle <= 90 )
220  {
221  pointList.append( nPoint );
222  }
223  if ( p1Angle >= 180 || p3Angle <= 180 )
224  {
225  pointList.append( wPoint );
226  }
227  if ( p1Angle >= 270 || p3Angle <= 270 )
228  {
229  pointList.append( sPoint );
230  }
231  }
232  }
233  else
234  {
235  if ( p2Angle < p1Angle && p2Angle > p3Angle )
236  {
237  if ( p1Angle >= 270 && p3Angle <= 270 )
238  {
239  pointList.append( sPoint );
240  }
241  if ( p1Angle >= 180 && p3Angle <= 180 )
242  {
243  pointList.append( wPoint );
244  }
245  if ( p1Angle >= 90 && p3Angle <= 90 )
246  {
247  pointList.append( nPoint );
248  }
249  }
250  else
251  {
252  pointList.append( ePoint );
253  if ( p1Angle <= 270 || p3Angle >= 270 )
254  {
255  pointList.append( sPoint );
256  }
257  if ( p1Angle <= 180 || p3Angle >= 180 )
258  {
259  pointList.append( wPoint );
260  }
261  if ( p1Angle <= 90 || p3Angle >= 90 )
262  {
263  pointList.append( nPoint );
264  }
265  }
266  }
267  return pointList;
268 }
269 
271 {
272  if ( !wkbPtr )
273  return false;
274 
275  QgsWkbTypes::Type type = wkbPtr.readHeader();
277  {
278  return false;
279  }
280  clearCache();
281  mWkbType = type;
282 
283  //type
284  bool hasZ = is3D();
285  bool hasM = isMeasure();
286  int nVertices = 0;
287  wkbPtr >> nVertices;
288  mX.resize( nVertices );
289  mY.resize( nVertices );
290  hasZ ? mZ.resize( nVertices ) : mZ.clear();
291  hasM ? mM.resize( nVertices ) : mM.clear();
292  for ( int i = 0; i < nVertices; ++i )
293  {
294  wkbPtr >> mX[i];
295  wkbPtr >> mY[i];
296  if ( hasZ )
297  {
298  wkbPtr >> mZ[i];
299  }
300  if ( hasM )
301  {
302  wkbPtr >> mM[i];
303  }
304  }
305 
306  return true;
307 }
308 
309 bool QgsCircularString::fromWkt( const QString &wkt )
310 {
311  clear();
312 
313  QPair<QgsWkbTypes::Type, QString> parts = QgsGeometryUtils::wktReadBlock( wkt );
314 
316  return false;
317  mWkbType = parts.first;
318 
319  parts.second = parts.second.remove( '(' ).remove( ')' );
320  QString secondWithoutParentheses = parts.second;
321  secondWithoutParentheses = secondWithoutParentheses.simplified().remove( ' ' );
322  if ( ( parts.second.compare( QLatin1String( "EMPTY" ), Qt::CaseInsensitive ) == 0 ) ||
323  secondWithoutParentheses.isEmpty() )
324  return true;
325 
327  if ( points.isEmpty() )
328  return false;
329 
330  setPoints( points );
331  return true;
332 }
333 
334 int QgsCircularString::wkbSize( QgsAbstractGeometry::WkbFlags ) const
335 {
336  int binarySize = sizeof( char ) + sizeof( quint32 ) + sizeof( quint32 );
337  binarySize += numPoints() * ( 2 + is3D() + isMeasure() ) * sizeof( double );
338  return binarySize;
339 }
340 
341 QByteArray QgsCircularString::asWkb( WkbFlags flags ) const
342 {
343  QByteArray wkbArray;
344  wkbArray.resize( QgsCircularString::wkbSize( flags ) );
345  QgsWkbPtr wkb( wkbArray );
346  wkb << static_cast<char>( QgsApplication::endian() );
347  wkb << static_cast<quint32>( wkbType() );
348  QgsPointSequence pts;
349  points( pts );
350  QgsGeometryUtils::pointsToWKB( wkb, pts, is3D(), isMeasure() );
351  return wkbArray;
352 }
353 
355 {
356  QString wkt = wktTypeStr() + ' ';
357 
358  if ( isEmpty() )
359  wkt += QLatin1String( "EMPTY" );
360  else
361  {
362  QgsPointSequence pts;
363  points( pts );
365  }
366  return wkt;
367 }
368 
369 QDomElement QgsCircularString::asGml2( QDomDocument &doc, int precision, const QString &ns, const AxisOrder axisOrder ) const
370 {
371  // GML2 does not support curves
372  std::unique_ptr< QgsLineString > line( curveToLine() );
373  QDomElement gml = line->asGml2( doc, precision, ns, axisOrder );
374  return gml;
375 }
376 
377 QDomElement QgsCircularString::asGml3( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
378 {
379  QgsPointSequence pts;
380  points( pts );
381 
382  QDomElement elemCurve = doc.createElementNS( ns, QStringLiteral( "Curve" ) );
383 
384  if ( isEmpty() )
385  return elemCurve;
386 
387  QDomElement elemSegments = doc.createElementNS( ns, QStringLiteral( "segments" ) );
388  QDomElement elemArcString = doc.createElementNS( ns, QStringLiteral( "ArcString" ) );
389  elemArcString.appendChild( QgsGeometryUtils::pointsToGML3( pts, doc, precision, ns, is3D(), axisOrder ) );
390  elemSegments.appendChild( elemArcString );
391  elemCurve.appendChild( elemSegments );
392  return elemCurve;
393 }
394 
395 
397 {
398  // GeoJSON does not support curves
399  std::unique_ptr< QgsLineString > line( curveToLine() );
400  return line->asJsonObject( precision );
401 }
402 
404 {
405  return mX.isEmpty();
406 }
407 
408 bool QgsCircularString::isValid( QString &error, int flags ) const
409 {
410  if ( !isEmpty() && ( numPoints() < 3 ) )
411  {
412  error = QObject::tr( "CircularString has less than 3 points and is not empty." );
413  return false;
414  }
415  return QgsCurve::isValid( error, flags );
416 }
417 
418 //curve interface
420 {
421  int nPoints = numPoints();
422  double length = 0;
423  for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
424  {
425  length += QgsGeometryUtils::circleLength( mX[i], mY[i], mX[i + 1], mY[i + 1], mX[i + 2], mY[i + 2] );
426  }
427  return length;
428 }
429 
431 {
432  if ( numPoints() < 1 )
433  {
434  return QgsPoint();
435  }
436  return pointN( 0 );
437 }
438 
440 {
441  if ( numPoints() < 1 )
442  {
443  return QgsPoint();
444  }
445  return pointN( numPoints() - 1 );
446 }
447 
449 {
450  QgsLineString *line = new QgsLineString();
452  int nPoints = numPoints();
453 
454  for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
455  {
456  QgsGeometryUtils::segmentizeArc( pointN( i ), pointN( i + 1 ), pointN( i + 2 ), points, tolerance, toleranceType, is3D(), isMeasure() );
457  }
458 
459  line->setPoints( points );
460  return line;
461 }
462 
463 QgsCircularString *QgsCircularString::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing ) const
464 {
465  // prepare result
466  std::unique_ptr<QgsCircularString> result { createEmptyWithSameType() };
467 
468  bool res = snapToGridPrivate( hSpacing, vSpacing, dSpacing, mSpacing, mX, mY, mZ, mM,
469  result->mX, result->mY, result->mZ, result->mM );
470  if ( res )
471  return result.release();
472  else
473  return nullptr;
474 }
475 
476 bool QgsCircularString::removeDuplicateNodes( double epsilon, bool useZValues )
477 {
478  if ( mX.count() <= 3 )
479  return false; // don't create degenerate lines
480  bool result = false;
481  double prevX = mX.at( 0 );
482  double prevY = mY.at( 0 );
483  bool hasZ = is3D();
484  bool useZ = hasZ && useZValues;
485  double prevZ = useZ ? mZ.at( 0 ) : 0;
486  int i = 1;
487  int remaining = mX.count();
488  // we have to consider points in pairs, since a segment can validly have the same start and
489  // end if it has a different curve point
490  while ( i + 1 < remaining )
491  {
492  double currentCurveX = mX.at( i );
493  double currentCurveY = mY.at( i );
494  double currentX = mX.at( i + 1 );
495  double currentY = mY.at( i + 1 );
496  double currentZ = useZ ? mZ.at( i + 1 ) : 0;
497  if ( qgsDoubleNear( currentCurveX, prevX, epsilon ) &&
498  qgsDoubleNear( currentCurveY, prevY, epsilon ) &&
499  qgsDoubleNear( currentX, prevX, epsilon ) &&
500  qgsDoubleNear( currentY, prevY, epsilon ) &&
501  ( !useZ || qgsDoubleNear( currentZ, prevZ, epsilon ) ) )
502  {
503  result = true;
504  // remove point
505  mX.removeAt( i );
506  mX.removeAt( i );
507  mY.removeAt( i );
508  mY.removeAt( i );
509  if ( hasZ )
510  {
511  mZ.removeAt( i );
512  mZ.removeAt( i );
513  }
514  remaining -= 2;
515  }
516  else
517  {
518  prevX = currentX;
519  prevY = currentY;
520  prevZ = currentZ;
521  i += 2;
522  }
523  }
524  return result;
525 }
526 
528 {
529  return std::min( mX.size(), mY.size() );
530 }
531 
533 {
534  if ( i < 0 || std::min( mX.size(), mY.size() ) <= i )
535  {
536  return QgsPoint();
537  }
538 
539  double x = mX.at( i );
540  double y = mY.at( i );
541  double z = 0;
542  double m = 0;
543 
544  if ( is3D() )
545  {
546  z = mZ.at( i );
547  }
548  if ( isMeasure() )
549  {
550  m = mM.at( i );
551  }
552 
554  if ( is3D() && isMeasure() )
555  {
557  }
558  else if ( is3D() )
559  {
561  }
562  else if ( isMeasure() )
563  {
565  }
566  return QgsPoint( t, x, y, z, m );
567 }
568 
569 double QgsCircularString::xAt( int index ) const
570 {
571  if ( index >= 0 && index < mX.size() )
572  return mX.at( index );
573  else
574  return 0.0;
575 }
576 
577 double QgsCircularString::yAt( int index ) const
578 {
579  if ( index >= 0 && index < mY.size() )
580  return mY.at( index );
581  else
582  return 0.0;
583 }
584 
586 {
587  if ( !transformer )
588  return false;
589 
590  bool hasZ = is3D();
591  bool hasM = isMeasure();
592  int size = mX.size();
593 
594  double *srcX = mX.data();
595  double *srcY = mY.data();
596  double *srcM = hasM ? mM.data() : nullptr;
597  double *srcZ = hasZ ? mZ.data() : nullptr;
598 
599  bool res = true;
600  for ( int i = 0; i < size; ++i )
601  {
602  double x = *srcX;
603  double y = *srcY;
604  double z = hasZ ? *srcZ : std::numeric_limits<double>::quiet_NaN();
605  double m = hasM ? *srcM : std::numeric_limits<double>::quiet_NaN();
606  if ( !transformer->transformPoint( x, y, z, m ) )
607  {
608  res = false;
609  break;
610  }
611 
612  *srcX++ = x;
613  *srcY++ = y;
614  if ( hasM )
615  *srcM++ = m;
616  if ( hasZ )
617  *srcZ++ = z;
618 
619  if ( feedback && feedback->isCanceled() )
620  {
621  res = false;
622  break;
623  }
624  }
625  clearCache();
626  return res;
627 }
628 
629 void QgsCircularString::filterVertices( const std::function<bool ( const QgsPoint & )> &filter )
630 {
631  bool hasZ = is3D();
632  bool hasM = isMeasure();
633  int size = mX.size();
634 
635  double *srcX = mX.data(); // clazy:exclude=detaching-member
636  double *srcY = mY.data(); // clazy:exclude=detaching-member
637  double *srcM = hasM ? mM.data() : nullptr; // clazy:exclude=detaching-member
638  double *srcZ = hasZ ? mZ.data() : nullptr; // clazy:exclude=detaching-member
639 
640  double *destX = srcX;
641  double *destY = srcY;
642  double *destM = srcM;
643  double *destZ = srcZ;
644 
645  int filteredPoints = 0;
646  for ( int i = 0; i < size; ++i )
647  {
648  double x = *srcX++;
649  double y = *srcY++;
650  double z = hasZ ? *srcZ++ : std::numeric_limits<double>::quiet_NaN();
651  double m = hasM ? *srcM++ : std::numeric_limits<double>::quiet_NaN();
652 
653  if ( filter( QgsPoint( x, y, z, m ) ) )
654  {
655  filteredPoints++;
656  *destX++ = x;
657  *destY++ = y;
658  if ( hasM )
659  *destM++ = m;
660  if ( hasZ )
661  *destZ++ = z;
662  }
663  }
664 
665  mX.resize( filteredPoints );
666  mY.resize( filteredPoints );
667  if ( hasZ )
668  mZ.resize( filteredPoints );
669  if ( hasM )
670  mM.resize( filteredPoints );
671 
672  clearCache();
673 }
674 
675 void QgsCircularString::transformVertices( const std::function<QgsPoint( const QgsPoint & )> &transform )
676 {
677  bool hasZ = is3D();
678  bool hasM = isMeasure();
679  int size = mX.size();
680 
681  double *srcX = mX.data();
682  double *srcY = mY.data();
683  double *srcM = hasM ? mM.data() : nullptr;
684  double *srcZ = hasZ ? mZ.data() : nullptr;
685 
686  for ( int i = 0; i < size; ++i )
687  {
688  double x = *srcX;
689  double y = *srcY;
690  double z = hasZ ? *srcZ : std::numeric_limits<double>::quiet_NaN();
691  double m = hasM ? *srcM : std::numeric_limits<double>::quiet_NaN();
692  QgsPoint res = transform( QgsPoint( x, y, z, m ) );
693  *srcX++ = res.x();
694  *srcY++ = res.y();
695  if ( hasM )
696  *srcM++ = res.m();
697  if ( hasZ )
698  *srcZ++ = res.z();
699  }
700  clearCache();
701 }
702 
704 {
705  pts.clear();
706  int nPts = numPoints();
707  for ( int i = 0; i < nPts; ++i )
708  {
709  pts.push_back( pointN( i ) );
710  }
711 }
712 
714 {
715  clearCache();
716 
717  if ( points.empty() )
718  {
720  mX.clear();
721  mY.clear();
722  mZ.clear();
723  mM.clear();
724  return;
725  }
726 
727  //get wkb type from first point
728  const QgsPoint &firstPt = points.at( 0 );
729  bool hasZ = firstPt.is3D();
730  bool hasM = firstPt.isMeasure();
731 
733 
734  mX.resize( points.size() );
735  mY.resize( points.size() );
736  if ( hasZ )
737  {
738  mZ.resize( points.size() );
739  }
740  else
741  {
742  mZ.clear();
743  }
744  if ( hasM )
745  {
746  mM.resize( points.size() );
747  }
748  else
749  {
750  mM.clear();
751  }
752 
753  for ( int i = 0; i < points.size(); ++i )
754  {
755  mX[i] = points[i].x();
756  mY[i] = points[i].y();
757  if ( hasZ )
758  {
759  double z = points.at( i ).z();
760  mZ[i] = std::isnan( z ) ? 0 : z;
761  }
762  if ( hasM )
763  {
764  double m = points.at( i ).m();
765  mM[i] = std::isnan( m ) ? 0 : m;
766  }
767  }
768 }
769 
770 void QgsCircularString::draw( QPainter &p ) const
771 {
772  QPainterPath path;
773  addToPainterPath( path );
774  p.drawPath( path );
775 }
776 
778 {
779  clearCache();
780 
781  double *zArray = mZ.data();
782 
783  bool hasZ = is3D();
784  int nPoints = numPoints();
785  bool useDummyZ = !hasZ || !transformZ;
786  if ( useDummyZ )
787  {
788  zArray = new double[nPoints];
789  for ( int i = 0; i < nPoints; ++i )
790  {
791  zArray[i] = 0;
792  }
793  }
794  ct.transformCoords( nPoints, mX.data(), mY.data(), zArray, d );
795  if ( useDummyZ )
796  {
797  delete[] zArray;
798  }
799 }
800 
801 void QgsCircularString::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
802 {
803  clearCache();
804 
805  int nPoints = numPoints();
806  bool hasZ = is3D();
807  bool hasM = isMeasure();
808  for ( int i = 0; i < nPoints; ++i )
809  {
810  qreal x, y;
811  t.map( mX.at( i ), mY.at( i ), &x, &y );
812  mX[i] = x;
813  mY[i] = y;
814  if ( hasZ )
815  {
816  mZ[i] = mZ.at( i ) * zScale + zTranslate;
817  }
818  if ( hasM )
819  {
820  mM[i] = mM.at( i ) * mScale + mTranslate;
821  }
822  }
823 }
824 
825 void QgsCircularString::addToPainterPath( QPainterPath &path ) const
826 {
827  int nPoints = numPoints();
828  if ( nPoints < 1 )
829  {
830  return;
831  }
832 
833  if ( path.isEmpty() || path.currentPosition() != QPointF( mX[0], mY[0] ) )
834  {
835  path.moveTo( QPointF( mX[0], mY[0] ) );
836  }
837 
838  for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
839  {
840  QgsPointSequence pt;
841  QgsGeometryUtils::segmentizeArc( QgsPoint( mX[i], mY[i] ), QgsPoint( mX[i + 1], mY[i + 1] ), QgsPoint( mX[i + 2], mY[i + 2] ), pt );
842  for ( int j = 1; j < pt.size(); ++j )
843  {
844  path.lineTo( pt.at( j ).x(), pt.at( j ).y() );
845  }
846 #if 0
847  //arcTo( path, QPointF( mX[i], mY[i] ), QPointF( mX[i + 1], mY[i + 1] ), QPointF( mX[i + 2], mY[i + 2] ) );
848 #endif
849  }
850 
851  //if number of points is even, connect to last point with straight line (even though the circular string is not valid)
852  if ( nPoints % 2 == 0 )
853  {
854  path.lineTo( mX[ nPoints - 1 ], mY[ nPoints - 1 ] );
855  }
856 }
857 
858 #if 0
859 void QgsCircularString::arcTo( QPainterPath &path, QPointF pt1, QPointF pt2, QPointF pt3 )
860 {
861  double centerX, centerY, radius;
862  QgsGeometryUtils::circleCenterRadius( QgsPoint( pt1.x(), pt1.y() ), QgsPoint( pt2.x(), pt2.y() ), QgsPoint( pt3.x(), pt3.y() ),
863  radius, centerX, centerY );
864 
865  double p1Angle = QgsGeometryUtils::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
866  double sweepAngle = QgsGeometryUtils::sweepAngle( centerX, centerY, pt1.x(), pt1.y(), pt2.x(), pt2.y(), pt3.x(), pt3.y() );
867 
868  double diameter = 2 * radius;
869  path.arcTo( centerX - radius, centerY - radius, diameter, diameter, p1Angle, sweepAngle );
870 }
871 #endif
872 
873 void QgsCircularString::drawAsPolygon( QPainter &p ) const
874 {
875  draw( p );
876 }
877 
878 bool QgsCircularString::insertVertex( QgsVertexId position, const QgsPoint &vertex )
879 {
880  if ( position.vertex >= mX.size() || position.vertex < 1 )
881  {
882  return false;
883  }
884 
885  mX.insert( position.vertex, vertex.x() );
886  mY.insert( position.vertex, vertex.y() );
887  if ( is3D() )
888  {
889  mZ.insert( position.vertex, vertex.z() );
890  }
891  if ( isMeasure() )
892  {
893  mM.insert( position.vertex, vertex.m() );
894  }
895 
896  bool vertexNrEven = ( position.vertex % 2 == 0 );
897  if ( vertexNrEven )
898  {
899  insertVertexBetween( position.vertex - 2, position.vertex - 1, position.vertex );
900  }
901  else
902  {
903  insertVertexBetween( position.vertex, position.vertex + 1, position.vertex - 1 );
904  }
905  clearCache(); //set bounding box invalid
906  return true;
907 }
908 
909 bool QgsCircularString::moveVertex( QgsVertexId position, const QgsPoint &newPos )
910 {
911  if ( position.vertex < 0 || position.vertex >= mX.size() )
912  {
913  return false;
914  }
915 
916  mX[position.vertex] = newPos.x();
917  mY[position.vertex] = newPos.y();
918  if ( is3D() && newPos.is3D() )
919  {
920  mZ[position.vertex] = newPos.z();
921  }
922  if ( isMeasure() && newPos.isMeasure() )
923  {
924  mM[position.vertex] = newPos.m();
925  }
926  clearCache(); //set bounding box invalid
927  return true;
928 }
929 
931 {
932  int nVertices = this->numPoints();
933  if ( nVertices < 4 ) //circular string must have at least 3 vertices
934  {
935  clear();
936  return true;
937  }
938  if ( position.vertex < 0 || position.vertex > ( nVertices - 1 ) )
939  {
940  return false;
941  }
942 
943  if ( position.vertex < ( nVertices - 2 ) )
944  {
945  //remove this and the following vertex
946  deleteVertex( position.vertex + 1 );
947  deleteVertex( position.vertex );
948  }
949  else //remove this and the preceding vertex
950  {
951  deleteVertex( position.vertex );
952  deleteVertex( position.vertex - 1 );
953  }
954 
955  clearCache(); //set bounding box invalid
956  return true;
957 }
958 
960 {
961  mX.remove( i );
962  mY.remove( i );
963  if ( is3D() )
964  {
965  mZ.remove( i );
966  }
967  if ( isMeasure() )
968  {
969  mM.remove( i );
970  }
971  clearCache();
972 }
973 
974 double QgsCircularString::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
975 {
976  double minDist = std::numeric_limits<double>::max();
977  QgsPoint minDistSegmentPoint;
978  QgsVertexId minDistVertexAfter;
979  int minDistLeftOf = 0;
980 
981  double currentDist = 0.0;
982 
983  int nPoints = numPoints();
984  for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
985  {
986  currentDist = closestPointOnArc( mX[i], mY[i], mX[i + 1], mY[i + 1], mX[i + 2], mY[i + 2], pt, segmentPt, vertexAfter, leftOf, epsilon );
987  if ( currentDist < minDist )
988  {
989  minDist = currentDist;
990  minDistSegmentPoint = segmentPt;
991  minDistVertexAfter.vertex = vertexAfter.vertex + i;
992  if ( leftOf )
993  {
994  minDistLeftOf = *leftOf;
995  }
996  }
997  }
998 
999  if ( minDist == std::numeric_limits<double>::max() )
1000  return -1; // error: no segments
1001 
1002  segmentPt = minDistSegmentPoint;
1003  vertexAfter = minDistVertexAfter;
1004  vertexAfter.part = 0;
1005  vertexAfter.ring = 0;
1006  if ( leftOf )
1007  {
1008  *leftOf = qgsDoubleNear( minDist, 0.0 ) ? 0 : minDistLeftOf;
1009  }
1010  return minDist;
1011 }
1012 
1013 bool QgsCircularString::pointAt( int node, QgsPoint &point, QgsVertexId::VertexType &type ) const
1014 {
1015  if ( node < 0 || node >= numPoints() )
1016  {
1017  return false;
1018  }
1019  point = pointN( node );
1020  type = ( node % 2 == 0 ) ? QgsVertexId::SegmentVertex : QgsVertexId::CurveVertex;
1021  return true;
1022 }
1023 
1024 void QgsCircularString::sumUpArea( double &sum ) const
1025 {
1026  int maxIndex = numPoints() - 2;
1027 
1028  for ( int i = 0; i < maxIndex; i += 2 )
1029  {
1030  QgsPoint p1( mX[i], mY[i] );
1031  QgsPoint p2( mX[i + 1], mY[i + 1] );
1032  QgsPoint p3( mX[i + 2], mY[i + 2] );
1033 
1034  //segment is a full circle, p2 is the center point
1035  if ( p1 == p3 )
1036  {
1037  double r2 = QgsGeometryUtils::sqrDistance2D( p1, p2 ) / 4.0;
1038  sum += M_PI * r2;
1039  continue;
1040  }
1041 
1042  sum += 0.5 * ( mX[i] * mY[i + 2] - mY[i] * mX[i + 2] );
1043 
1044  //calculate area between circle and chord, then sum / subtract from total area
1045  double midPointX = ( p1.x() + p3.x() ) / 2.0;
1046  double midPointY = ( p1.y() + p3.y() ) / 2.0;
1047 
1048  double radius, centerX, centerY;
1049  QgsGeometryUtils::circleCenterRadius( p1, p2, p3, radius, centerX, centerY );
1050 
1051  double d = std::sqrt( QgsGeometryUtils::sqrDistance2D( QgsPoint( centerX, centerY ), QgsPoint( midPointX, midPointY ) ) );
1052  double r2 = radius * radius;
1053 
1054  if ( d > radius )
1055  {
1056  //d cannot be greater than radius, something must be wrong...
1057  continue;
1058  }
1059 
1060  bool circlePointLeftOfLine = QgsGeometryUtils::leftOfLine( p2.x(), p2.y(), p1.x(), p1.y(), p3.x(), p3.y() ) < 0;
1061  bool centerPointLeftOfLine = QgsGeometryUtils::leftOfLine( centerX, centerY, p1.x(), p1.y(), p3.x(), p3.y() ) < 0;
1062 
1063  double cov = 0.5 - d * std::sqrt( r2 - d * d ) / ( M_PI * r2 ) - M_1_PI * std::asin( d / radius );
1064  double circleChordArea = 0;
1065  if ( circlePointLeftOfLine == centerPointLeftOfLine )
1066  {
1067  circleChordArea = M_PI * r2 * ( 1 - cov );
1068  }
1069  else
1070  {
1071  circleChordArea = M_PI * r2 * cov;
1072  }
1073 
1074  if ( !circlePointLeftOfLine )
1075  {
1076  sum += circleChordArea;
1077  }
1078  else
1079  {
1080  sum -= circleChordArea;
1081  }
1082  }
1083 }
1084 
1086 {
1087  return true;
1088 }
1089 
1090 double QgsCircularString::closestPointOnArc( double x1, double y1, double x2, double y2, double x3, double y3,
1091  const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon )
1092 {
1093  double radius, centerX, centerY;
1094  QgsPoint pt1( x1, y1 );
1095  QgsPoint pt2( x2, y2 );
1096  QgsPoint pt3( x3, y3 );
1097 
1098  QgsGeometryUtils::circleCenterRadius( pt1, pt2, pt3, radius, centerX, centerY );
1099  double angle = QgsGeometryUtils::ccwAngle( pt.y() - centerY, pt.x() - centerX );
1100  double angle1 = QgsGeometryUtils::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
1101  double angle2 = QgsGeometryUtils::ccwAngle( pt2.y() - centerY, pt2.x() - centerX );
1102  double angle3 = QgsGeometryUtils::ccwAngle( pt3.y() - centerY, pt3.x() - centerX );
1103 
1104  bool clockwise = QgsGeometryUtils::circleClockwise( angle1, angle2, angle3 );
1105 
1106  if ( QgsGeometryUtils::angleOnCircle( angle, angle1, angle2, angle3 ) )
1107  {
1108  //get point on line center -> pt with distance radius
1109  segmentPt = QgsGeometryUtils::pointOnLineWithDistance( QgsPoint( centerX, centerY ), pt, radius );
1110 
1111  //vertexAfter
1112  vertexAfter.vertex = QgsGeometryUtils::circleAngleBetween( angle, angle1, angle2, clockwise ) ? 1 : 2;
1113  }
1114  else
1115  {
1116  double distPtPt1 = QgsGeometryUtils::sqrDistance2D( pt, pt1 );
1117  double distPtPt3 = QgsGeometryUtils::sqrDistance2D( pt, pt3 );
1118  segmentPt = ( distPtPt1 <= distPtPt3 ) ? pt1 : pt3;
1119  vertexAfter.vertex = ( distPtPt1 <= distPtPt3 ) ? 1 : 2;
1120  }
1121 
1122  double sqrDistance = QgsGeometryUtils::sqrDistance2D( segmentPt, pt );
1123  //prevent rounding errors if the point is directly on the segment
1124  if ( qgsDoubleNear( sqrDistance, 0.0, epsilon ) )
1125  {
1126  segmentPt.setX( pt.x() );
1127  segmentPt.setY( pt.y() );
1128  sqrDistance = 0.0;
1129  }
1130 
1131  if ( leftOf )
1132  {
1133  double sqrDistancePointToCenter = ( pt.x() - centerX ) * ( pt.x() - centerX ) + ( pt.y() - centerY ) * ( pt.y() - centerY );
1134  *leftOf = clockwise ? ( sqrDistancePointToCenter > radius * radius ? -1 : 1 )
1135  : ( sqrDistancePointToCenter < radius * radius ? -1 : 1 );
1136  }
1137 
1138  return sqrDistance;
1139 }
1140 
1141 void QgsCircularString::insertVertexBetween( int after, int before, int pointOnCircle )
1142 {
1143  double xAfter = mX.at( after );
1144  double yAfter = mY.at( after );
1145  double xBefore = mX.at( before );
1146  double yBefore = mY.at( before );
1147  double xOnCircle = mX.at( pointOnCircle );
1148  double yOnCircle = mY.at( pointOnCircle );
1149 
1150  double radius, centerX, centerY;
1151  QgsGeometryUtils::circleCenterRadius( QgsPoint( xAfter, yAfter ), QgsPoint( xBefore, yBefore ), QgsPoint( xOnCircle, yOnCircle ), radius, centerX, centerY );
1152 
1153  double x = ( xAfter + xBefore ) / 2.0;
1154  double y = ( yAfter + yBefore ) / 2.0;
1155 
1156  QgsPoint newVertex = QgsGeometryUtils::pointOnLineWithDistance( QgsPoint( centerX, centerY ), QgsPoint( x, y ), radius );
1157  mX.insert( before, newVertex.x() );
1158  mY.insert( before, newVertex.y() );
1159 
1160  if ( is3D() )
1161  {
1162  mZ.insert( before, ( mZ[after] + mZ[before] ) / 2.0 );
1163  }
1164  if ( isMeasure() )
1165  {
1166  mM.insert( before, ( mM[after] + mM[before] ) / 2.0 );
1167  }
1168  clearCache();
1169 }
1170 
1172 {
1173  if ( numPoints() < 3 )
1174  {
1175  //undefined
1176  return 0.0;
1177  }
1178 
1179  int before = vId.vertex - 1;
1180  int vertex = vId.vertex;
1181  int after = vId.vertex + 1;
1182 
1183  if ( vId.vertex % 2 != 0 ) // a curve vertex
1184  {
1185  if ( vId.vertex >= 1 && vId.vertex < numPoints() - 1 )
1186  {
1187  return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex], mY[vertex] ), QgsPoint( mX[before], mY[before] ),
1188  QgsPoint( mX[vertex], mY[vertex] ), QgsPoint( mX[after], mY[after] ) );
1189  }
1190  }
1191  else //a point vertex
1192  {
1193  if ( vId.vertex == 0 )
1194  {
1195  return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[0], mY[0] ), QgsPoint( mX[0], mY[0] ),
1196  QgsPoint( mX[1], mY[1] ), QgsPoint( mX[2], mY[2] ) );
1197  }
1198  if ( vId.vertex >= numPoints() - 1 )
1199  {
1200  int a = numPoints() - 3;
1201  int b = numPoints() - 2;
1202  int c = numPoints() - 1;
1203  return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[c], mY[c] ), QgsPoint( mX[a], mY[a] ),
1204  QgsPoint( mX[b], mY[b] ), QgsPoint( mX[c], mY[c] ) );
1205  }
1206  else
1207  {
1208  if ( vId.vertex + 2 > numPoints() - 1 )
1209  {
1210  return 0.0;
1211  }
1212 
1213  int vertex1 = vId.vertex - 2;
1214  int vertex2 = vId.vertex - 1;
1215  int vertex3 = vId.vertex;
1216  double angle1 = QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex3], mY[vertex3] ),
1217  QgsPoint( mX[vertex1], mY[vertex1] ), QgsPoint( mX[vertex2], mY[vertex2] ), QgsPoint( mX[vertex3], mY[vertex3] ) );
1218  int vertex4 = vId.vertex + 1;
1219  int vertex5 = vId.vertex + 2;
1220  double angle2 = QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex3], mY[vertex3] ),
1221  QgsPoint( mX[vertex3], mY[vertex3] ), QgsPoint( mX[vertex4], mY[vertex4] ), QgsPoint( mX[vertex5], mY[vertex5] ) );
1222  return QgsGeometryUtils::averageAngle( angle1, angle2 );
1223  }
1224  }
1225  return 0.0;
1226 }
1227 
1229 {
1230  if ( startVertex.vertex < 0 || startVertex.vertex >= mX.count() - 2 )
1231  return 0.0;
1232 
1233  if ( startVertex.vertex % 2 == 1 )
1234  return 0.0; // curve point?
1235 
1236  double x1 = mX.at( startVertex.vertex );
1237  double y1 = mY.at( startVertex.vertex );
1238  double x2 = mX.at( startVertex.vertex + 1 );
1239  double y2 = mY.at( startVertex.vertex + 1 );
1240  double x3 = mX.at( startVertex.vertex + 2 );
1241  double y3 = mY.at( startVertex.vertex + 2 );
1242  return QgsGeometryUtils::circleLength( x1, y1, x2, y2, x3, y3 );
1243 }
1244 
1246 {
1247  QgsCircularString *copy = clone();
1248  std::reverse( copy->mX.begin(), copy->mX.end() );
1249  std::reverse( copy->mY.begin(), copy->mY.end() );
1250  if ( is3D() )
1251  {
1252  std::reverse( copy->mZ.begin(), copy->mZ.end() );
1253  }
1254  if ( isMeasure() )
1255  {
1256  std::reverse( copy->mM.begin(), copy->mM.end() );
1257  }
1258  return copy;
1259 }
1260 
1261 QgsPoint *QgsCircularString::interpolatePoint( const double distance ) const
1262 {
1263  if ( distance < 0 )
1264  return nullptr;
1265 
1266  double distanceTraversed = 0;
1267  const int totalPoints = numPoints();
1268  if ( totalPoints == 0 )
1269  return nullptr;
1270 
1272  if ( is3D() )
1273  pointType = QgsWkbTypes::PointZ;
1274  if ( isMeasure() )
1275  pointType = QgsWkbTypes::addM( pointType );
1276 
1277  const double *x = mX.constData();
1278  const double *y = mY.constData();
1279  const double *z = is3D() ? mZ.constData() : nullptr;
1280  const double *m = isMeasure() ? mM.constData() : nullptr;
1281 
1282  double prevX = *x++;
1283  double prevY = *y++;
1284  double prevZ = z ? *z++ : 0.0;
1285  double prevM = m ? *m++ : 0.0;
1286 
1287  if ( qgsDoubleNear( distance, 0.0 ) )
1288  {
1289  return new QgsPoint( pointType, prevX, prevY, prevZ, prevM );
1290  }
1291 
1292  for ( int i = 0; i < ( totalPoints - 2 ) ; i += 2 )
1293  {
1294  double x1 = prevX;
1295  double y1 = prevY;
1296  double z1 = prevZ;
1297  double m1 = prevM;
1298 
1299  double x2 = *x++;
1300  double y2 = *y++;
1301  double z2 = z ? *z++ : 0.0;
1302  double m2 = m ? *m++ : 0.0;
1303 
1304  double x3 = *x++;
1305  double y3 = *y++;
1306  double z3 = z ? *z++ : 0.0;
1307  double m3 = m ? *m++ : 0.0;
1308 
1309  const double segmentLength = QgsGeometryUtils::circleLength( x1, y1, x2, y2, x3, y3 );
1310  if ( distance < distanceTraversed + segmentLength || qgsDoubleNear( distance, distanceTraversed + segmentLength ) )
1311  {
1312  // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1313  const double distanceToPoint = std::min( distance - distanceTraversed, segmentLength );
1314  return new QgsPoint( QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1315  QgsPoint( pointType, x2, y2, z2, m2 ),
1316  QgsPoint( pointType, x3, y3, z3, m3 ), distanceToPoint ) );
1317  }
1318 
1319  distanceTraversed += segmentLength;
1320 
1321  prevX = x3;
1322  prevY = y3;
1323  prevZ = z3;
1324  prevM = m3;
1325  }
1326 
1327  return nullptr;
1328 }
1329 
1330 QgsCircularString *QgsCircularString::curveSubstring( double startDistance, double endDistance ) const
1331 {
1332  if ( startDistance < 0 && endDistance < 0 )
1333  return createEmptyWithSameType();
1334 
1335  endDistance = std::max( startDistance, endDistance );
1336 
1337  const int totalPoints = numPoints();
1338  if ( totalPoints == 0 )
1339  return clone();
1340 
1341  QVector< QgsPoint > substringPoints;
1342  substringPoints.reserve( totalPoints );
1343 
1345  if ( is3D() )
1346  pointType = QgsWkbTypes::PointZ;
1347  if ( isMeasure() )
1348  pointType = QgsWkbTypes::addM( pointType );
1349 
1350  const double *x = mX.constData();
1351  const double *y = mY.constData();
1352  const double *z = is3D() ? mZ.constData() : nullptr;
1353  const double *m = isMeasure() ? mM.constData() : nullptr;
1354 
1355  double distanceTraversed = 0;
1356  double prevX = *x++;
1357  double prevY = *y++;
1358  double prevZ = z ? *z++ : 0.0;
1359  double prevM = m ? *m++ : 0.0;
1360  bool foundStart = false;
1361 
1362  if ( startDistance < 0 )
1363  startDistance = 0;
1364 
1365  for ( int i = 0; i < ( totalPoints - 2 ) ; i += 2 )
1366  {
1367  double x1 = prevX;
1368  double y1 = prevY;
1369  double z1 = prevZ;
1370  double m1 = prevM;
1371 
1372  double x2 = *x++;
1373  double y2 = *y++;
1374  double z2 = z ? *z++ : 0.0;
1375  double m2 = m ? *m++ : 0.0;
1376 
1377  double x3 = *x++;
1378  double y3 = *y++;
1379  double z3 = z ? *z++ : 0.0;
1380  double m3 = m ? *m++ : 0.0;
1381 
1382  bool addedSegmentEnd = false;
1383  const double segmentLength = QgsGeometryUtils::circleLength( x1, y1, x2, y2, x3, y3 );
1384  if ( distanceTraversed <= startDistance && startDistance < distanceTraversed + segmentLength )
1385  {
1386  // start point falls on this segment
1387  const double distanceToStart = startDistance - distanceTraversed;
1388  const QgsPoint startPoint = QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1389  QgsPoint( pointType, x2, y2, z2, m2 ),
1390  QgsPoint( pointType, x3, y3, z3, m3 ), distanceToStart );
1391 
1392  // does end point also fall on this segment?
1393  const bool endPointOnSegment = distanceTraversed + segmentLength > endDistance;
1394  if ( endPointOnSegment )
1395  {
1396  const double distanceToEnd = endDistance - distanceTraversed;
1397  const double midPointDistance = ( distanceToEnd - distanceToStart ) * 0.5 + distanceToStart;
1398  substringPoints << startPoint
1399  << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1400  QgsPoint( pointType, x2, y2, z2, m2 ),
1401  QgsPoint( pointType, x3, y3, z3, m3 ), midPointDistance )
1402  << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1403  QgsPoint( pointType, x2, y2, z2, m2 ),
1404  QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd );
1405  addedSegmentEnd = true;
1406  }
1407  else
1408  {
1409  const double midPointDistance = ( segmentLength - distanceToStart ) * 0.5 + distanceToStart;
1410  substringPoints << startPoint
1411  << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1412  QgsPoint( pointType, x2, y2, z2, m2 ),
1413  QgsPoint( pointType, x3, y3, z3, m3 ), midPointDistance )
1414  << QgsPoint( pointType, x3, y3, z3, m3 );
1415  addedSegmentEnd = true;
1416  }
1417  foundStart = true;
1418  }
1419  if ( !addedSegmentEnd && foundStart && ( distanceTraversed + segmentLength > endDistance ) )
1420  {
1421  // end point falls on this segment
1422  const double distanceToEnd = endDistance - distanceTraversed;
1423  // add mid point, at half way along this arc, then add the interpolated end point
1424  substringPoints << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1425  QgsPoint( pointType, x2, y2, z2, m2 ),
1426  QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd / 2.0 )
1427 
1428  << QgsGeometryUtils::interpolatePointOnArc( QgsPoint( pointType, x1, y1, z1, m1 ),
1429  QgsPoint( pointType, x2, y2, z2, m2 ),
1430  QgsPoint( pointType, x3, y3, z3, m3 ), distanceToEnd );
1431  }
1432  else if ( !addedSegmentEnd && foundStart )
1433  {
1434  substringPoints << QgsPoint( pointType, x2, y2, z2, m2 )
1435  << QgsPoint( pointType, x3, y3, z3, m3 );
1436  }
1437 
1438  prevX = x3;
1439  prevY = y3;
1440  prevZ = z3;
1441  prevM = m3;
1442  distanceTraversed += segmentLength;
1443  if ( distanceTraversed >= endDistance )
1444  break;
1445  }
1446 
1447  // start point is the last node
1448  if ( !foundStart && qgsDoubleNear( distanceTraversed, startDistance ) )
1449  {
1450  substringPoints << QgsPoint( pointType, prevX, prevY, prevZ, prevM )
1451  << QgsPoint( pointType, prevX, prevY, prevZ, prevM )
1452  << QgsPoint( pointType, prevX, prevY, prevZ, prevM );
1453  }
1454 
1455  std::unique_ptr< QgsCircularString > result = qgis::make_unique< QgsCircularString >();
1456  result->setPoints( substringPoints );
1457  return result.release();
1458 }
1459 
1460 bool QgsCircularString::addZValue( double zValue )
1461 {
1462  if ( QgsWkbTypes::hasZ( mWkbType ) )
1463  return false;
1464 
1465  clearCache();
1467 
1468  int nPoints = numPoints();
1469  mZ.clear();
1470  mZ.reserve( nPoints );
1471  for ( int i = 0; i < nPoints; ++i )
1472  {
1473  mZ << zValue;
1474  }
1475  return true;
1476 }
1477 
1478 bool QgsCircularString::addMValue( double mValue )
1479 {
1480  if ( QgsWkbTypes::hasM( mWkbType ) )
1481  return false;
1482 
1483  clearCache();
1485 
1486  int nPoints = numPoints();
1487  mM.clear();
1488  mM.reserve( nPoints );
1489  for ( int i = 0; i < nPoints; ++i )
1490  {
1491  mM << mValue;
1492  }
1493  return true;
1494 }
1495 
1497 {
1498  if ( !QgsWkbTypes::hasZ( mWkbType ) )
1499  return false;
1500 
1501  clearCache();
1502 
1504  mZ.clear();
1505  return true;
1506 }
1507 
1509 {
1510  if ( !QgsWkbTypes::hasM( mWkbType ) )
1511  return false;
1512 
1513  clearCache();
1514 
1516  mM.clear();
1517  return true;
1518 }
1519 
1521 {
1522  std::swap( mX, mY );
1523  clearCache();
1524 }
An abstract base class for classes which transform geometries by transforming input points to output ...
virtual bool transformPoint(double &x, double &y, double &z, double &m)=0
Transforms the point defined by the coordinates (x, y, z) and the specified m value.
SegmentationToleranceType
Segmentation tolerance as maximum angle or maximum difference between approximation and circle.
bool is3D() const SIP_HOLDGIL
Returns true if the geometry is 3D and contains a z-value.
AxisOrder
Axis order for GML generation.
QString wktTypeStr() const
Returns the WKT type string of the geometry.
QgsWkbTypes::Type wkbType() const SIP_HOLDGIL
Returns the WKB type of the geometry.
QgsWkbTypes::Type mWkbType
QgsGeometryConstPartIterator parts() const
Returns Java-style iterator for traversal of parts of the geometry.
bool isMeasure() const SIP_HOLDGIL
Returns true if the geometry contains m values.
void setZMTypeFromSubGeometry(const QgsAbstractGeometry *subggeom, QgsWkbTypes::Type baseGeomType)
Updates the geometry type based on whether sub geometries contain z or m values.
static endian_t endian()
Returns whether this machine uses big or little endian.
Circular string geometry type.
bool equals(const QgsCurve &other) const override
Checks whether this curve exactly equals another curve.
double length() const override
Returns the planar, 2-dimensional length of the geometry.
QgsPoint pointN(int i) const SIP_HOLDGIL
Returns the point at index i within the circular string.
void points(QgsPointSequence &pts) const override
Returns a list of points within the curve.
bool isEmpty() const override SIP_HOLDGIL
Returns true if the geometry is empty.
int numPoints() const override SIP_HOLDGIL
Returns the number of points in the curve.
bool moveVertex(QgsVertexId position, const QgsPoint &newPos) override
Moves a vertex within the geometry.
bool deleteVertex(QgsVertexId position) override
Deletes a vertex within the geometry.
QgsCircularString * snappedToGrid(double hSpacing, double vSpacing, double dSpacing=0, double mSpacing=0) const override
Makes a new geometry with all the points or vertices snapped to the closest point of the grid.
QgsCircularString * clone() const override
Clones the geometry by performing a deep copy.
QString geometryType() const override SIP_HOLDGIL
Returns a unique string representing the geometry type.
bool fromWkb(QgsConstWkbPtr &wkb) override
Sets the geometry from a WKB string.
int dimension() const override SIP_HOLDGIL
Returns the inherent dimension of the geometry.
bool pointAt(int node, QgsPoint &point, QgsVertexId::VertexType &type) const override
Returns the point and vertex id of a point within the curve.
void draw(QPainter &p) const override
Draws the geometry using the specified QPainter.
QgsCircularString * createEmptyWithSameType() const override
Creates a new geometry with the same class and same WKB type as the original and transfers ownership.
double closestSegment(const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf=nullptr, double epsilon=4 *std::numeric_limits< double >::epsilon()) const override
Searches for the closest segment of the geometry to a given point.
void swapXy() override
Swaps the x and y coordinates from the geometry.
bool addMValue(double mValue=0) override
Adds a measure to the geometry, initialized to a preset value.
static QgsCircularString fromTwoPointsAndCenter(const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &center, bool useShortestArc=true)
Creates a circular string with a single arc representing the curve from p1 to p2 with the specified c...
void filterVertices(const std::function< bool(const QgsPoint &) > &filter) override
Filters the vertices from the geometry in place, removing any which do not return true for the filter...
double segmentLength(QgsVertexId startVertex) const override
Returns the length of the segment of the geometry which begins at startVertex.
double xAt(int index) const override SIP_HOLDGIL
Returns the x-coordinate of the specified node in the line string.
bool removeDuplicateNodes(double epsilon=4 *std::numeric_limits< double >::epsilon(), bool useZValues=false) override
Removes duplicate nodes from the geometry, wherever removing the nodes does not result in a degenerat...
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
bool fromWkt(const QString &wkt) override
Sets the geometry from a WKT string.
QDomElement asGml2(QDomDocument &doc, int precision=17, const QString &ns="gml", QgsAbstractGeometry::AxisOrder axisOrder=QgsAbstractGeometry::AxisOrder::XY) const override
Returns a GML2 representation of the geometry.
void sumUpArea(double &sum) const override
Sums up the area of the curve by iterating over the vertices (shoelace formula).
QgsCircularString * reversed() const override
Returns a reversed copy of the curve, where the direction of the curve has been flipped.
QgsRectangle calculateBoundingBox() const override
Default calculator for the minimal bounding box for the geometry.
bool dropMValue() override
Drops any measure values which exist in the geometry.
double yAt(int index) const override SIP_HOLDGIL
Returns the y-coordinate of the specified node in the line string.
QgsPoint endPoint() const override SIP_HOLDGIL
Returns the end point of the curve.
void addToPainterPath(QPainterPath &path) const override
Adds a curve to a painter path.
int wkbSize(QgsAbstractGeometry::WkbFlags flags=QgsAbstractGeometry::WkbFlags()) const override
Returns the length of the QByteArray returned by asWkb()
QgsCircularString * curveSubstring(double startDistance, double endDistance) const override
Returns a new curve representing a substring of this curve.
void drawAsPolygon(QPainter &p) const override
Draws the curve as a polygon on the specified QPainter.
void setPoints(const QgsPointSequence &points)
Sets the circular string's points.
QgsLineString * curveToLine(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const override
Returns a new line string geometry corresponding to a segmentized approximation of the curve.
double vertexAngle(QgsVertexId vertex) const override
Returns approximate angle at a vertex.
bool dropZValue() override
Drops any z-dimensions which exist in the geometry.
QDomElement asGml3(QDomDocument &doc, int precision=17, const QString &ns="gml", QgsAbstractGeometry::AxisOrder axisOrder=QgsAbstractGeometry::AxisOrder::XY) const override
Returns a GML3 representation of the geometry.
QByteArray asWkb(QgsAbstractGeometry::WkbFlags flags=QgsAbstractGeometry::WkbFlags()) const override
void transform(const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d=QgsCoordinateTransform::ForwardTransform, bool transformZ=false) override SIP_THROW(QgsCsException)
Transforms the geometry using a coordinate transform.
void clear() override
Clears the geometry, ie reset it to a null geometry.
QString asWkt(int precision=17) const override
Returns a WKT representation of the geometry.
bool isValid(QString &error, int flags=0) const override
Checks validity of the geometry, and returns true if the geometry is valid.
bool hasCurvedSegments() const override
Returns true if the geometry contains curved segments.
QgsPoint * interpolatePoint(double distance) const override
Returns an interpolated point on the curve at the specified distance.
bool insertVertex(QgsVertexId position, const QgsPoint &vertex) override
Inserts a vertex into the geometry.
QgsPoint startPoint() const override SIP_HOLDGIL
Returns the starting point of the curve.
json asJsonObject(int precision=17) const override
Returns a json object representation of the geometry.
void transformVertices(const std::function< QgsPoint(const QgsPoint &) > &transform) override
Transforms the vertices from the geometry in place, applying the transform function to every vertex.
QgsCircularString() SIP_HOLDGIL
Constructs an empty circular string.
A const WKB pointer.
Definition: qgswkbptr.h:130
QgsWkbTypes::Type readHeader() const
readHeader
Definition: qgswkbptr.cpp:54
Class for doing transforms between two map coordinate systems.
TransformDirection
Enum used to indicate the direction (forward or inverse) of the transform.
void transformCoords(int numPoint, double *x, double *y, double *z, TransformDirection direction=ForwardTransform) const SIP_THROW(QgsCsException)
Transform an array of coordinates to the destination CRS.
Abstract base class for curved geometry type.
Definition: qgscurve.h:36
void clearCache() const override
Clears any cached parameters associated with the geometry, e.g., bounding boxes.
Definition: qgscurve.cpp:257
bool snapToGridPrivate(double hSpacing, double vSpacing, double dSpacing, double mSpacing, const QVector< double > &srcX, const QVector< double > &srcY, const QVector< double > &srcZ, const QVector< double > &srcM, QVector< double > &outX, QVector< double > &outY, QVector< double > &outZ, QVector< double > &outM) const
Helper function for QgsCurve subclasses to snap to grids.
Definition: qgscurve.cpp:280
bool isValid(QString &error, int flags=0) const override
Checks validity of the geometry, and returns true if the geometry is valid.
Definition: qgscurve.cpp:211
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:45
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
static QPair< QgsWkbTypes::Type, QString > wktReadBlock(const QString &wkt)
Parses a WKT block of the format "TYPE( contents )" and returns a pair of geometry type to contents (...
static double circleTangentDirection(const QgsPoint &tangentPoint, const QgsPoint &cp1, const QgsPoint &cp2, const QgsPoint &cp3) SIP_HOLDGIL
Calculates the direction angle of a circle tangent (clockwise from north in radians)
static double sqrDistance2D(const QgsPoint &pt1, const QgsPoint &pt2) SIP_HOLDGIL
Returns the squared 2D distance between two points.
static double ccwAngle(double dy, double dx) SIP_HOLDGIL
Returns the counter clockwise angle between a line with components dx, dy and the line with dx > 0 an...
static void pointsToWKB(QgsWkbPtr &wkb, const QgsPointSequence &points, bool is3D, bool isMeasure)
Returns a LinearRing { uint32 numPoints; Point points[numPoints]; }.
static QgsPoint segmentMidPointFromCenter(const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &center, bool useShortestArc=true) SIP_HOLDGIL
Calculates the midpoint on the circle passing through p1 and p2, with the specified center coordinate...
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 averageAngle(double x1, double y1, double x2, double y2, double x3, double y3) SIP_HOLDGIL
Calculates the average angle (in radians) between the two linear segments from (x1,...
static bool angleOnCircle(double angle, double angle1, double angle2, double angle3) SIP_HOLDGIL
Returns true if an angle is between angle1 and angle3 on a circle described by angle1,...
static bool circleClockwise(double angle1, double angle2, double angle3) SIP_HOLDGIL
Returns true if the circle defined by three angles is ordered clockwise.
static double sweepAngle(double centerX, double centerY, double x1, double y1, double x2, double y2, double x3, double y3) SIP_HOLDGIL
Calculates angle of a circular string part defined by pt1, pt2, pt3.
static double circleLength(double x1, double y1, double x2, double y2, double x3, double y3) SIP_HOLDGIL
Length of a circular string segment defined by pt1, pt2, pt3.
static QgsPoint pointOnLineWithDistance(const QgsPoint &startPoint, const QgsPoint &directionPoint, double distance) SIP_HOLDGIL
Returns a point a specified distance toward a second point.
static QgsPointSequence pointsFromWKT(const QString &wktCoordinateList, bool is3D, bool isMeasure)
Returns a list of points contained in a WKT string.
static void segmentizeArc(const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, QgsPointSequence &points, double tolerance=M_PI_2/90, QgsAbstractGeometry::SegmentationToleranceType toleranceType=QgsAbstractGeometry::MaximumAngle, bool hasZ=false, bool hasM=false)
Convert circular arc defined by p1, p2, p3 (p1/p3 being start resp.
static bool circleAngleBetween(double angle, double angle1, double angle2, bool clockwise) SIP_HOLDGIL
Returns true if, in a circle, angle is between angle1 and angle2.
static QgsPoint interpolatePointOnArc(const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3, double distance) SIP_HOLDGIL
Interpolates a point on an arc defined by three points, pt1, pt2 and pt3.
static QDomElement pointsToGML3(const QgsPointSequence &points, QDomDocument &doc, int precision, const QString &ns, bool is3D, QgsAbstractGeometry::AxisOrder axisOrder=QgsAbstractGeometry::AxisOrder::XY)
Returns a gml::posList DOM element.
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).
static QString pointsToWKT(const QgsPointSequence &points, int precision, bool is3D, bool isMeasure)
Returns a WKT coordinate list.
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:44
void setPoints(const QgsPointSequence &points)
Resets the line string to match the specified list of points.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:38
void setX(double x) SIP_HOLDGIL
Sets the point's x-coordinate.
Definition: qgspoint.h:269
Q_GADGET double x
Definition: qgspoint.h:41
void setY(double y) SIP_HOLDGIL
Sets the point's y-coordinate.
Definition: qgspoint.h:280
double z
Definition: qgspoint.h:43
double m
Definition: qgspoint.h:44
double y
Definition: qgspoint.h:42
A rectangle specified with double values.
Definition: qgsrectangle.h:42
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
Definition: qgsrectangle.h:359
WKB pointer handler.
Definition: qgswkbptr.h:44
static bool hasM(Type type) SIP_HOLDGIL
Tests whether a WKB type contains m values.
Definition: qgswkbtypes.h:1100
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:70
static Type dropZ(Type type) SIP_HOLDGIL
Drops the z dimension (if present) for a WKB type and returns the new type.
Definition: qgswkbtypes.h:1207
static Type flatType(Type type) SIP_HOLDGIL
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:702
static Type addZ(Type type) SIP_HOLDGIL
Adds the z dimension to a WKB type and returns the new type.
Definition: qgswkbtypes.h:1146
static bool hasZ(Type type) SIP_HOLDGIL
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:1050
static Type addM(Type type) SIP_HOLDGIL
Adds the m dimension to a WKB type and returns the new type.
Definition: qgswkbtypes.h:1171
static Type dropM(Type type) SIP_HOLDGIL
Drops the m dimension (if present) for a WKB type and returns the new type.
Definition: qgswkbtypes.h:1225
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
double ANALYSIS_EXPORT leftOf(const QgsPoint &thepoint, const QgsPoint *p1, const QgsPoint *p2)
Returns whether 'thepoint' is left or right of the line from 'p1' to 'p2'. Negative values mean left ...
Definition: MathUtils.cpp:292
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:316
QVector< QgsPoint > QgsPointSequence
int precision
Utility class for identifying a unique vertex within a geometry.
int vertex
Vertex number.
int part
Part number.
int ring
Ring number.
VertexType
Type of vertex.
@ SegmentVertex
The actual start or end point of a segment.
@ CurveVertex
An intermediate point on a segment defining the curvature of the segment.