QGIS API Documentation  3.0.2-Girona (307d082)
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 <QPainter>
28 #include <QPainterPath>
29 #include <memory>
30 
32 {
34 }
35 
36 bool QgsCircularString::equals( const QgsCurve &other ) const
37 {
38  const QgsCircularString *otherLine = dynamic_cast< const QgsCircularString * >( &other );
39  if ( !otherLine )
40  return false;
41 
42  if ( mWkbType != otherLine->mWkbType )
43  return false;
44 
45  if ( mX.count() != otherLine->mX.count() )
46  return false;
47 
48  for ( int i = 0; i < mX.count(); ++i )
49  {
50  if ( !qgsDoubleNear( mX.at( i ), otherLine->mX.at( i ) )
51  || !qgsDoubleNear( mY.at( i ), otherLine->mY.at( i ) ) )
52  return false;
53 
54  if ( is3D() && !qgsDoubleNear( mZ.at( i ), otherLine->mZ.at( i ) ) )
55  return false;
56 
57  if ( isMeasure() && !qgsDoubleNear( mM.at( i ), otherLine->mM.at( i ) ) )
58  return false;
59  }
60 
61  return true;
62 }
63 
65 {
66  auto result = qgis::make_unique< QgsCircularString >();
67  result->mWkbType = mWkbType;
68  return result.release();
69 }
70 
72 {
73  return QStringLiteral( "CircularString" );
74 }
75 
77 {
78  return 1;
79 }
80 
82 {
83  return new QgsCircularString( *this );
84 }
85 
87 {
89  mX.clear();
90  mY.clear();
91  mZ.clear();
92  mM.clear();
93  clearCache();
94 }
95 
97 {
98  QgsRectangle bbox;
99  int nPoints = numPoints();
100  for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
101  {
102  if ( i == 0 )
103  {
104  bbox = segmentBoundingBox( QgsPoint( mX[i], mY[i] ), QgsPoint( mX[i + 1], mY[i + 1] ), QgsPoint( mX[i + 2], mY[i + 2] ) );
105  }
106  else
107  {
108  QgsRectangle segmentBox = segmentBoundingBox( QgsPoint( mX[i], mY[i] ), QgsPoint( mX[i + 1], mY[i + 1] ), QgsPoint( mX[i + 2], mY[i + 2] ) );
109  bbox.combineExtentWith( segmentBox );
110  }
111  }
112 
113  if ( nPoints > 0 && nPoints % 2 == 0 )
114  {
115  if ( nPoints == 2 )
116  {
117  bbox.combineExtentWith( mX[ 0 ], mY[ 0 ] );
118  }
119  bbox.combineExtentWith( mX[ nPoints - 1 ], mY[ nPoints - 1 ] );
120  }
121  return bbox;
122 }
123 
124 QgsRectangle QgsCircularString::segmentBoundingBox( const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3 )
125 {
126  double centerX, centerY, radius;
127  QgsGeometryUtils::circleCenterRadius( pt1, pt2, pt3, radius, centerX, centerY );
128 
129  double p1Angle = QgsGeometryUtils::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
130  double p2Angle = QgsGeometryUtils::ccwAngle( pt2.y() - centerY, pt2.x() - centerX );
131  double p3Angle = QgsGeometryUtils::ccwAngle( pt3.y() - centerY, pt3.x() - centerX );
132 
133  //start point, end point and compass points in between can be on bounding box
134  QgsRectangle bbox( pt1.x(), pt1.y(), pt1.x(), pt1.y() );
135  bbox.combineExtentWith( pt3.x(), pt3.y() );
136 
137  QgsPointSequence compassPoints = compassPointsOnSegment( p1Angle, p2Angle, p3Angle, centerX, centerY, radius );
138  QgsPointSequence::const_iterator cpIt = compassPoints.constBegin();
139  for ( ; cpIt != compassPoints.constEnd(); ++cpIt )
140  {
141  bbox.combineExtentWith( cpIt->x(), cpIt->y() );
142  }
143  return bbox;
144 }
145 
146 QgsPointSequence QgsCircularString::compassPointsOnSegment( double p1Angle, double p2Angle, double p3Angle, double centerX, double centerY, double radius )
147 {
148  QgsPointSequence pointList;
149 
150  QgsPoint nPoint( centerX, centerY + radius );
151  QgsPoint ePoint( centerX + radius, centerY );
152  QgsPoint sPoint( centerX, centerY - radius );
153  QgsPoint wPoint( centerX - radius, centerY );
154 
155  if ( p3Angle >= p1Angle )
156  {
157  if ( p2Angle > p1Angle && p2Angle < p3Angle )
158  {
159  if ( p1Angle <= 90 && p3Angle >= 90 )
160  {
161  pointList.append( nPoint );
162  }
163  if ( p1Angle <= 180 && p3Angle >= 180 )
164  {
165  pointList.append( wPoint );
166  }
167  if ( p1Angle <= 270 && p3Angle >= 270 )
168  {
169  pointList.append( sPoint );
170  }
171  }
172  else
173  {
174  pointList.append( ePoint );
175  if ( p1Angle >= 90 || p3Angle <= 90 )
176  {
177  pointList.append( nPoint );
178  }
179  if ( p1Angle >= 180 || p3Angle <= 180 )
180  {
181  pointList.append( wPoint );
182  }
183  if ( p1Angle >= 270 || p3Angle <= 270 )
184  {
185  pointList.append( sPoint );
186  }
187  }
188  }
189  else
190  {
191  if ( p2Angle < p1Angle && p2Angle > p3Angle )
192  {
193  if ( p1Angle >= 270 && p3Angle <= 270 )
194  {
195  pointList.append( sPoint );
196  }
197  if ( p1Angle >= 180 && p3Angle <= 180 )
198  {
199  pointList.append( wPoint );
200  }
201  if ( p1Angle >= 90 && p3Angle <= 90 )
202  {
203  pointList.append( nPoint );
204  }
205  }
206  else
207  {
208  pointList.append( ePoint );
209  if ( p1Angle <= 270 || p3Angle >= 270 )
210  {
211  pointList.append( sPoint );
212  }
213  if ( p1Angle <= 180 || p3Angle >= 180 )
214  {
215  pointList.append( wPoint );
216  }
217  if ( p1Angle <= 90 || p3Angle >= 90 )
218  {
219  pointList.append( nPoint );
220  }
221  }
222  }
223  return pointList;
224 }
225 
227 {
228  if ( !wkbPtr )
229  return false;
230 
231  QgsWkbTypes::Type type = wkbPtr.readHeader();
233  {
234  return false;
235  }
236  clearCache();
237  mWkbType = type;
238 
239  //type
240  bool hasZ = is3D();
241  bool hasM = isMeasure();
242  int nVertices = 0;
243  wkbPtr >> nVertices;
244  mX.resize( nVertices );
245  mY.resize( nVertices );
246  hasZ ? mZ.resize( nVertices ) : mZ.clear();
247  hasM ? mM.resize( nVertices ) : mM.clear();
248  for ( int i = 0; i < nVertices; ++i )
249  {
250  wkbPtr >> mX[i];
251  wkbPtr >> mY[i];
252  if ( hasZ )
253  {
254  wkbPtr >> mZ[i];
255  }
256  if ( hasM )
257  {
258  wkbPtr >> mM[i];
259  }
260  }
261 
262  return true;
263 }
264 
265 bool QgsCircularString::fromWkt( const QString &wkt )
266 {
267  clear();
268 
269  QPair<QgsWkbTypes::Type, QString> parts = QgsGeometryUtils::wktReadBlock( wkt );
270 
271  if ( QgsWkbTypes::flatType( parts.first ) != QgsWkbTypes::CircularString )
272  return false;
273  mWkbType = parts.first;
274 
275  setPoints( QgsGeometryUtils::pointsFromWKT( parts.second, is3D(), isMeasure() ) );
276  return true;
277 }
278 
279 QByteArray QgsCircularString::asWkb() const
280 {
281  int binarySize = sizeof( char ) + sizeof( quint32 ) + sizeof( quint32 );
282  binarySize += numPoints() * ( 2 + is3D() + isMeasure() ) * sizeof( double );
283 
284  QByteArray wkbArray;
285  wkbArray.resize( binarySize );
286  QgsWkbPtr wkb( wkbArray );
287  wkb << static_cast<char>( QgsApplication::endian() );
288  wkb << static_cast<quint32>( wkbType() );
289  QgsPointSequence pts;
290  points( pts );
291  QgsGeometryUtils::pointsToWKB( wkb, pts, is3D(), isMeasure() );
292  return wkbArray;
293 }
294 
295 QString QgsCircularString::asWkt( int precision ) const
296 {
297  QString wkt = wktTypeStr() + ' ';
298  QgsPointSequence pts;
299  points( pts );
300  wkt += QgsGeometryUtils::pointsToWKT( pts, precision, is3D(), isMeasure() );
301  return wkt;
302 }
303 
304 QDomElement QgsCircularString::asGml2( QDomDocument &doc, int precision, const QString &ns ) const
305 {
306  // GML2 does not support curves
307  std::unique_ptr< QgsLineString > line( curveToLine() );
308  QDomElement gml = line->asGml2( doc, precision, ns );
309  return gml;
310 }
311 
312 QDomElement QgsCircularString::asGml3( QDomDocument &doc, int precision, const QString &ns ) const
313 {
314  QgsPointSequence pts;
315  points( pts );
316 
317  QDomElement elemCurve = doc.createElementNS( ns, QStringLiteral( "Curve" ) );
318 
319  if ( isEmpty() )
320  return elemCurve;
321 
322  QDomElement elemSegments = doc.createElementNS( ns, QStringLiteral( "segments" ) );
323  QDomElement elemArcString = doc.createElementNS( ns, QStringLiteral( "ArcString" ) );
324  elemArcString.appendChild( QgsGeometryUtils::pointsToGML3( pts, doc, precision, ns, is3D() ) );
325  elemSegments.appendChild( elemArcString );
326  elemCurve.appendChild( elemSegments );
327  return elemCurve;
328 }
329 
330 QString QgsCircularString::asJson( int precision ) const
331 {
332  // GeoJSON does not support curves
333  std::unique_ptr< QgsLineString > line( curveToLine() );
334  QString json = line->asJson( precision );
335  return json;
336 }
337 
339 {
340  return mX.isEmpty();
341 }
342 
343 //curve interface
345 {
346  int nPoints = numPoints();
347  double length = 0;
348  for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
349  {
350  length += QgsGeometryUtils::circleLength( mX[i], mY[i], mX[i + 1], mY[i + 1], mX[i + 2], mY[i + 2] );
351  }
352  return length;
353 }
354 
356 {
357  if ( numPoints() < 1 )
358  {
359  return QgsPoint();
360  }
361  return pointN( 0 );
362 }
363 
365 {
366  if ( numPoints() < 1 )
367  {
368  return QgsPoint();
369  }
370  return pointN( numPoints() - 1 );
371 }
372 
374 {
375  QgsLineString *line = new QgsLineString();
377  int nPoints = numPoints();
378 
379  for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
380  {
381  QgsGeometryUtils::segmentizeArc( pointN( i ), pointN( i + 1 ), pointN( i + 2 ), points, tolerance, toleranceType, is3D(), isMeasure() );
382  }
383 
384  line->setPoints( points );
385  return line;
386 }
387 
388 QgsCircularString *QgsCircularString::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing ) const
389 {
390  // prepare result
391  std::unique_ptr<QgsCircularString> result { createEmptyWithSameType() };
392 
393  bool res = snapToGridPrivate( hSpacing, vSpacing, dSpacing, mSpacing, mX, mY, mZ, mM,
394  result->mX, result->mY, result->mZ, result->mM );
395  if ( res )
396  return result.release();
397  else
398  return nullptr;
399 }
400 
401 bool QgsCircularString::removeDuplicateNodes( double epsilon, bool useZValues )
402 {
403  if ( mX.count() <= 3 )
404  return false; // don't create degenerate lines
405  bool result = false;
406  double prevX = mX.at( 0 );
407  double prevY = mY.at( 0 );
408  bool hasZ = is3D();
409  bool useZ = hasZ && useZValues;
410  double prevZ = useZ ? mZ.at( 0 ) : 0;
411  int i = 1;
412  int remaining = mX.count();
413  // we have to consider points in pairs, since a segment can validly have the same start and
414  // end if it has a different curve point
415  while ( i + 1 < remaining )
416  {
417  double currentCurveX = mX.at( i );
418  double currentCurveY = mY.at( i );
419  double currentX = mX.at( i + 1 );
420  double currentY = mY.at( i + 1 );
421  double currentZ = useZ ? mZ.at( i + 1 ) : 0;
422  if ( qgsDoubleNear( currentCurveX, prevX, epsilon ) &&
423  qgsDoubleNear( currentCurveY, prevY, epsilon ) &&
424  qgsDoubleNear( currentX, prevX, epsilon ) &&
425  qgsDoubleNear( currentY, prevY, epsilon ) &&
426  ( !useZ || qgsDoubleNear( currentZ, prevZ, epsilon ) ) )
427  {
428  result = true;
429  // remove point
430  mX.removeAt( i );
431  mX.removeAt( i );
432  mY.removeAt( i );
433  mY.removeAt( i );
434  if ( hasZ )
435  {
436  mZ.removeAt( i );
437  mZ.removeAt( i );
438  }
439  remaining -= 2;
440  }
441  else
442  {
443  prevX = currentX;
444  prevY = currentY;
445  prevZ = currentZ;
446  i += 2;
447  }
448  }
449  return result;
450 }
451 
453 {
454  return std::min( mX.size(), mY.size() );
455 }
456 
458 {
459  if ( i < 0 || std::min( mX.size(), mY.size() ) <= i )
460  {
461  return QgsPoint();
462  }
463 
464  double x = mX.at( i );
465  double y = mY.at( i );
466  double z = 0;
467  double m = 0;
468 
469  if ( is3D() )
470  {
471  z = mZ.at( i );
472  }
473  if ( isMeasure() )
474  {
475  m = mM.at( i );
476  }
477 
479  if ( is3D() && isMeasure() )
480  {
482  }
483  else if ( is3D() )
484  {
486  }
487  else if ( isMeasure() )
488  {
490  }
491  return QgsPoint( t, x, y, z, m );
492 }
493 
494 double QgsCircularString::xAt( int index ) const
495 {
496  if ( index >= 0 && index < mX.size() )
497  return mX.at( index );
498  else
499  return 0.0;
500 }
501 
502 double QgsCircularString::yAt( int index ) const
503 {
504  if ( index >= 0 && index < mY.size() )
505  return mY.at( index );
506  else
507  return 0.0;
508 }
509 
511 {
512  pts.clear();
513  int nPts = numPoints();
514  for ( int i = 0; i < nPts; ++i )
515  {
516  pts.push_back( pointN( i ) );
517  }
518 }
519 
521 {
522  clearCache();
523 
524  if ( points.empty() )
525  {
527  mX.clear();
528  mY.clear();
529  mZ.clear();
530  mM.clear();
531  return;
532  }
533 
534  //get wkb type from first point
535  const QgsPoint &firstPt = points.at( 0 );
536  bool hasZ = firstPt.is3D();
537  bool hasM = firstPt.isMeasure();
538 
540 
541  mX.resize( points.size() );
542  mY.resize( points.size() );
543  if ( hasZ )
544  {
545  mZ.resize( points.size() );
546  }
547  else
548  {
549  mZ.clear();
550  }
551  if ( hasM )
552  {
553  mM.resize( points.size() );
554  }
555  else
556  {
557  mM.clear();
558  }
559 
560  for ( int i = 0; i < points.size(); ++i )
561  {
562  mX[i] = points[i].x();
563  mY[i] = points[i].y();
564  if ( hasZ )
565  {
566  double z = points.at( i ).z();
567  mZ[i] = std::isnan( z ) ? 0 : z;
568  }
569  if ( hasM )
570  {
571  double m = points.at( i ).m();
572  mM[i] = std::isnan( m ) ? 0 : m;
573  }
574  }
575 }
576 
577 void QgsCircularString::draw( QPainter &p ) const
578 {
579  QPainterPath path;
580  addToPainterPath( path );
581  p.drawPath( path );
582 }
583 
585 {
586  clearCache();
587 
588  double *zArray = mZ.data();
589 
590  bool hasZ = is3D();
591  int nPoints = numPoints();
592  bool useDummyZ = !hasZ || !transformZ;
593  if ( useDummyZ )
594  {
595  zArray = new double[nPoints];
596  for ( int i = 0; i < nPoints; ++i )
597  {
598  zArray[i] = 0;
599  }
600  }
601  ct.transformCoords( nPoints, mX.data(), mY.data(), zArray, d );
602  if ( useDummyZ )
603  {
604  delete[] zArray;
605  }
606 }
607 
608 void QgsCircularString::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
609 {
610  clearCache();
611 
612  int nPoints = numPoints();
613  bool hasZ = is3D();
614  bool hasM = isMeasure();
615  for ( int i = 0; i < nPoints; ++i )
616  {
617  qreal x, y;
618  t.map( mX.at( i ), mY.at( i ), &x, &y );
619  mX[i] = x;
620  mY[i] = y;
621  if ( hasZ )
622  {
623  mZ[i] = mZ.at( i ) * zScale + zTranslate;
624  }
625  if ( hasM )
626  {
627  mM[i] = mM.at( i ) * mScale + mTranslate;
628  }
629  }
630 }
631 
632 void QgsCircularString::addToPainterPath( QPainterPath &path ) const
633 {
634  int nPoints = numPoints();
635  if ( nPoints < 1 )
636  {
637  return;
638  }
639 
640  if ( path.isEmpty() || path.currentPosition() != QPointF( mX[0], mY[0] ) )
641  {
642  path.moveTo( QPointF( mX[0], mY[0] ) );
643  }
644 
645  for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
646  {
647  QgsPointSequence pt;
648  QgsGeometryUtils::segmentizeArc( QgsPoint( mX[i], mY[i] ), QgsPoint( mX[i + 1], mY[i + 1] ), QgsPoint( mX[i + 2], mY[i + 2] ), pt );
649  for ( int j = 1; j < pt.size(); ++j )
650  {
651  path.lineTo( pt.at( j ).x(), pt.at( j ).y() );
652  }
653 #if 0
654  //arcTo( path, QPointF( mX[i], mY[i] ), QPointF( mX[i + 1], mY[i + 1] ), QPointF( mX[i + 2], mY[i + 2] ) );
655 #endif
656  }
657 
658  //if number of points is even, connect to last point with straight line (even though the circular string is not valid)
659  if ( nPoints % 2 == 0 )
660  {
661  path.lineTo( mX[ nPoints - 1 ], mY[ nPoints - 1 ] );
662  }
663 }
664 
665 #if 0
666 void QgsCircularString::arcTo( QPainterPath &path, QPointF pt1, QPointF pt2, QPointF pt3 )
667 {
668  double centerX, centerY, radius;
669  QgsGeometryUtils::circleCenterRadius( QgsPoint( pt1.x(), pt1.y() ), QgsPoint( pt2.x(), pt2.y() ), QgsPoint( pt3.x(), pt3.y() ),
670  radius, centerX, centerY );
671 
672  double p1Angle = QgsGeometryUtils::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
673  double sweepAngle = QgsGeometryUtils::sweepAngle( centerX, centerY, pt1.x(), pt1.y(), pt2.x(), pt2.y(), pt3.x(), pt3.y() );
674 
675  double diameter = 2 * radius;
676  path.arcTo( centerX - radius, centerY - radius, diameter, diameter, p1Angle, sweepAngle );
677 }
678 #endif
679 
680 void QgsCircularString::drawAsPolygon( QPainter &p ) const
681 {
682  draw( p );
683 }
684 
685 bool QgsCircularString::insertVertex( QgsVertexId position, const QgsPoint &vertex )
686 {
687  if ( position.vertex >= mX.size() || position.vertex < 1 )
688  {
689  return false;
690  }
691 
692  mX.insert( position.vertex, vertex.x() );
693  mY.insert( position.vertex, vertex.y() );
694  if ( is3D() )
695  {
696  mZ.insert( position.vertex, vertex.z() );
697  }
698  if ( isMeasure() )
699  {
700  mM.insert( position.vertex, vertex.m() );
701  }
702 
703  bool vertexNrEven = ( position.vertex % 2 == 0 );
704  if ( vertexNrEven )
705  {
706  insertVertexBetween( position.vertex - 2, position.vertex - 1, position.vertex );
707  }
708  else
709  {
710  insertVertexBetween( position.vertex, position.vertex + 1, position.vertex - 1 );
711  }
712  clearCache(); //set bounding box invalid
713  return true;
714 }
715 
716 bool QgsCircularString::moveVertex( QgsVertexId position, const QgsPoint &newPos )
717 {
718  if ( position.vertex < 0 || position.vertex >= mX.size() )
719  {
720  return false;
721  }
722 
723  mX[position.vertex] = newPos.x();
724  mY[position.vertex] = newPos.y();
725  if ( is3D() && newPos.is3D() )
726  {
727  mZ[position.vertex] = newPos.z();
728  }
729  if ( isMeasure() && newPos.isMeasure() )
730  {
731  mM[position.vertex] = newPos.m();
732  }
733  clearCache(); //set bounding box invalid
734  return true;
735 }
736 
738 {
739  int nVertices = this->numPoints();
740  if ( nVertices < 4 ) //circular string must have at least 3 vertices
741  {
742  clear();
743  return true;
744  }
745  if ( position.vertex < 0 || position.vertex > ( nVertices - 1 ) )
746  {
747  return false;
748  }
749 
750  if ( position.vertex < ( nVertices - 2 ) )
751  {
752  //remove this and the following vertex
753  deleteVertex( position.vertex + 1 );
754  deleteVertex( position.vertex );
755  }
756  else //remove this and the preceding vertex
757  {
758  deleteVertex( position.vertex );
759  deleteVertex( position.vertex - 1 );
760  }
761 
762  clearCache(); //set bounding box invalid
763  return true;
764 }
765 
767 {
768  mX.remove( i );
769  mY.remove( i );
770  if ( is3D() )
771  {
772  mZ.remove( i );
773  }
774  if ( isMeasure() )
775  {
776  mM.remove( i );
777  }
778  clearCache();
779 }
780 
781 double QgsCircularString::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
782 {
783  double minDist = std::numeric_limits<double>::max();
784  QgsPoint minDistSegmentPoint;
785  QgsVertexId minDistVertexAfter;
786  int minDistLeftOf = 0;
787 
788  double currentDist = 0.0;
789 
790  int nPoints = numPoints();
791  for ( int i = 0; i < ( nPoints - 2 ) ; i += 2 )
792  {
793  currentDist = closestPointOnArc( mX[i], mY[i], mX[i + 1], mY[i + 1], mX[i + 2], mY[i + 2], pt, segmentPt, vertexAfter, leftOf, epsilon );
794  if ( currentDist < minDist )
795  {
796  minDist = currentDist;
797  minDistSegmentPoint = segmentPt;
798  minDistVertexAfter.vertex = vertexAfter.vertex + i;
799  if ( leftOf )
800  {
801  minDistLeftOf = *leftOf;
802  }
803  }
804  }
805 
806  if ( minDist == std::numeric_limits<double>::max() )
807  return -1; // error: no segments
808 
809  segmentPt = minDistSegmentPoint;
810  vertexAfter = minDistVertexAfter;
811  vertexAfter.part = 0;
812  vertexAfter.ring = 0;
813  if ( leftOf )
814  {
815  *leftOf = qgsDoubleNear( minDist, 0.0 ) ? 0 : minDistLeftOf;
816  }
817  return minDist;
818 }
819 
820 bool QgsCircularString::pointAt( int node, QgsPoint &point, QgsVertexId::VertexType &type ) const
821 {
822  if ( node < 0 || node >= numPoints() )
823  {
824  return false;
825  }
826  point = pointN( node );
827  type = ( node % 2 == 0 ) ? QgsVertexId::SegmentVertex : QgsVertexId::CurveVertex;
828  return true;
829 }
830 
831 void QgsCircularString::sumUpArea( double &sum ) const
832 {
833  int maxIndex = numPoints() - 2;
834 
835  for ( int i = 0; i < maxIndex; i += 2 )
836  {
837  QgsPoint p1( mX[i], mY[i] );
838  QgsPoint p2( mX[i + 1], mY[i + 1] );
839  QgsPoint p3( mX[i + 2], mY[i + 2] );
840 
841  //segment is a full circle, p2 is the center point
842  if ( p1 == p3 )
843  {
844  double r2 = QgsGeometryUtils::sqrDistance2D( p1, p2 ) / 4.0;
845  sum += M_PI * r2;
846  continue;
847  }
848 
849  sum += 0.5 * ( mX[i] * mY[i + 2] - mY[i] * mX[i + 2] );
850 
851  //calculate area between circle and chord, then sum / subtract from total area
852  double midPointX = ( p1.x() + p3.x() ) / 2.0;
853  double midPointY = ( p1.y() + p3.y() ) / 2.0;
854 
855  double radius, centerX, centerY;
856  QgsGeometryUtils::circleCenterRadius( p1, p2, p3, radius, centerX, centerY );
857 
858  double d = std::sqrt( QgsGeometryUtils::sqrDistance2D( QgsPoint( centerX, centerY ), QgsPoint( midPointX, midPointY ) ) );
859  double r2 = radius * radius;
860 
861  if ( d > radius )
862  {
863  //d cannot be greater than radius, something must be wrong...
864  continue;
865  }
866 
867  bool circlePointLeftOfLine = QgsGeometryUtils::leftOfLine( p2.x(), p2.y(), p1.x(), p1.y(), p3.x(), p3.y() ) < 0;
868  bool centerPointLeftOfLine = QgsGeometryUtils::leftOfLine( centerX, centerY, p1.x(), p1.y(), p3.x(), p3.y() ) < 0;
869 
870  double cov = 0.5 - d * std::sqrt( r2 - d * d ) / ( M_PI * r2 ) - M_1_PI * std::asin( d / radius );
871  double circleChordArea = 0;
872  if ( circlePointLeftOfLine == centerPointLeftOfLine )
873  {
874  circleChordArea = M_PI * r2 * ( 1 - cov );
875  }
876  else
877  {
878  circleChordArea = M_PI * r2 * cov;
879  }
880 
881  if ( !circlePointLeftOfLine )
882  {
883  sum += circleChordArea;
884  }
885  else
886  {
887  sum -= circleChordArea;
888  }
889  }
890 }
891 
893 {
894  return true;
895 }
896 
897 double QgsCircularString::closestPointOnArc( double x1, double y1, double x2, double y2, double x3, double y3,
898  const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon )
899 {
900  double radius, centerX, centerY;
901  QgsPoint pt1( x1, y1 );
902  QgsPoint pt2( x2, y2 );
903  QgsPoint pt3( x3, y3 );
904 
905  QgsGeometryUtils::circleCenterRadius( pt1, pt2, pt3, radius, centerX, centerY );
906  double angle = QgsGeometryUtils::ccwAngle( pt.y() - centerY, pt.x() - centerX );
907  double angle1 = QgsGeometryUtils::ccwAngle( pt1.y() - centerY, pt1.x() - centerX );
908  double angle2 = QgsGeometryUtils::ccwAngle( pt2.y() - centerY, pt2.x() - centerX );
909  double angle3 = QgsGeometryUtils::ccwAngle( pt3.y() - centerY, pt3.x() - centerX );
910 
911  bool clockwise = QgsGeometryUtils::circleClockwise( angle1, angle2, angle3 );
912 
913  if ( QgsGeometryUtils::angleOnCircle( angle, angle1, angle2, angle3 ) )
914  {
915  //get point on line center -> pt with distance radius
916  segmentPt = QgsGeometryUtils::pointOnLineWithDistance( QgsPoint( centerX, centerY ), pt, radius );
917 
918  //vertexAfter
919  vertexAfter.vertex = QgsGeometryUtils::circleAngleBetween( angle, angle1, angle2, clockwise ) ? 1 : 2;
920  }
921  else
922  {
923  double distPtPt1 = QgsGeometryUtils::sqrDistance2D( pt, pt1 );
924  double distPtPt3 = QgsGeometryUtils::sqrDistance2D( pt, pt3 );
925  segmentPt = ( distPtPt1 <= distPtPt3 ) ? pt1 : pt3;
926  vertexAfter.vertex = ( distPtPt1 <= distPtPt3 ) ? 1 : 2;
927  }
928 
929  double sqrDistance = QgsGeometryUtils::sqrDistance2D( segmentPt, pt );
930  //prevent rounding errors if the point is directly on the segment
931  if ( qgsDoubleNear( sqrDistance, 0.0, epsilon ) )
932  {
933  segmentPt.setX( pt.x() );
934  segmentPt.setY( pt.y() );
935  sqrDistance = 0.0;
936  }
937 
938  if ( leftOf )
939  {
940  double sqrDistancePointToCenter = ( pt.x() - centerX ) * ( pt.x() - centerX ) + ( pt.y() - centerY ) * ( pt.y() - centerY );
941  *leftOf = clockwise ? ( sqrDistancePointToCenter > radius * radius ? -1 : 1 )
942  : ( sqrDistancePointToCenter < radius * radius ? -1 : 1 );
943  }
944 
945  return sqrDistance;
946 }
947 
948 void QgsCircularString::insertVertexBetween( int after, int before, int pointOnCircle )
949 {
950  double xAfter = mX.at( after );
951  double yAfter = mY.at( after );
952  double xBefore = mX.at( before );
953  double yBefore = mY.at( before );
954  double xOnCircle = mX.at( pointOnCircle );
955  double yOnCircle = mY.at( pointOnCircle );
956 
957  double radius, centerX, centerY;
958  QgsGeometryUtils::circleCenterRadius( QgsPoint( xAfter, yAfter ), QgsPoint( xBefore, yBefore ), QgsPoint( xOnCircle, yOnCircle ), radius, centerX, centerY );
959 
960  double x = ( xAfter + xBefore ) / 2.0;
961  double y = ( yAfter + yBefore ) / 2.0;
962 
963  QgsPoint newVertex = QgsGeometryUtils::pointOnLineWithDistance( QgsPoint( centerX, centerY ), QgsPoint( x, y ), radius );
964  mX.insert( before, newVertex.x() );
965  mY.insert( before, newVertex.y() );
966 
967  if ( is3D() )
968  {
969  mZ.insert( before, ( mZ[after] + mZ[before] ) / 2.0 );
970  }
971  if ( isMeasure() )
972  {
973  mM.insert( before, ( mM[after] + mM[before] ) / 2.0 );
974  }
975  clearCache();
976 }
977 
979 {
980  if ( numPoints() < 3 )
981  {
982  //undefined
983  return 0.0;
984  }
985 
986  int before = vId.vertex - 1;
987  int vertex = vId.vertex;
988  int after = vId.vertex + 1;
989 
990  if ( vId.vertex % 2 != 0 ) // a curve vertex
991  {
992  if ( vId.vertex >= 1 && vId.vertex < numPoints() - 1 )
993  {
994  return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex], mY[vertex] ), QgsPoint( mX[before], mY[before] ),
995  QgsPoint( mX[vertex], mY[vertex] ), QgsPoint( mX[after], mY[after] ) );
996  }
997  }
998  else //a point vertex
999  {
1000  if ( vId.vertex == 0 )
1001  {
1002  return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[0], mY[0] ), QgsPoint( mX[0], mY[0] ),
1003  QgsPoint( mX[1], mY[1] ), QgsPoint( mX[2], mY[2] ) );
1004  }
1005  if ( vId.vertex >= numPoints() - 1 )
1006  {
1007  int a = numPoints() - 3;
1008  int b = numPoints() - 2;
1009  int c = numPoints() - 1;
1010  return QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[c], mY[c] ), QgsPoint( mX[a], mY[a] ),
1011  QgsPoint( mX[b], mY[b] ), QgsPoint( mX[c], mY[c] ) );
1012  }
1013  else
1014  {
1015  if ( vId.vertex + 2 > numPoints() - 1 )
1016  {
1017  return 0.0;
1018  }
1019 
1020  int vertex1 = vId.vertex - 2;
1021  int vertex2 = vId.vertex - 1;
1022  int vertex3 = vId.vertex;
1023  double angle1 = QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex3], mY[vertex3] ),
1024  QgsPoint( mX[vertex1], mY[vertex1] ), QgsPoint( mX[vertex2], mY[vertex2] ), QgsPoint( mX[vertex3], mY[vertex3] ) );
1025  int vertex4 = vId.vertex + 1;
1026  int vertex5 = vId.vertex + 2;
1027  double angle2 = QgsGeometryUtils::circleTangentDirection( QgsPoint( mX[vertex3], mY[vertex3] ),
1028  QgsPoint( mX[vertex3], mY[vertex3] ), QgsPoint( mX[vertex4], mY[vertex4] ), QgsPoint( mX[vertex5], mY[vertex5] ) );
1029  return QgsGeometryUtils::averageAngle( angle1, angle2 );
1030  }
1031  }
1032  return 0.0;
1033 }
1034 
1036 {
1037  if ( startVertex.vertex < 0 || startVertex.vertex >= mX.count() - 2 )
1038  return 0.0;
1039 
1040  if ( startVertex.vertex % 2 == 1 )
1041  return 0.0; // curve point?
1042 
1043  double x1 = mX.at( startVertex.vertex );
1044  double y1 = mY.at( startVertex.vertex );
1045  double x2 = mX.at( startVertex.vertex + 1 );
1046  double y2 = mY.at( startVertex.vertex + 1 );
1047  double x3 = mX.at( startVertex.vertex + 2 );
1048  double y3 = mY.at( startVertex.vertex + 2 );
1049  return QgsGeometryUtils::circleLength( x1, y1, x2, y2, x3, y3 );
1050 }
1051 
1053 {
1054  QgsCircularString *copy = clone();
1055  std::reverse( copy->mX.begin(), copy->mX.end() );
1056  std::reverse( copy->mY.begin(), copy->mY.end() );
1057  if ( is3D() )
1058  {
1059  std::reverse( copy->mZ.begin(), copy->mZ.end() );
1060  }
1061  if ( isMeasure() )
1062  {
1063  std::reverse( copy->mM.begin(), copy->mM.end() );
1064  }
1065  return copy;
1066 }
1067 
1068 bool QgsCircularString::addZValue( double zValue )
1069 {
1070  if ( QgsWkbTypes::hasZ( mWkbType ) )
1071  return false;
1072 
1073  clearCache();
1075 
1076  int nPoints = numPoints();
1077  mZ.clear();
1078  mZ.reserve( nPoints );
1079  for ( int i = 0; i < nPoints; ++i )
1080  {
1081  mZ << zValue;
1082  }
1083  return true;
1084 }
1085 
1086 bool QgsCircularString::addMValue( double mValue )
1087 {
1088  if ( QgsWkbTypes::hasM( mWkbType ) )
1089  return false;
1090 
1091  clearCache();
1093 
1094  int nPoints = numPoints();
1095  mM.clear();
1096  mM.reserve( nPoints );
1097  for ( int i = 0; i < nPoints; ++i )
1098  {
1099  mM << mValue;
1100  }
1101  return true;
1102 }
1103 
1105 {
1106  if ( !QgsWkbTypes::hasZ( mWkbType ) )
1107  return false;
1108 
1109  clearCache();
1110 
1112  mZ.clear();
1113  return true;
1114 }
1115 
1117 {
1118  if ( !QgsWkbTypes::hasM( mWkbType ) )
1119  return false;
1120 
1121  clearCache();
1122 
1124  mM.clear();
1125  return true;
1126 }
bool isMeasure() const
Returns true if the geometry contains m values.
bool fromWkt(const QString &wkt) override
Sets the geometry from a WKT string.
void transformCoords(int numPoint, double *x, double *y, double *z, TransformDirection direction=ForwardTransform) const
Transform an array of coordinates to the destination CRS.
A rectangle specified with double values.
Definition: qgsrectangle.h:39
double y
Definition: qgspoint.h:42
void setPoints(const QgsPointSequence &points)
Resets the line string to match the specified list of points.
int numPoints() const override
Returns the number of points in the curve.
static bool circleAngleBetween(double angle, double angle1, double angle2, bool clockwise)
Returns true if, in a circle, angle is between angle1 and angle2.
static double ccwAngle(double dy, double dx)
Returns the counter clockwise angle between a line with components dx, dy and the line with dx > 0 an...
QDomElement asGml3(QDomDocument &doc, int precision=17, const QString &ns="gml") const override
Returns a GML3 representation of the geometry.
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 (...
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
static double averageAngle(double x1, double y1, double x2, double y2, double x3, double y3)
Angle between two linear segments.
bool pointAt(int node, QgsPoint &point, QgsVertexId::VertexType &type) const override
Returns the point and vertex id of a point within the curve.
QString asJson(int precision=17) const override
Returns a GeoJSON representation of the geometry.
TransformDirection
Enum used to indicate the direction (forward or inverse) of the transform.
QgsRectangle calculateBoundingBox() const override
Default calculator for the minimal bounding box for the geometry.
bool hasCurvedSegments() const override
Returns true if the geometry contains curved segments.
bool deleteVertex(QgsVertexId position) override
Deletes a vertex within the geometry.
void clearCache() const override
Clears any cached parameters associated with the geometry, e.g., bounding boxes.
Definition: qgscurve.cpp:203
static double circleLength(double x1, double y1, double x2, double y2, double x3, double y3)
Length of a circular string segment defined by pt1, pt2, pt3.
SegmentationToleranceType
Segmentation tolerance as maximum angle or maximum difference between approximation and circle...
double closestSegment(const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf=nullptr, double epsilon=4 *DBL_EPSILON) const override
Searches for the closest segment of the geometry to a given point.
double segmentLength(QgsVertexId startVertex) const override
Returns the length of the segment of the geometry which begins at startVertex.
static endian_t endian()
Returns whether this machine uses big or little endian.
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
static bool hasZ(Type type)
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:768
QgsPoint pointN(int i) const
Returns the point at index i within the circular string.
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:251
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
static Type dropM(Type type)
Drops the m dimension (if present) for a WKB type and returns the new type.
Definition: qgswkbtypes.h:938
QgsWkbTypes::Type mWkbType
void draw(QPainter &p) const override
Draws the geometry using the specified QPainter.
void points(QgsPointSequence &pts) const override
Returns a list of points within the curve.
void clear() override
Clears the geometry, ie reset it to a null geometry.
bool moveVertex(QgsVertexId position, const QgsPoint &newPos) override
Moves a vertex within the geometry.
QString wktTypeStr() const
Returns the WKT type string of the geometry.
static bool circleClockwise(double angle1, double angle2, double angle3)
Returns true if circle is ordered clockwise.
bool insertVertex(QgsVertexId position, const QgsPoint &vertex) override
Inserts a vertex into the geometry.
bool equals(const QgsCurve &other) const override
Checks whether this curve exactly equals another curve.
static QString pointsToWKT(const QgsPointSequence &points, int precision, bool is3D, bool isMeasure)
Returns a WKT coordinate list.
QgsCircularString * createEmptyWithSameType() const override
Creates a new geometry with the same class and same WKB type as the original and transfers ownership...
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:67
static Type addM(Type type)
Adds the m dimension to a WKB type and returns the new type.
Definition: qgswkbtypes.h:889
double vertexAngle(QgsVertexId vertex) const override
Returns approximate angle at a vertex.
Utility class for identifying a unique vertex within a geometry.
QString geometryType() const override
Returns a unique string representing the geometry type.
double length() const override
Returns the length of the geometry.
static QDomElement pointsToGML3(const QgsPointSequence &points, QDomDocument &doc, int precision, const QString &ns, bool is3D)
Returns a gml::posList DOM element.
void setZMTypeFromSubGeometry(const QgsAbstractGeometry *subggeom, QgsWkbTypes::Type baseGeomType)
Updates the geometry type based on whether sub geometries contain z or m values.
static Type addZ(Type type)
Adds the z dimension to a WKB type and returns the new type.
Definition: qgswkbtypes.h:864
static double circleTangentDirection(const QgsPoint &tangentPoint, const QgsPoint &cp1, const QgsPoint &cp2, const QgsPoint &cp3)
Calculates the direction angle of a circle tangent (clockwise from north in radians) ...
QgsCircularString * reversed() const override
Returns a reversed copy of the curve, where the direction of the curve has been flipped.
static bool angleOnCircle(double angle, double angle1, double angle2, double angle3)
Returns true if an angle is between angle1 and angle3 on a circle described by angle1, angle2 and angle3.
Abstract base class for curved geometry type.
Definition: qgscurve.h:35
int dimension() const override
Returns the inherent dimension of the geometry.
bool removeDuplicateNodes(double epsilon=4 *DBL_EPSILON, bool useZValues=false) override
Removes duplicate nodes from the geometry, wherever removing the nodes does not result in a degenerat...
void sumUpArea(double &sum) const override
Sums up the area of the curve by iterating over the vertices (shoelace formula).
void drawAsPolygon(QPainter &p) const override
Draws the curve as a polygon on the specified QPainter.
QgsWkbTypes::Type wkbType() const
Returns the WKB type of the geometry.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:37
QgsPoint endPoint() const override
Returns the end point of the curve.
void setX(double x)
Sets the point&#39;s x-coordinate.
Definition: qgspoint.h:192
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:224
void setY(double y)
Sets the point&#39;s y-coordinate.
Definition: qgspoint.h:203
QVector< QgsPoint > QgsPointSequence
void combineExtentWith(const QgsRectangle &rect)
Expand the rectangle so that covers both the original rectangle and the given rectangle.
static QgsPoint pointOnLineWithDistance(const QgsPoint &startPoint, const QgsPoint &directionPoint, double distance)
Returns a point a specified distance toward a second point.
bool addMValue(double mValue=0) override
Adds a measure to the geometry, initialized to a preset value.
static double sqrDistance2D(const QgsPoint &pt1, const QgsPoint &pt2)
Returns the squared 2D distance between two points.
static Type dropZ(Type type)
Drops the z dimension (if present) for a WKB type and returns the new type.
Definition: qgswkbtypes.h:920
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
static double sweepAngle(double centerX, double centerY, double x1, double y1, double x2, double y2, double x3, double y3)
Calculates angle of a circular string part defined by pt1, pt2, pt3.
static QgsPointSequence pointsFromWKT(const QString &wktCoordinateList, bool is3D, bool isMeasure)
Returns a list of points contained in a WKT string.
QgsCircularString * clone() const override
Clones the geometry by performing a deep copy.
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:41
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...
static int leftOfLine(double x, double y, double x1, double y1, double x2, double y2)
Returns a value < 0 if the point (x, y) is left of the line from (x1, y1) -> ( x2, y2).
void setPoints(const QgsPointSequence &points)
Sets the circular string&#39;s points.
QByteArray asWkb() const override
Returns a WKB representation of the geometry.
static void circleCenterRadius(const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3, double &radius, double &centerX, double &centerY)
Returns radius and center of the circle through pt1, pt2, pt3.
Class for doing transforms between two map coordinate systems.
static void pointsToWKB(QgsWkbPtr &wkb, const QgsPointSequence &points, bool is3D, bool isMeasure)
Returns a LinearRing { uint32 numPoints; Point points[numPoints]; }.
double z
Definition: qgspoint.h:43
static bool hasM(Type type)
Tests whether a WKB type contains m values.
Definition: qgswkbtypes.h:818
QString asWkt(int precision=17) const override
Returns a WKT representation of the geometry.
Circular string geometry type.
void transform(const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d=QgsCoordinateTransform::ForwardTransform, bool transformZ=false) override
Transforms the geometry using a coordinate transform.
bool fromWkb(QgsConstWkbPtr &wkb) override
Sets the geometry from a WKB string.
void addToPainterPath(QPainterPath &path) const override
Adds a curve to a painter path.
static Type flatType(Type type)
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:427
QgsWkbTypes::Type readHeader() const
readHeader
Definition: qgswkbptr.cpp:53
QDomElement asGml2(QDomDocument &doc, int precision=17, const QString &ns="gml") const override
Returns a GML2 representation of the geometry.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
double ANALYSIS_EXPORT leftOf(const QgsPoint &thepoint, const QgsPoint *p1, const QgsPoint *p2)
Returns whether &#39;thepoint&#39; is left or right of the line from &#39;p1&#39; to &#39;p2&#39;. Negativ values mean left a...
Definition: MathUtils.cpp:292
bool dropZValue() override
Drops any z-dimensions which exist in the geometry.
bool isEmpty() const override
Returns true if the geometry is empty.
QgsPoint startPoint() const override
Returns the starting point of the curve.
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 m
Definition: qgspoint.h:44
bool dropMValue() override
Drops any measure values which exist in the geometry.
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.
double x
Definition: qgspoint.h:41