1 /***************************************************************************
2                          qgscircle.cpp
3                          --------------
4     begin                : March 2017
5     copyright            : (C) 2017 by Loîc Bartoletti
6     email                : lbartoletti at tuxfamily dot org
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 "qgscircle.h"
19 #include "qgslinestring.h"
20 #include "qgsgeometryutils.h"
21 #include "qgstriangle.h"
22 
23 #include <memory>
24 #include <utility>
25 
QgsCircle()26 QgsCircle::QgsCircle() :
27   QgsEllipse( QgsPoint(), 0.0, 0.0, 0.0 )
28 {
29 
30 }
31 
QgsCircle(const QgsPoint & center,double radius,double azimuth)32 QgsCircle::QgsCircle( const QgsPoint &center, double radius, double azimuth ) :
33   QgsEllipse( center, radius, radius, azimuth )
34 {
35 
36 }
37 
from2Points(const QgsPoint & pt1,const QgsPoint & pt2)38 QgsCircle QgsCircle::from2Points( const QgsPoint &pt1, const QgsPoint &pt2 )
39 {
40   QgsPoint center = QgsGeometryUtils::midpoint( pt1, pt2 );
41   const double azimuth = QgsGeometryUtils::lineAngle( pt1.x(), pt1.y(), pt2.x(), pt2.y() ) * 180.0 / M_PI;
42   const double radius = pt1.distance( pt2 ) / 2.0;
43 
44   QgsGeometryUtils::transferFirstZOrMValueToPoint( QgsPointSequence() << pt1 << pt2, center );
45 
46   return QgsCircle( center, radius, azimuth );
47 }
48 
isPerpendicular(const QgsPoint & pt1,const QgsPoint & pt2,const QgsPoint & pt3,double epsilon)49 static bool isPerpendicular( const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3, double epsilon )
50 {
51   // check the given point are perpendicular to x or y axis
52 
53   const double yDelta_a = pt2.y() - pt1.y();
54   const double xDelta_a = pt2.x() - pt1.x();
55   const double yDelta_b = pt3.y() - pt2.y();
56   const double xDelta_b = pt3.x() - pt2.x();
57 
58   if ( ( std::fabs( xDelta_a ) <= epsilon ) && ( std::fabs( yDelta_b ) <= epsilon ) )
59   {
60     return false;
61   }
62 
63   if ( std::fabs( yDelta_a ) <= epsilon )
64   {
65     return true;
66   }
67   else if ( std::fabs( yDelta_b ) <= epsilon )
68   {
69     return true;
70   }
71   else if ( std::fabs( xDelta_a ) <= epsilon )
72   {
73     return true;
74   }
75   else if ( std::fabs( xDelta_b ) <= epsilon )
76   {
77     return true;
78   }
79 
80   return false;
81 
82 }
83 
from3Points(const QgsPoint & pt1,const QgsPoint & pt2,const QgsPoint & pt3,double epsilon)84 QgsCircle QgsCircle::from3Points( const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3, double epsilon )
85 {
86   QgsPoint p1, p2, p3;
87 
88   if ( !isPerpendicular( pt1, pt2, pt3, epsilon ) )
89   {
90     p1 = pt1;
91     p2 = pt2;
92     p3 = pt3;
93   }
94   else if ( !isPerpendicular( pt1, pt3, pt2, epsilon ) )
95   {
96     p1 = pt1;
97     p2 = pt3;
98     p3 = pt2;
99   }
100   else if ( !isPerpendicular( pt2, pt1, pt3, epsilon ) )
101   {
102     p1 = pt2;
103     p2 = pt1;
104     p3 = pt3;
105   }
106   else if ( !isPerpendicular( pt2, pt3, pt1, epsilon ) )
107   {
108     p1 = pt2;
109     p2 = pt3;
110     p3 = pt1;
111   }
112   else if ( !isPerpendicular( pt3, pt2, pt1, epsilon ) )
113   {
114     p1 = pt3;
115     p2 = pt2;
116     p3 = pt1;
117   }
118   else if ( !isPerpendicular( pt3, pt1, pt2, epsilon ) )
119   {
120     p1 = pt3;
121     p2 = pt1;
122     p3 = pt2;
123   }
124   else
125   {
126     return QgsCircle();
127   }
128   QgsPoint center = QgsPoint();
129   double radius = -0.0;
130   // Paul Bourke's algorithm
131   const double yDelta_a = p2.y() - p1.y();
132   const double xDelta_a = p2.x() - p1.x();
133   const double yDelta_b = p3.y() - p2.y();
134   const double xDelta_b = p3.x() - p2.x();
135 
136   if ( qgsDoubleNear( xDelta_a, 0.0, epsilon ) || qgsDoubleNear( xDelta_b, 0.0, epsilon ) )
137   {
138     return QgsCircle();
139   }
140 
141   const double aSlope = yDelta_a / xDelta_a;
142   const double bSlope = yDelta_b / xDelta_b;
143 
144   // set z and m coordinate for center
145   QgsGeometryUtils::transferFirstZOrMValueToPoint( QgsPointSequence() << p1 << p2 << p3, center );
146 
147   if ( ( std::fabs( xDelta_a ) <= epsilon ) && ( std::fabs( yDelta_b ) <= epsilon ) )
148   {
149     center.setX( 0.5 * ( p2.x() + p3.x() ) );
150     center.setY( 0.5 * ( p1.y() + p2.y() ) );
151     radius = center.distance( pt1 );
152 
153     return QgsCircle( center, radius );
154   }
155 
156   if ( std::fabs( aSlope - bSlope ) <= epsilon )
157   {
158     return QgsCircle();
159   }
160 
161   center.setX(
162     ( aSlope * bSlope * ( p1.y() - p3.y() ) +
163       bSlope * ( p1.x() + p2.x() ) -
164       aSlope * ( p2.x() + p3.x() ) ) /
165     ( 2.0 * ( bSlope - aSlope ) )
166   );
167   center.setY(
168     -1.0 * ( center.x() - ( p1.x() + p2.x() ) / 2.0 ) /
169     aSlope + ( p1.y() + p2.y() ) / 2.0
170   );
171 
172   radius = center.distance( p1 );
173 
174   return QgsCircle( center, radius );
175 }
176 
fromCenterDiameter(const QgsPoint & center,double diameter,double azimuth)177 QgsCircle QgsCircle::fromCenterDiameter( const QgsPoint &center, double diameter, double azimuth )
178 {
179   return QgsCircle( center, diameter / 2.0, azimuth );
180 }
181 
fromCenterPoint(const QgsPoint & center,const QgsPoint & pt1)182 QgsCircle QgsCircle::fromCenterPoint( const QgsPoint &center, const QgsPoint &pt1 )
183 {
184   const double azimuth = QgsGeometryUtils::lineAngle( center.x(), center.y(), pt1.x(), pt1.y() ) * 180.0 / M_PI;
185 
186   QgsPoint centerPt( center );
187   QgsGeometryUtils::transferFirstZOrMValueToPoint( QgsPointSequence() << center << pt1, centerPt );
188 
189   return QgsCircle( centerPt, centerPt.distance( pt1 ), azimuth );
190 }
191 
from2ParallelsLine(const QgsPoint & pt1_par1,const QgsPoint & pt2_par1,const QgsPoint & pt1_par2,const QgsPoint & pt2_par2,const QgsPoint & pt1_line1,const QgsPoint & pt2_line1,const QgsPoint & pos,double epsilon)192 static QVector<QgsCircle> from2ParallelsLine( const QgsPoint &pt1_par1, const QgsPoint &pt2_par1, const QgsPoint &pt1_par2, const QgsPoint &pt2_par2, const QgsPoint &pt1_line1, const QgsPoint &pt2_line1, const QgsPoint &pos, double epsilon )
193 {
194   const double radius = QgsGeometryUtils::perpendicularSegment( pt1_par2, pt1_par1, pt2_par1 ).length() / 2.0;
195 
196   bool isInter;
197   const QgsPoint ptInter;
198   QVector<QgsCircle> circles;
199 
200   QgsPoint ptInter_par1line1, ptInter_par2line1;
201   double angle1, angle2;
202   double x, y;
203   QgsGeometryUtils::angleBisector( pt1_par1.x(), pt1_par1.y(), pt2_par1.x(), pt2_par1.y(), pt1_line1.x(), pt1_line1.y(), pt2_line1.x(), pt2_line1.y(), x, y, angle1 );
204   ptInter_par1line1.setX( x );
205   ptInter_par1line1.setY( y );
206 
207   QgsGeometryUtils::angleBisector( pt1_par2.x(), pt1_par2.y(), pt2_par2.x(), pt2_par2.y(), pt1_line1.x(), pt1_line1.y(), pt2_line1.x(), pt2_line1.y(), x, y, angle2 );
208   ptInter_par2line1.setX( x );
209   ptInter_par2line1.setY( y );
210 
211   QgsPoint center;
212   QgsGeometryUtils::segmentIntersection( ptInter_par1line1, ptInter_par1line1.project( 1.0, angle1 ), ptInter_par2line1, ptInter_par2line1.project( 1.0, angle2 ), center, isInter, epsilon, true );
213   if ( isInter )
214   {
215     if ( !pos.isEmpty() )
216     {
217       if ( QgsGeometryUtils::leftOfLine( center, pt1_line1, pt2_line1 ) == QgsGeometryUtils::leftOfLine( pos, pt1_line1, pt2_line1 ) )
218       {
219         circles.append( QgsCircle( center, radius ) );
220       }
221     }
222     else
223     {
224       circles.append( QgsCircle( center, radius ) );
225     }
226   }
227 
228   QgsGeometryUtils::segmentIntersection( ptInter_par1line1, ptInter_par1line1.project( 1.0, angle1 ), ptInter_par2line1, ptInter_par2line1.project( 1.0, angle2 + 90.0 ), center, isInter, epsilon, true );
229   if ( isInter )
230   {
231     if ( !pos.isEmpty() )
232     {
233       if ( QgsGeometryUtils::leftOfLine( center, pt1_line1, pt2_line1 ) == QgsGeometryUtils::leftOfLine( pos, pt1_line1, pt2_line1 ) )
234       {
235         circles.append( QgsCircle( center, radius ) );
236       }
237     }
238     else
239     {
240       circles.append( QgsCircle( center, radius ) );
241     }
242   }
243 
244   QgsGeometryUtils::segmentIntersection( ptInter_par1line1, ptInter_par1line1.project( 1.0, angle1 + 90.0 ), ptInter_par2line1, ptInter_par2line1.project( 1.0, angle2 ), center, isInter, epsilon, true );
245   if ( isInter && !circles.contains( QgsCircle( center, radius ) ) )
246   {
247     if ( !pos.isEmpty() )
248     {
249       if ( QgsGeometryUtils::leftOfLine( center, pt1_line1, pt2_line1 ) == QgsGeometryUtils::leftOfLine( pos, pt1_line1, pt2_line1 ) )
250       {
251         circles.append( QgsCircle( center, radius ) );
252       }
253     }
254     else
255     {
256       circles.append( QgsCircle( center, radius ) );
257     }
258   }
259   QgsGeometryUtils::segmentIntersection( ptInter_par1line1, ptInter_par1line1.project( 1.0, angle1 + 90.0 ), ptInter_par2line1, ptInter_par2line1.project( 1.0, angle2 + 90.0 ), center, isInter, epsilon, true );
260   if ( isInter && !circles.contains( QgsCircle( center, radius ) ) )
261   {
262     if ( !pos.isEmpty() )
263     {
264       if ( QgsGeometryUtils::leftOfLine( center, pt1_line1, pt2_line1 ) == QgsGeometryUtils::leftOfLine( pos, pt1_line1, pt2_line1 ) )
265       {
266         circles.append( QgsCircle( center, radius ) );
267       }
268     }
269     else
270     {
271       circles.append( QgsCircle( center, radius ) );
272     }
273   }
274 
275   return circles;
276 }
277 
from3TangentsMulti(const QgsPoint & pt1_tg1,const QgsPoint & pt2_tg1,const QgsPoint & pt1_tg2,const QgsPoint & pt2_tg2,const QgsPoint & pt1_tg3,const QgsPoint & pt2_tg3,double epsilon,const QgsPoint & pos)278 QVector<QgsCircle> QgsCircle::from3TangentsMulti( const QgsPoint &pt1_tg1, const QgsPoint &pt2_tg1, const QgsPoint &pt1_tg2, const QgsPoint &pt2_tg2, const QgsPoint &pt1_tg3, const QgsPoint &pt2_tg3, double epsilon, const QgsPoint &pos )
279 {
280   QgsPoint p1, p2, p3;
281   bool isIntersect_tg1tg2 = false;
282   bool isIntersect_tg1tg3 = false;
283   bool isIntersect_tg2tg3 = false;
284   QgsGeometryUtils::segmentIntersection( pt1_tg1, pt2_tg1, pt1_tg2, pt2_tg2, p1, isIntersect_tg1tg2, epsilon );
285   QgsGeometryUtils::segmentIntersection( pt1_tg1, pt2_tg1, pt1_tg3, pt2_tg3, p2, isIntersect_tg1tg3, epsilon );
286   QgsGeometryUtils::segmentIntersection( pt1_tg2, pt2_tg2, pt1_tg3, pt2_tg3, p3, isIntersect_tg2tg3, epsilon );
287 
288   QVector<QgsCircle> circles;
289   if ( !isIntersect_tg1tg2 && !isIntersect_tg2tg3 ) // three lines are parallels
290     return circles;
291 
292   if ( !isIntersect_tg1tg2 )
293     return from2ParallelsLine( pt1_tg1, pt2_tg1, pt1_tg2, pt2_tg2, pt1_tg3, pt2_tg3, pos, epsilon );
294   else if ( !isIntersect_tg1tg3 )
295     return from2ParallelsLine( pt1_tg1, pt2_tg1, pt1_tg3, pt2_tg3, pt1_tg2, pt2_tg2, pos, epsilon );
296   else if ( !isIntersect_tg2tg3 )
297     return from2ParallelsLine( pt1_tg2, pt2_tg2, pt1_tg3, pt2_tg3, pt1_tg1, pt1_tg1, pos, epsilon );
298 
299   if ( p1.is3D() )
300   {
301     p1.convertTo( QgsWkbTypes::dropZ( p1.wkbType() ) );
302   }
303 
304   if ( p2.is3D() )
305   {
306     p2.convertTo( QgsWkbTypes::dropZ( p2.wkbType() ) );
307   }
308 
309   if ( p3.is3D() )
310   {
311     p3.convertTo( QgsWkbTypes::dropZ( p3.wkbType() ) );
312   }
313 
314   circles.append( QgsTriangle( p1, p2, p3 ).inscribedCircle() );
315   return circles;
316 }
317 
from3Tangents(const QgsPoint & pt1_tg1,const QgsPoint & pt2_tg1,const QgsPoint & pt1_tg2,const QgsPoint & pt2_tg2,const QgsPoint & pt1_tg3,const QgsPoint & pt2_tg3,double epsilon,const QgsPoint & pos)318 QgsCircle QgsCircle::from3Tangents( const QgsPoint &pt1_tg1, const QgsPoint &pt2_tg1, const QgsPoint &pt1_tg2, const QgsPoint &pt2_tg2, const QgsPoint &pt1_tg3, const QgsPoint &pt2_tg3, double epsilon, const QgsPoint &pos )
319 {
320   const QVector<QgsCircle> circles = from3TangentsMulti( pt1_tg1, pt2_tg1, pt1_tg2, pt2_tg2, pt1_tg3, pt2_tg3, epsilon, pos );
321   if ( circles.length() != 1 )
322     return QgsCircle();
323   return circles.at( 0 );
324 }
325 
minimalCircleFrom3Points(const QgsPoint & pt1,const QgsPoint & pt2,const QgsPoint & pt3,double epsilon)326 QgsCircle QgsCircle::minimalCircleFrom3Points( const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3, double epsilon )
327 {
328   const double l1 = pt2.distance( pt3 );
329   const double l2 = pt3.distance( pt1 );
330   const double l3 = pt1.distance( pt2 );
331 
332   if ( ( l1 * l1 ) - ( l2 * l2 + l3 * l3 ) >= epsilon )
333     return QgsCircle().from2Points( pt2, pt3 );
334   else if ( ( l2 * l2 ) - ( l1 * l1 + l3 * l3 ) >= epsilon )
335     return QgsCircle().from2Points( pt3, pt1 );
336   else if ( ( l3 * l3 ) - ( l1 * l1 + l2 * l2 ) >= epsilon )
337     return QgsCircle().from2Points( pt1, pt2 );
338   else
339     return QgsCircle().from3Points( pt1, pt2, pt3, epsilon );
340 }
341 
intersections(const QgsCircle & other,QgsPoint & intersection1,QgsPoint & intersection2,bool useZ) const342 int QgsCircle::intersections( const QgsCircle &other, QgsPoint &intersection1, QgsPoint &intersection2, bool useZ ) const
343 {
344   if ( useZ && mCenter.is3D() && other.center().is3D() && !qgsDoubleNear( mCenter.z(), other.center().z() ) )
345     return 0;
346 
347   QgsPointXY int1, int2;
348 
349   const int res = QgsGeometryUtils::circleCircleIntersections( QgsPointXY( mCenter ), radius(),
350                   QgsPointXY( other.center() ), other.radius(),
351                   int1, int2 );
352   if ( res == 0 )
353     return 0;
354 
355   intersection1 = QgsPoint( int1 );
356   intersection2 = QgsPoint( int2 );
357   if ( useZ && mCenter.is3D() )
358   {
359     intersection1.addZValue( mCenter.z() );
360     intersection2.addZValue( mCenter.z() );
361   }
362   return res;
363 }
364 
tangentToPoint(const QgsPointXY & p,QgsPointXY & pt1,QgsPointXY & pt2) const365 bool QgsCircle::tangentToPoint( const QgsPointXY &p, QgsPointXY &pt1, QgsPointXY &pt2 ) const
366 {
367   return QgsGeometryUtils::tangentPointAndCircle( QgsPointXY( mCenter ), radius(), p, pt1, pt2 );
368 }
369 
outerTangents(const QgsCircle & other,QgsPointXY & line1P1,QgsPointXY & line1P2,QgsPointXY & line2P1,QgsPointXY & line2P2) const370 int QgsCircle::outerTangents( const QgsCircle &other, QgsPointXY &line1P1, QgsPointXY &line1P2, QgsPointXY &line2P1, QgsPointXY &line2P2 ) const
371 {
372   return QgsGeometryUtils::circleCircleOuterTangents( QgsPointXY( mCenter ), radius(),
373          QgsPointXY( other.center() ), other.radius(), line1P1, line1P2, line2P1, line2P2 );
374 }
375 
innerTangents(const QgsCircle & other,QgsPointXY & line1P1,QgsPointXY & line1P2,QgsPointXY & line2P1,QgsPointXY & line2P2) const376 int QgsCircle::innerTangents( const QgsCircle &other, QgsPointXY &line1P1, QgsPointXY &line1P2, QgsPointXY &line2P1, QgsPointXY &line2P2 ) const
377 {
378   return QgsGeometryUtils::circleCircleInnerTangents( QgsPointXY( mCenter ), radius(),
379          QgsPointXY( other.center() ), other.radius(), line1P1, line1P2, line2P1, line2P2 );
380 }
381 
fromExtent(const QgsPoint & pt1,const QgsPoint & pt2)382 QgsCircle QgsCircle::fromExtent( const QgsPoint &pt1, const QgsPoint &pt2 )
383 {
384   const double delta_x = std::fabs( pt1.x() - pt2.x() );
385   const double delta_y = std::fabs( pt1.x() - pt2.y() );
386   if ( !qgsDoubleNear( delta_x, delta_y ) )
387   {
388     return QgsCircle();
389   }
390 
391   QgsPoint center = QgsGeometryUtils::midpoint( pt1, pt2 );
392   QgsGeometryUtils::transferFirstZOrMValueToPoint( QgsPointSequence() << pt1 << pt2, center );
393 
394   return QgsCircle( center, delta_x / 2.0, 0 );
395 }
396 
area() const397 double QgsCircle::area() const
398 {
399   return M_PI * mSemiMajorAxis * mSemiMajorAxis;
400 }
401 
perimeter() const402 double QgsCircle::perimeter() const
403 {
404   return 2.0 * M_PI * mSemiMajorAxis;
405 }
406 
setSemiMajorAxis(const double semiMajorAxis)407 void QgsCircle::setSemiMajorAxis( const double semiMajorAxis )
408 {
409   mSemiMajorAxis = std::fabs( semiMajorAxis );
410   mSemiMinorAxis = mSemiMajorAxis;
411 }
412 
setSemiMinorAxis(const double semiMinorAxis)413 void QgsCircle::setSemiMinorAxis( const double semiMinorAxis )
414 {
415   mSemiMajorAxis = std::fabs( semiMinorAxis );
416   mSemiMinorAxis = mSemiMajorAxis;
417 }
418 
northQuadrant() const419 QVector<QgsPoint> QgsCircle::northQuadrant() const
420 {
421   QVector<QgsPoint> quad;
422   quad.append( QgsPoint( mCenter.x(), mCenter.y() + mSemiMajorAxis, mCenter.z(), mCenter.m() ) );
423   quad.append( QgsPoint( mCenter.x() + mSemiMajorAxis, mCenter.y(), mCenter.z(), mCenter.m() ) );
424   quad.append( QgsPoint( mCenter.x(), mCenter.y() - mSemiMajorAxis, mCenter.z(), mCenter.m() ) );
425   quad.append( QgsPoint( mCenter.x() - mSemiMajorAxis, mCenter.y(), mCenter.z(), mCenter.m() ) );
426 
427   return quad;
428 }
429 
toCircularString(bool oriented) const430 QgsCircularString *QgsCircle::toCircularString( bool oriented ) const
431 {
432   std::unique_ptr<QgsCircularString> circString( new QgsCircularString() );
433   QgsPointSequence points;
434   QVector<QgsPoint> quad;
435   if ( oriented )
436   {
437     quad = quadrant();
438   }
439   else
440   {
441     quad = northQuadrant();
442   }
443   quad.append( quad.at( 0 ) );
444   for ( QVector<QgsPoint>::const_iterator it = quad.constBegin(); it != quad.constEnd(); ++it )
445   {
446     points.append( *it );
447   }
448   circString->setPoints( points );
449 
450   return circString.release();
451 }
452 
contains(const QgsPoint & point,double epsilon) const453 bool QgsCircle::contains( const QgsPoint &point, double epsilon ) const
454 {
455   return ( mCenter.distance( point ) <= mSemiMajorAxis + epsilon );
456 }
457 
boundingBox() const458 QgsRectangle QgsCircle::boundingBox() const
459 {
460   return QgsRectangle( mCenter.x() - mSemiMajorAxis, mCenter.y() - mSemiMajorAxis, mCenter.x() + mSemiMajorAxis, mCenter.y() + mSemiMajorAxis );
461 }
462 
toString(int pointPrecision,int radiusPrecision,int azimuthPrecision) const463 QString QgsCircle::toString( int pointPrecision, int radiusPrecision, int azimuthPrecision ) const
464 {
465   QString rep;
466   if ( isEmpty() )
467     rep = QStringLiteral( "Empty" );
468   else
469     rep = QStringLiteral( "Circle (Center: %1, Radius: %2, Azimuth: %3)" )
470           .arg( mCenter.asWkt( pointPrecision ), 0, 's' )
471           .arg( qgsDoubleToString( mSemiMajorAxis, radiusPrecision ), 0, 'f' )
472           .arg( qgsDoubleToString( mAzimuth, azimuthPrecision ), 0, 'f' );
473 
474   return rep;
475 
476 }
477 
asGml2(QDomDocument & doc,int precision,const QString & ns,const QgsAbstractGeometry::AxisOrder axisOrder) const478 QDomElement QgsCircle::asGml2( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
479 {
480   // Gml2 does not support curve. It will be converted to a linestring via CircularString
481   std::unique_ptr< QgsCircularString > circularString( toCircularString() );
482   const QDomElement gml = circularString->asGml2( doc, precision, ns, axisOrder );
483   return gml;
484 }
485 
asGml3(QDomDocument & doc,int precision,const QString & ns,const QgsAbstractGeometry::AxisOrder axisOrder) const486 QDomElement QgsCircle::asGml3( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
487 {
488   QgsPointSequence pts;
489   pts << northQuadrant().at( 0 ) << northQuadrant().at( 1 ) << northQuadrant().at( 2 );
490 
491   QDomElement elemCircle = doc.createElementNS( ns, QStringLiteral( "Circle" ) );
492 
493   if ( isEmpty() )
494     return elemCircle;
495 
496   elemCircle.appendChild( QgsGeometryUtils::pointsToGML3( pts, doc, precision, ns, mCenter.is3D(), axisOrder ) );
497   return elemCircle;
498 }
499