1 /***************************************************************************
2      testqgsellipse.cpp
3      --------------------------------------
4     Date                 : August 2021
5     Copyright            : (C) 2021 by Loïc Bartoletti
6     Email                : loic dot bartoletti at oslandia dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 #include "qgstest.h"
16 #include <QObject>
17 #include <QString>
18 
19 #include "qgsellipse.h"
20 #include "qgsgeometryutils.h"
21 #include "qgslinestring.h"
22 #include "qgspoint.h"
23 
24 #include "testgeometryutils.h"
25 
26 class TestQgsEllipse: public QObject
27 {
28     Q_OBJECT
29   private slots:
30     void ellipse();
31 };
32 
ellipse()33 void TestQgsEllipse::ellipse()
34 {
35   //test constructors
36   QgsEllipse elp1;
37   QVERIFY( elp1.center().isEmpty() );
38   QCOMPARE( elp1.semiMajorAxis(), 0.0 );
39   QCOMPARE( elp1.semiMinorAxis(), 0.0 );
40   QCOMPARE( elp1.azimuth(), 90.0 );
41   QVERIFY( elp1.isEmpty() );
42 
43   QgsEllipse elp2( QgsPoint( 5, 10 ), 3, 2 );
44   QVERIFY( elp2.center() == QgsPoint( 5, 10 ) );
45   QCOMPARE( elp2.semiMajorAxis(), 3.0 );
46   QCOMPARE( elp2.semiMinorAxis(), 2.0 );
47   QCOMPARE( elp2.azimuth(), 90.0 );
48   QVERIFY( !elp2.isEmpty() );
49 
50   QgsEllipse elp3( QgsPoint( 5, 10 ), 3, 2, 45 );
51   QVERIFY( elp3.center() == QgsPoint( 5, 10 ) );
52   QCOMPARE( elp3.semiMajorAxis(), 3.0 );
53   QCOMPARE( elp3.semiMinorAxis(), 2.0 );
54   QCOMPARE( elp3.azimuth(), 45.0 );
55   QVERIFY( !elp3.isEmpty() );
56 
57   QgsEllipse elp4( QgsPoint( 5, 10 ), 2, 3, 45 );
58   QVERIFY( elp4.center() == QgsPoint( 5, 10 ) );
59   QCOMPARE( elp4.semiMajorAxis(), 3.0 );
60   QCOMPARE( elp4.semiMinorAxis(), 2.0 );
61   QCOMPARE( elp4.azimuth(), 135.0 );
62   QVERIFY( !elp4.isEmpty() );
63 
64   //test toString
65   QCOMPARE( elp1.toString(), QString( "Empty" ) );
66   QCOMPARE( elp2.toString(), QString( "Ellipse (Center: Point (5 10), Semi-Major Axis: 3, Semi-Minor Axis: 2, Azimuth: 90)" ) );
67   QCOMPARE( elp3.toString(), QString( "Ellipse (Center: Point (5 10), Semi-Major Axis: 3, Semi-Minor Axis: 2, Azimuth: 45)" ) );
68   QCOMPARE( elp4.toString(), QString( "Ellipse (Center: Point (5 10), Semi-Major Axis: 3, Semi-Minor Axis: 2, Azimuth: 135)" ) );
69 
70   //test equality operator
71   QCOMPARE( QgsEllipse().isEmpty(), QgsEllipse( QgsPoint(), 0, 0, 90 ).isEmpty() );
72   QVERIFY( !( QgsEllipse() == QgsEllipse( QgsPoint( 0, 0 ), 0, 0, 0.0005 ) ) );
73   QVERIFY( elp2 == QgsEllipse( QgsPoint( 5, 10 ), 2, 3, 0 ) );
74   QVERIFY( elp2 != elp3 );
75   QVERIFY( elp3 != elp4 );
76   QVERIFY( elp4 == QgsEllipse( QgsPoint( 5, 10 ), 3, 2, 90 + 45 ) );
77 
78   //test setter and getter
79   elp1.setAzimuth( 45 );
80   QCOMPARE( elp1.azimuth(), 45.0 );
81 
82   elp1.setSemiMajorAxis( 50 );
83   QCOMPARE( elp1.semiMajorAxis(), 50.0 );
84 
85   // axis_b > axis_a
86   elp1.setSemiMinorAxis( 70 );
87   QCOMPARE( elp1.semiMajorAxis(), 70.0 );
88   QCOMPARE( elp1.semiMinorAxis(), 50.0 );
89   // axis_b < axis_a
90   elp1.setSemiMinorAxis( 3 );
91   QCOMPARE( elp1.semiMinorAxis(), 3.0 );
92   QCOMPARE( elp1.semiMajorAxis(), 70.0 );
93 
94   elp1.setSemiMajorAxis( 2 );
95   QCOMPARE( elp1.semiMinorAxis(), 2.0 );
96   QCOMPARE( elp1.semiMajorAxis(), 3.0 );
97 
98   elp1.setCenter( QgsPoint( 5, 10 ) );
99   QVERIFY( elp1.center() == QgsPoint( 5, 10 ) );
100   QVERIFY( elp1.rcenter() == QgsPoint( 5, 10 ) );
101   elp1.rcenter() = QgsPoint( 25, 310 );
102   QVERIFY( elp1.center() == QgsPoint( 25, 310 ) );
103 
104   //test "alt" constructors
105   // fromExtent
106   QgsEllipse elp_alt = QgsEllipse( QgsPoint( 2.5, 5 ), 2.5, 5 );
107   QVERIFY( QgsEllipse().fromExtent( QgsPoint( 0, 0 ), QgsPoint( 5, 10 ) ) == elp_alt );
108   QVERIFY( QgsEllipse().fromExtent( QgsPoint( 5, 10 ), QgsPoint( 0, 0 ) ) == elp_alt );
109   QVERIFY( QgsEllipse().fromExtent( QgsPoint( 5, 0 ), QgsPoint( 0, 10 ) ) == elp_alt );
110   QVERIFY( QgsEllipse().fromExtent( QgsPoint( -5, 0 ), QgsPoint( 0, 10 ) ) != elp_alt );
111   // fromCenterPoint
112   QVERIFY( QgsEllipse().fromCenterPoint( QgsPoint( 2.5, 5 ), QgsPoint( 5, 10 ) ) == elp_alt );
113   QVERIFY( QgsEllipse().fromCenterPoint( QgsPoint( 2.5, 5 ), QgsPoint( -0, 0 ) ) == elp_alt );
114   QVERIFY( QgsEllipse().fromCenterPoint( QgsPoint( 2.5, 5 ), QgsPoint( 0, -10 ) ) != elp_alt );
115   // fromCenter2Points
116   QVERIFY( QgsEllipse().fromCenter2Points( QgsPoint( 2.5, 5 ), QgsPoint( 2.5, 0 ), QgsPoint( 7.5, 5 ) ) ==
117            QgsEllipse( QgsPoint( 2.5, 5 ), 5, 5, 180 ) );
118   QVERIFY( QgsEllipse().fromCenter2Points( QgsPoint( 2.5, 5 ), QgsPoint( 2.5, 7.5 ), QgsPoint( 7.5, 5 ) ) != elp_alt ); //same ellipse with different azimuth
119   QVERIFY( QgsEllipse().fromCenter2Points( QgsPoint( 2.5, 5 ), QgsPoint( 2.5, 2.5 ), QgsPoint( 7.5, 5 ) ) != elp_alt ); //same ellipse with different azimuth
120   QVERIFY( QgsEllipse().fromCenter2Points( QgsPoint( 2.5, 5 ), QgsPoint( 2.5, 0 ), QgsPoint( 5, 5 ) ) == elp_alt );
121   QVERIFY( QgsEllipse().fromCenter2Points( QgsPoint( 5, 10 ), QgsPoint( 5, 10 ).project( 3, 45 ), QgsPoint( 5, 10 ).project( 2, 90 + 45 ) ) ==
122            QgsEllipse( QgsPoint( 5, 10 ), 3, 2, 45 ) );
123 
124   // fromFoci
125   // horizontal
126   QgsEllipse elp_hor = QgsEllipse().fromFoci( QgsPoint( -4, 0 ), QgsPoint( 4, 0 ), QgsPoint( 0, 4 ) );
127   QVERIFY( QgsEllipse( QgsPoint( 0, 0 ), std::sqrt( 32.0 ), std::sqrt( 16.0 ), 90.0 ) == elp_hor );
128   QGSCOMPARENEARPOINT( QgsPoint( 4, 0 ), elp_hor.foci().at( 0 ), 1e-8 );
129   QGSCOMPARENEARPOINT( QgsPoint( -4, 0 ), elp_hor.foci().at( 1 ), 1e-8 );
130   elp_hor = QgsEllipse().fromFoci( QgsPoint( 4, 0 ), QgsPoint( -4, 0 ), QgsPoint( 0, 4 ) );
131   QVERIFY( QgsEllipse( QgsPoint( 0, 0 ), std::sqrt( 32.0 ), std::sqrt( 16.0 ), 270.0 ) == elp_hor );
132   QGSCOMPARENEARPOINT( QgsPoint( -4, 0 ), elp_hor.foci().at( 0 ), 1e-8 );
133   QGSCOMPARENEARPOINT( QgsPoint( 4, 0 ), elp_hor.foci().at( 1 ), 1e-8 );
134 
135   // vertical
136   QgsEllipse elp_ver = QgsEllipse().fromFoci( QgsPoint( 45, -15 ), QgsPoint( 45, 10 ), QgsPoint( 55, 0 ) );
137   QVERIFY( QgsEllipse( QgsPoint( 45, -2.5 ), 16.084946, 10.123017725, 0.0 ) == elp_ver );
138   elp_ver = QgsEllipse().fromFoci( QgsPoint( 45, 10 ), QgsPoint( 45, -15 ), QgsPoint( 55, 0 ) );
139   QVERIFY( QgsEllipse( QgsPoint( 45, -2.5 ), 16.084946, 10.123017725, 180.0 ) == elp_ver );
140   QGSCOMPARENEARPOINT( QgsPoint( 45, -15 ), elp_ver.foci().at( 0 ), 1e-8 );
141   QGSCOMPARENEARPOINT( QgsPoint( 45, 10 ), elp_ver.foci().at( 1 ), 1e-8 );
142   // oriented
143   // first quadrant
144   QgsEllipse elp_ori = QgsEllipse().fromFoci( QgsPoint( 10, 10 ), QgsPoint( 25, 20 ), QgsPoint( 15, 20 ) );
145   QVERIFY( QgsEllipse( QgsPoint( 17.5, 15.0 ), 10.5901699437, 5.55892970251, 90.0 - 33.690067526 ) == elp_ori );
146   QGSCOMPARENEARPOINT( QgsPoint( 25, 20 ), elp_ori.foci().at( 0 ), 1e-8 );
147   QGSCOMPARENEARPOINT( QgsPoint( 10, 10 ), elp_ori.foci().at( 1 ), 1e-8 );
148   // second quadrant
149   elp_ori = QgsEllipse().fromFoci( QgsPoint( 10, 10 ), QgsPoint( 5, 20 ), QgsPoint( 15, 20 ) );
150   QVERIFY( QgsEllipse( QgsPoint( 7.5, 15.0 ), 10.5901699437, 8.99453719974, 360 - 26.56505117 ) == elp_ori );
151   QGSCOMPARENEARPOINT( QgsPoint( 5, 20 ), elp_ori.foci().at( 0 ), 1e-8 );
152   QGSCOMPARENEARPOINT( QgsPoint( 10, 10 ), elp_ori.foci().at( 1 ), 1e-8 );
153   // third quadrant
154   elp_ori = QgsEllipse().fromFoci( QgsPoint( 10, 10 ), QgsPoint( 5, -5 ), QgsPoint( 15, 20 ) );
155   QVERIFY( QgsEllipse( QgsPoint( 7.5, 2.5 ), 19.0530819616, 17.3355107289893, 198.434948822922 ) == elp_ori );
156   QGSCOMPARENEARPOINT( QgsPoint( 10, 10 ), elp_ori.foci().at( 1 ), 1e-8 );
157   QGSCOMPARENEARPOINT( QgsPoint( 5, -5 ), elp_ori.foci().at( 0 ), 1e-8 );
158   // fourth quadrant
159   elp_ori = QgsEllipse().fromFoci( QgsPoint( 10, 10 ), QgsPoint( 25, -5 ), QgsPoint( 15, 20 ) );
160   QVERIFY( QgsEllipse( QgsPoint( 17.5, 2.5 ), 19.0530819616, 15.82782146, 135 ) == elp_ori );
161   QGSCOMPARENEARPOINT( QgsPoint( 25, -5 ), elp_ori.foci().at( 0 ), 1e-8 );
162   QGSCOMPARENEARPOINT( QgsPoint( 10, 10 ), elp_ori.foci().at( 1 ), 1e-8 );
163 
164   // test quadrant
165   QgsEllipse elpq( QgsPoint( 5, 10 ), 3, 2, 45 );
166   QgsPointSequence q = elpq.quadrant();
167   QGSCOMPARENEARPOINT( q.at( 0 ), QgsPoint( 7.1213, 12.1213 ), 0.001 );
168   QGSCOMPARENEARPOINT( q.at( 3 ), QgsPoint( 3.5858, 11.4142 ), 0.001 );
169   QGSCOMPARENEARPOINT( q.at( 2 ), QgsPoint( 2.8787, 7.8787 ), 0.001 );
170   QGSCOMPARENEARPOINT( q.at( 1 ), QgsPoint( 6.4142, 8.5858 ), 0.001 );
171 
172   elpq = QgsEllipse( QgsPoint( 0, 0 ), 5, 2, 90 );
173   q.clear();
174   q = elpq.quadrant();
175   QCOMPARE( q.at( 3 ), QgsPoint( 0, 2 ) );
176   QCOMPARE( q.at( 0 ), QgsPoint( 5, 0 ) );
177   QCOMPARE( q.at( 1 ), QgsPoint( 0, -2 ) );
178   QCOMPARE( q.at( 2 ), QgsPoint( -5, 0 ) );
179 
180   elpq = QgsEllipse( QgsPoint( QgsWkbTypes::PointZM, 0, 0, 123, 321 ), 5, 2, 0 );
181   q.clear();
182   q = elpq.quadrant();
183   QCOMPARE( q.at( 0 ), QgsPoint( QgsWkbTypes::PointZM, 0, 5, 123, 321 ) );
184   QCOMPARE( q.at( 3 ), QgsPoint( QgsWkbTypes::PointZM, -2, 0, 123, 321 ) );
185   QCOMPARE( q.at( 2 ), QgsPoint( QgsWkbTypes::PointZM, 0, -5, 123, 321 ) );
186   QCOMPARE( q.at( 1 ), QgsPoint( QgsWkbTypes::PointZM, 2, 0, 123, 321 ) );
187 
188   elpq = QgsEllipse( QgsPoint( 0, 0 ), 2.5, 2, 315 );
189   q.clear();
190   q = elpq.quadrant();
191   QGSCOMPARENEARPOINT( q.at( 1 ), QgsPoint( 1.4142, 1.4142 ), 0.001 );
192   QGSCOMPARENEARPOINT( q.at( 2 ), QgsPoint( 1.7678, -1.7678 ), 0.001 );
193   QGSCOMPARENEARPOINT( q.at( 3 ), QgsPoint( -1.4142, -1.4142 ), 0.001 );
194   QGSCOMPARENEARPOINT( q.at( 0 ), QgsPoint( -1.7678, 1.7678 ), 0.001 );
195 
196   elpq = QgsEllipse( QgsPoint( 0, 0 ), 5, 2.5, 45 );
197   q.clear();
198   q = elpq.quadrant();
199   QGSCOMPARENEARPOINT( q.at( 3 ), QgsPoint( -1.7678, 1.7678 ), 0.001 );
200   QGSCOMPARENEARPOINT( q.at( 0 ), QgsPoint( 3.5355, 3.5355 ), 0.001 );
201   QGSCOMPARENEARPOINT( q.at( 1 ), QgsPoint( 1.7678, -1.7678 ), 0.001 );
202   QGSCOMPARENEARPOINT( q.at( 2 ), QgsPoint( -3.5355, -3.5355 ), 0.001 );
203 
204   //test conversion
205   // points
206   QgsPointSequence pts;
207   pts = QgsEllipse( QgsPoint( 0, 0 ), 5, 2, 0 ).points( 4 );
208   q = QgsEllipse( QgsPoint( 0, 0 ), 5, 2, 0 ).quadrant();
209   QCOMPARE( pts.length(), 4 );
210   QGSCOMPARENEARPOINT( q.at( 0 ), pts.at( 0 ), 2 );
211   QGSCOMPARENEARPOINT( q.at( 1 ), pts.at( 1 ), 2 );
212   QGSCOMPARENEARPOINT( q.at( 2 ), pts.at( 2 ), 2 );
213   QGSCOMPARENEARPOINT( q.at( 3 ), pts.at( 3 ), 2 );
214 
215   QVERIFY( QgsEllipse( QgsPoint( 0, 0 ), 5, 2, 0 ).points( 2 ).isEmpty() ); // segments too low
216 
217   // linestring
218   std::unique_ptr< QgsLineString > l( new QgsLineString() );
219 
220   l.reset( QgsEllipse( QgsPoint( 0, 0 ), 5, 2, 0 ).toLineString( 2 ) );
221   QVERIFY( l->isEmpty() ); // segments too low
222 
223   l.reset( QgsEllipse( QgsPoint( 0, 0 ), 5, 2, 0 ).toLineString( 4 ) );
224   QCOMPARE( l->numPoints(), 5 ); // closed linestring
225   QgsPointSequence pts_l;
226   l->points( pts_l );
227   pts_l.pop_back();
228   QCOMPARE( pts, pts_l );
229 
230   // polygon
231   std::unique_ptr< QgsPolygon > p1( new QgsPolygon() );
232 
233   p1.reset( QgsEllipse( QgsPoint( 0, 0 ), 5, 2, 0 ).toPolygon( 2 ) );
234   QVERIFY( p1->isEmpty() ); // segments too low
235 
236   p1.reset( QgsEllipse( QgsPoint( 0, 0 ), 5, 2, 0 ).toPolygon( 4 ) );
237   q = QgsEllipse( QgsPoint( 0, 0 ), 5, 2, 0 ).quadrant();
238   QCOMPARE( p1->vertexAt( QgsVertexId( 0, 0, 0 ) ), q.at( 0 ) );
239   QCOMPARE( p1->vertexAt( QgsVertexId( 0, 0, 1 ) ), q.at( 1 ) );
240   QCOMPARE( p1->vertexAt( QgsVertexId( 0, 0, 2 ) ), q.at( 2 ) );
241   QCOMPARE( p1->vertexAt( QgsVertexId( 0, 0, 3 ) ), q.at( 3 ) );
242   QCOMPARE( p1->vertexAt( QgsVertexId( 0, 0, 4 ) ), q.at( 0 ) );
243   QCOMPARE( 0, p1->numInteriorRings() );
244   QCOMPARE( 5, p1->exteriorRing()->numPoints() );
245 
246   p1.reset( QgsEllipse( QgsPoint( 0, 0 ), 5, 2, 90 ).toPolygon( 4 ) );
247   q = QgsEllipse( QgsPoint( 0, 0 ), 5, 2, 90 ).quadrant();
248   QCOMPARE( p1->vertexAt( QgsVertexId( 0, 0, 0 ) ), q.at( 0 ) );
249   QCOMPARE( p1->vertexAt( QgsVertexId( 0, 0, 1 ) ), q.at( 1 ) );
250   QCOMPARE( p1->vertexAt( QgsVertexId( 0, 0, 2 ) ), q.at( 2 ) );
251   QCOMPARE( p1->vertexAt( QgsVertexId( 0, 0, 3 ) ), q.at( 3 ) );
252   QCOMPARE( p1->vertexAt( QgsVertexId( 0, 0, 4 ) ), q.at( 0 ) );
253   QCOMPARE( 0, p1->numInteriorRings() );
254   QCOMPARE( 5, p1->exteriorRing()->numPoints() );
255 
256   p1.reset( elpq.toPolygon( 4 ) );
257   q = elpq.quadrant();
258   QCOMPARE( p1->vertexAt( QgsVertexId( 0, 0, 0 ) ), q.at( 0 ) );
259   QCOMPARE( p1->vertexAt( QgsVertexId( 0, 0, 1 ) ), q.at( 1 ) );
260   QCOMPARE( p1->vertexAt( QgsVertexId( 0, 0, 2 ) ), q.at( 2 ) );
261   QCOMPARE( p1->vertexAt( QgsVertexId( 0, 0, 3 ) ), q.at( 3 ) );
262   QCOMPARE( p1->vertexAt( QgsVertexId( 0, 0, 4 ) ), q.at( 0 ) );
263   QCOMPARE( 0, p1->numInteriorRings() );
264   QCOMPARE( 5, p1->exteriorRing()->numPoints() );
265 
266   // oriented bounding box
267   std::unique_ptr< QgsPolygon > ombb( QgsEllipse().orientedBoundingBox() );
268   QVERIFY( ombb->isEmpty() );
269 
270   elpq = QgsEllipse( QgsPoint( 0, 0 ), 5, 2 );
271   ombb.reset( new QgsPolygon() );
272   QgsLineString *ext = new QgsLineString();
273   ext->setPoints( QgsPointSequence() << QgsPoint( 5, 2 ) << QgsPoint( 5, -2 ) << QgsPoint( -5, -2 ) << QgsPoint( -5, 2 ) );
274   ombb->setExteriorRing( ext );
275   std::unique_ptr< QgsPolygon >ombb2( elpq.orientedBoundingBox() );
276   QCOMPARE( ombb->asWkt( 2 ), ombb2->asWkt( 2 ) );
277 
278   elpq = QgsEllipse( QgsPoint( 0, 0 ), 5, 2.5, 45 );
279   ombb.reset( elpq.orientedBoundingBox() );
280   QGSCOMPARENEARPOINT( ombb->exteriorRing()->vertexAt( QgsVertexId( 0, 0, 0 ) ), QgsPoint( 1.7678, 5.3033 ), 0.0001 );
281   QGSCOMPARENEARPOINT( ombb->exteriorRing()->vertexAt( QgsVertexId( 0, 0, 1 ) ), QgsPoint( 5.3033, 1.7678 ), 0.0001 );
282   QGSCOMPARENEARPOINT( ombb->exteriorRing()->vertexAt( QgsVertexId( 0, 0, 2 ) ), QgsPoint( -1.7678, -5.3033 ), 0.0001 );
283   QGSCOMPARENEARPOINT( ombb->exteriorRing()->vertexAt( QgsVertexId( 0, 0, 3 ) ), QgsPoint( -5.3033, -1.7678 ), 0.0001 );
284 
285   elpq = QgsEllipse( QgsPoint( 0, 0 ), 5, 2.5, 315 );
286   ombb.reset( elpq.orientedBoundingBox() );
287   QGSCOMPARENEARPOINT( ombb->exteriorRing()->vertexAt( QgsVertexId( 0, 0, 0 ) ), QgsPoint( -5.3033, 1.7678 ), 0.0001 );
288   QGSCOMPARENEARPOINT( ombb->exteriorRing()->vertexAt( QgsVertexId( 0, 0, 1 ) ), QgsPoint( -1.7678, 5.3033 ), 0.0001 );
289   QGSCOMPARENEARPOINT( ombb->exteriorRing()->vertexAt( QgsVertexId( 0, 0, 2 ) ), QgsPoint( 5.3033, -1.7678 ), 0.0001 );
290   QGSCOMPARENEARPOINT( ombb->exteriorRing()->vertexAt( QgsVertexId( 0, 0, 3 ) ), QgsPoint( 1.7678, -5.3033 ), 0.0001 );
291 
292   // bounding box
293   QCOMPARE( QgsEllipse().boundingBox(), QgsRectangle() );
294   ombb.reset( QgsEllipse( QgsPoint( 0, 0 ), 5, 2 ).orientedBoundingBox() );
295   QCOMPARE( QgsEllipse( QgsPoint( 0, 0 ), 5, 2 ).boundingBox(), ombb->boundingBox() );
296   QCOMPARE( QgsEllipse( QgsPoint( 0, 0 ), 5, 5 ).boundingBox(), QgsRectangle( QgsPointXY( -5, -5 ), QgsPointXY( 5, 5 ) ) );
297   QCOMPARE( QgsEllipse( QgsPoint( 0, 0 ), 5, 5, 60 ).boundingBox(), QgsRectangle( QgsPointXY( -5, -5 ), QgsPointXY( 5, 5 ) ) );
298   QCOMPARE( QgsEllipse( QgsPoint( 0, 0 ), 13, 9, 45 ).boundingBox().toString( 4 ).toStdString(), QgsRectangle( QgsPointXY( -11.1803, -11.1803 ), QgsPointXY( 11.1803, 11.1803 ) ).toString( 4 ).toStdString() );
299   QCOMPARE( QgsEllipse( QgsPoint( 0, 0 ), 13, 9, 60 ).boundingBox().toString( 4 ).toStdString(), QgsRectangle( QgsPointXY( -12.12436, -10.14889 ), QgsPointXY( 12.12436, 10.14889 ) ).toString( 4 ).toStdString() );
300   QCOMPARE( QgsEllipse( QgsPoint( 0, 0 ), 13, 9, 60 + 90 ).boundingBox().toString( 4 ).toStdString(), QgsRectangle( QgsPointXY( -10.14889, -12.12436 ), QgsPointXY( 10.14889, 12.12436 ) ).toString( 4 ).toStdString() );
301   QCOMPARE( QgsEllipse( QgsPoint( 0, 0 ), 13, 9, 300 ).boundingBox().toString( 4 ).toStdString(), QgsRectangle( QgsPointXY( -12.12436, -10.14889 ), QgsPointXY( 12.12436, 10.14889 ) ).toString( 4 ).toStdString() );
302   QCOMPARE( QgsEllipse( QgsPoint( 0, 0 ), 13, 9, 300 - 90 ).boundingBox().toString( 4 ).toStdString(), QgsRectangle( QgsPointXY( -10.14889, -12.12436 ), QgsPointXY( 10.14889, 12.12436 ) ).toString( 4 ).toStdString() );
303 
304   // focus
305   QCOMPARE( QgsEllipse().fromFoci( QgsPoint( -4, 0 ), QgsPoint( 4, 0 ), QgsPoint( 0, 4 ) ).focusDistance(), 4.0 );
306   QGSCOMPARENEAR( QgsEllipse().fromFoci( QgsPoint( 10, 10 ), QgsPoint( 25, 20 ), QgsPoint( 15, 20 ) ).focusDistance(), 9.01388, 0.0001 );
307 
308   // eccentricity
309   QCOMPARE( QgsEllipse().fromFoci( QgsPoint( -4, 0 ), QgsPoint( 4, 0 ), QgsPoint( 0, 4 ) ).eccentricity(), 0.7071067811865475 );
310   QCOMPARE( QgsEllipse( QgsPoint( 0, 0 ), 3, 3 ).eccentricity(), 0.0 );
311   QVERIFY( std::isnan( QgsEllipse().eccentricity() ) );
312 
313   // area
314   QGSCOMPARENEAR( 31.4159, QgsEllipse( QgsPoint( 0, 0 ), 5, 2, 0 ).area(), 0.0001 );
315   // perimeter
316   p1.reset( QgsEllipse( QgsPoint( 0, 0 ), 5, 2, 45 ).toPolygon( 10000 ) );
317   QGSCOMPARENEAR( QgsEllipse( QgsPoint( 0, 0 ), 5, 2, 45 ).perimeter(), p1->perimeter(), 0.001 );
318 
319 }
320 
321 
322 QGSTEST_MAIN( TestQgsEllipse )
323 #include "testqgsellipse.moc"
324