1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 
3 // SPDX-FileCopyrightText: 2010 Matias Kallio <matias.kallio@gmail.com>
4 // SPDX-FileCopyrightText: 2011 Friedrich W. H. Kossebau <kossebau@kde.org>
5 // SPDX-FileCopyrightText: 2012 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
6 
7 #include "MarbleGlobal.h"
8 #include "MarbleWidget.h"
9 #include "GeoDataCoordinates.h"
10 #include "TestUtils.h"
11 
12 #include <QLocale>
13 #include <QDebug>
14 #include <QTranslator>
15 #include <QTemporaryFile>
16 
17 using namespace Marble;
18 
19 
20 class TestGeoDataCoordinates : public QObject
21 {
22     Q_OBJECT
23 
24 private Q_SLOTS:
25     void initTestCase();
26 
27     void testConstruction();
28     void testSet_Degree();
29     void testSet_Radian();
30     void testSetLongitude_Degree();
31     void testSetLongitude_Radian();
32     void testSetLatitude_Degree();
33     void testSetLatitude_Radian();
34     void testAltitude();
35     void testOperatorAssignment();
36     void testDetail();
37     void testIsPole_data();
38     void testIsPole();
39     void testNotation();
40     void testNormalizeLat_data();
41     void testNormalizeLat();
42     void testNormalizeLon_data();
43     void testNormalizeLon();
44     void testNormalizeDegree_data();
45     void testNormalizeDegree();
46     void testNormalizeRadian_data();
47     void testNormalizeRadian();
48     void testFromStringDMS_data();
49     void testFromStringDMS();
50     void testFromStringDM_data();
51     void testFromStringDM();
52     void testFromStringD_data();
53     void testFromStringD();
54     void testFromLocaleString_data();
55     void testFromLocaleString();
56     void testToString_Decimal_data();
57     void testToString_Decimal();
58     void testToString_DMS_data();
59     void testToString_DMS();
60     void testToString_DM_data();
61     void testToString_DM();
62     void testPack_data();
63     void testPack();
64     void testUTM_data();
65     void testUTM();
66 };
67 
initTestCase()68 void TestGeoDataCoordinates::initTestCase()
69 {
70     QLocale::setDefault( QLocale::c() ); // needed for testing toString* conversions
71 
72     QTime time = QTime::currentTime();
73     qsrand((uint)time.msec());
74 }
75 
76 /*
77  * test constructors
78  */
testConstruction()79 void TestGeoDataCoordinates::testConstruction()
80 {
81     GeoDataCoordinates invalid1;
82 
83     QVERIFY(!invalid1.isValid());
84 
85     GeoDataCoordinates invalid2(invalid1);
86 
87     QVERIFY(!invalid2.isValid());
88     QVERIFY(!invalid1.isValid());
89     QCOMPARE(invalid1, invalid2);
90 
91     const qreal lon = 164.77;
92     const qreal lat = 55.9;
93     const qreal alt = 400.003;
94 
95     GeoDataCoordinates coordinates3(lon, lat, alt, GeoDataCoordinates::Degree);
96 
97     QVERIFY(coordinates3.isValid());
98     QCOMPARE(coordinates3, GeoDataCoordinates(lon, lat, alt, GeoDataCoordinates::Degree));
99     QVERIFY(coordinates3 != invalid1);
100     QVERIFY(coordinates3 != invalid2);
101 
102     QCOMPARE(coordinates3.longitude(GeoDataCoordinates::Degree), lon);
103     QCOMPARE(coordinates3.longitude(), lon*DEG2RAD);
104 
105     QCOMPARE(coordinates3.latitude(GeoDataCoordinates::Degree), lat);
106     QCOMPARE(coordinates3.latitude(), lat*DEG2RAD);
107 
108     QCOMPARE(coordinates3.altitude(), alt);
109 
110     qreal myLongitude = 0;
111     qreal myLatitude = 0;
112 
113     coordinates3.geoCoordinates(myLongitude, myLatitude, GeoDataCoordinates::Degree);
114 
115     QCOMPARE(myLongitude, lon);
116     QCOMPARE(myLatitude, lat);
117 
118     myLongitude = 0;
119     myLatitude = 0;
120 
121     coordinates3.geoCoordinates(myLongitude, myLatitude);
122 
123     QCOMPARE(myLongitude, lon*DEG2RAD);
124     QCOMPARE(myLatitude, lat*DEG2RAD);
125 
126     GeoDataCoordinates coordinates4(lon*DEG2RAD, lat*DEG2RAD, alt);
127 
128     QVERIFY(coordinates4.isValid());
129     QCOMPARE(coordinates4, GeoDataCoordinates(lon, lat, alt, GeoDataCoordinates::Degree));
130     QCOMPARE(coordinates4, coordinates3);
131     QVERIFY(coordinates4 != invalid1);
132     QVERIFY(coordinates4 != invalid2);
133 
134     GeoDataCoordinates coordinates5(coordinates3);
135 
136     QVERIFY(coordinates5.isValid());
137     QCOMPARE(coordinates5, GeoDataCoordinates(lon, lat, alt, GeoDataCoordinates::Degree));
138     QCOMPARE(coordinates5, coordinates3);
139     QCOMPARE(coordinates5, coordinates4);
140     QVERIFY(coordinates5 != invalid1);
141     QVERIFY(coordinates5 != invalid2);
142 
143     GeoDataCoordinates coordinates6(invalid1.longitude(), invalid1.latitude(), invalid1.altitude(), GeoDataCoordinates::Radian, invalid1.detail());
144 
145     QVERIFY(coordinates6.isValid());  // it should be valid, even though
146     QCOMPARE(coordinates6, invalid1); // it is equal to an invalid one
147 }
148 
149 /*
150  * test setting coordinates in degree
151  */
testSet_Degree()152 void TestGeoDataCoordinates::testSet_Degree()
153 {
154     const qreal lon = 345.8;
155     const qreal lat = 70.3;
156     const qreal alt = 1000.9;
157 
158     GeoDataCoordinates coordinates1; // invalid
159     coordinates1.set(lon, lat, alt, GeoDataCoordinates::Degree);
160 
161     QVERIFY(coordinates1.isValid());
162 
163     GeoDataCoordinates coordinates2(coordinates1);
164     coordinates2.set(0, 0, 0, GeoDataCoordinates::Degree);
165 
166     QVERIFY(coordinates2.isValid());
167     QCOMPARE(coordinates1, GeoDataCoordinates(lon, lat, alt, GeoDataCoordinates::Degree));
168     QCOMPARE(coordinates2, GeoDataCoordinates(0, 0, 0, GeoDataCoordinates::Degree));
169 
170 }
171 
172 /*
173  * test setting coordinates in radian
174  */
testSet_Radian()175 void TestGeoDataCoordinates::testSet_Radian()
176 {
177     const qreal lon = 1.3;
178     const qreal lat = 0.7;
179     const qreal alt = 6886.44;
180 
181     GeoDataCoordinates coordinates1; // invalid
182     coordinates1.set(lon, lat, alt);
183 
184     QVERIFY(coordinates1.isValid());
185 
186     GeoDataCoordinates coordinates2(coordinates1);
187     coordinates2.set(0, 0, 0);
188 
189     QVERIFY(coordinates2.isValid());
190     QCOMPARE(coordinates1, GeoDataCoordinates(lon, lat, alt));
191     QCOMPARE(coordinates2, GeoDataCoordinates(0, 0, 0));
192 }
193 
194 /*
195  * test setLongitude() in degree
196  */
testSetLongitude_Degree()197 void TestGeoDataCoordinates::testSetLongitude_Degree()
198 {
199     const qreal lon = 143.8;
200 
201     GeoDataCoordinates coordinates1; // invalid
202     coordinates1.setLongitude(lon, GeoDataCoordinates::Degree);
203 
204     QVERIFY(coordinates1.isValid());
205 
206     GeoDataCoordinates coordinates2(coordinates1);
207     coordinates2.setLongitude(0, GeoDataCoordinates::Degree);
208 
209     QVERIFY(coordinates2.isValid());
210     QCOMPARE(coordinates1, GeoDataCoordinates(lon, 0, 0, GeoDataCoordinates::Degree));
211     QCOMPARE(coordinates2, GeoDataCoordinates(0, 0, 0, GeoDataCoordinates::Degree));
212 }
213 
214 /*
215  * test setLongitude() in radian
216  */
testSetLongitude_Radian()217 void TestGeoDataCoordinates::testSetLongitude_Radian()
218 {
219     const qreal lon = 2.5;
220 
221     GeoDataCoordinates coordinates1; // invalid
222     coordinates1.setLongitude(lon);
223 
224     QVERIFY(coordinates1.isValid());
225 
226     GeoDataCoordinates coordinates2(coordinates1);
227     coordinates2.setLongitude(0);
228 
229     QVERIFY(coordinates2.isValid());
230     QCOMPARE(coordinates1, GeoDataCoordinates(lon, 0));
231     QCOMPARE(coordinates2, GeoDataCoordinates(0, 0));
232 }
233 
234 /*
235  * test setLatitude() and latitude() in degree
236  */
testSetLatitude_Degree()237 void TestGeoDataCoordinates::testSetLatitude_Degree()
238 {
239     const qreal lat = 75.0;
240 
241     GeoDataCoordinates coordinates1; // invalid
242     coordinates1.setLatitude(lat, GeoDataCoordinates::Degree);
243 
244     QVERIFY(coordinates1.isValid());
245 
246     GeoDataCoordinates coordinates2(coordinates1);
247     coordinates2.setLatitude(0, GeoDataCoordinates::Degree);
248 
249     QVERIFY(coordinates2.isValid());
250     QCOMPARE(coordinates1, GeoDataCoordinates(0, lat, 0, GeoDataCoordinates::Degree));
251     QCOMPARE(coordinates2, GeoDataCoordinates(0, 0, 0, GeoDataCoordinates::Degree));
252 }
253 
254 /*
255  * test setLatitude() in radian
256  */
testSetLatitude_Radian()257 void TestGeoDataCoordinates::testSetLatitude_Radian()
258 {
259     const qreal lat = 1.2;
260 
261     GeoDataCoordinates coordinates1; // invalid
262     coordinates1.setLatitude(lat);
263 
264     QVERIFY(coordinates1.isValid());
265 
266     GeoDataCoordinates coordinates2(coordinates1);
267     coordinates2.setLatitude(0);
268 
269     QVERIFY(coordinates2.isValid());
270     QCOMPARE(coordinates1, GeoDataCoordinates(0, lat));
271     QCOMPARE(coordinates2, GeoDataCoordinates(0, 0));
272 }
273 
274 /*
275  * test setAltitude()
276  */
testAltitude()277 void TestGeoDataCoordinates::testAltitude()
278 {
279     const qreal alt = 400;
280 
281     GeoDataCoordinates coordinates1; // invalid
282     coordinates1.setAltitude(alt);
283 
284     QVERIFY(coordinates1.isValid());
285 
286     GeoDataCoordinates coordinates2(coordinates1);
287     coordinates2.setAltitude(0);
288 
289     QVERIFY(coordinates2.isValid());
290     QCOMPARE(coordinates1, GeoDataCoordinates(0, 0, alt));
291     QCOMPARE(coordinates2, GeoDataCoordinates(0, 0, 0));
292 }
293 
testOperatorAssignment()294 void TestGeoDataCoordinates::testOperatorAssignment()
295 {
296     const qreal lon = 123.4;
297     const qreal lat = 56.7;
298     const qreal alt = 890.1;
299 
300     const GeoDataCoordinates coordinates1(lon, lat, alt, GeoDataCoordinates::Degree);
301     const GeoDataCoordinates coordinates2(0, 0, 0);
302 
303     GeoDataCoordinates coordinates3; // invalid
304     coordinates3 = coordinates1;
305 
306     QVERIFY(coordinates3.isValid());
307     QCOMPARE(coordinates1, GeoDataCoordinates(lon, lat, alt, GeoDataCoordinates::Degree)); // stays unmodified
308     QCOMPARE(coordinates3, coordinates1);
309 
310     coordinates3 = GeoDataCoordinates();
311 
312     QVERIFY(!coordinates3.isValid());
313 
314     GeoDataCoordinates coordinates4(coordinates1);
315     coordinates4 = coordinates2;
316 
317     QVERIFY(coordinates4.isValid());
318     QCOMPARE(coordinates1, GeoDataCoordinates(lon, lat, alt, GeoDataCoordinates::Degree)); // stays unmodified
319     QCOMPARE(coordinates2, GeoDataCoordinates(0, 0, 0)); // stays unmodified
320     QCOMPARE(coordinates4, coordinates2);
321 }
322 
323 /*
324  * test setDetail() and detail()
325  */
testDetail()326 void TestGeoDataCoordinates::testDetail()
327 {
328     const quint8 detailnumber = 15;
329 
330     GeoDataCoordinates coordinates1;
331     coordinates1.setDetail(detailnumber);
332 
333     GeoDataCoordinates coordinates2(coordinates1);
334     coordinates2.setDetail(0);
335 
336     QCOMPARE(coordinates1.detail(), detailnumber);
337 }
338 
339 /*
340  * test setDefaultNotation() and defaultNotation
341  */
testNotation()342 void TestGeoDataCoordinates::testNotation()
343 {
344     GeoDataCoordinates::setDefaultNotation(GeoDataCoordinates::Decimal);
345     QVERIFY(GeoDataCoordinates::defaultNotation() == GeoDataCoordinates::Decimal);
346 
347     GeoDataCoordinates::setDefaultNotation(GeoDataCoordinates::DMS);
348     QVERIFY(GeoDataCoordinates::defaultNotation() == GeoDataCoordinates::DMS);
349 }
350 
351 /*
352  * test data for testIsPole()
353  */
testIsPole_data()354 void TestGeoDataCoordinates::testIsPole_data()
355 {
356     QTest::addColumn<qreal>("lon");
357     QTest::addColumn<qreal>("lat");
358     QTest::addColumn<qreal>("alt");
359     QTest::addColumn<QString>("pole");
360 
361     QTest::newRow("false") << qreal(50.0) << qreal(50.0) << qreal(0.0) << "false_pole";
362     QTest::newRow("south") << qreal(0.0) << qreal(-90.0) << qreal(0.0) << "south_pole";
363     QTest::newRow("north") << qreal(0.0) << qreal(90.0) << qreal(0.0) << "north_pole";
364 }
365 
366 /*
367  * Test isPole-method
368  */
testIsPole()369 void TestGeoDataCoordinates::testIsPole()
370 {
371     QFETCH(qreal, lon);
372     QFETCH(qreal, lat);
373     QFETCH(qreal, alt);
374     QFETCH(QString, pole);
375 
376     GeoDataCoordinates coordinates1;
377 
378     if (pole == QLatin1String("false_pole")) {
379         coordinates1.set(lon, lat, alt, GeoDataCoordinates::Degree);
380         QVERIFY(coordinates1.isPole() == false);
381     } else if (pole == QLatin1String("south_pole")) {
382         coordinates1.set(lon, lat, alt, GeoDataCoordinates::Degree);
383         QVERIFY(coordinates1.isPole(SouthPole));
384     } else if (pole == QLatin1String("north_pole")) {
385         coordinates1.set(lon, lat, alt, GeoDataCoordinates::Degree);
386         QVERIFY(coordinates1.isPole(NorthPole));
387     }
388 }
389 
testNormalizeLat_data()390 void TestGeoDataCoordinates::testNormalizeLat_data()
391 {
392     QTest::addColumn<qreal>( "latRadian" );
393 
394     QTest::newRow( "north pole" ) << qreal(M_PI / 2);
395     QTest::newRow( "south pole" ) << qreal(- M_PI / 2);
396     QTest::newRow( "somewhere" ) << qreal(1.0);
397 }
398 
testNormalizeLat()399 void TestGeoDataCoordinates::testNormalizeLat()
400 {
401     QFETCH( qreal, latRadian );
402 
403     qreal latDegree = RAD2DEG * latRadian;
404     for ( int i = 1; i < 10; ++i ) {
405         if ( ( i % 2 ) == 0 ) {
406             QCOMPARE( GeoDataCoordinates::normalizeLat( latRadian + i * M_PI, GeoDataCoordinates::Radian ), latRadian );
407             QCOMPARE( GeoDataCoordinates::normalizeLat( latRadian + i * M_PI ), latRadian );
408             QCOMPARE( GeoDataCoordinates::normalizeLat( latDegree + i * 180, GeoDataCoordinates::Degree ), latDegree );
409         }
410         else {
411             QCOMPARE( GeoDataCoordinates::normalizeLat( latRadian + i * M_PI, GeoDataCoordinates::Radian ), -latRadian );
412             QCOMPARE( GeoDataCoordinates::normalizeLat( latRadian + i * M_PI ), -latRadian );
413             QCOMPARE( GeoDataCoordinates::normalizeLat( latDegree + i * 180, GeoDataCoordinates::Degree ), -latDegree );
414         }
415     }
416 }
417 
testNormalizeLon_data()418 void TestGeoDataCoordinates::testNormalizeLon_data()
419 {
420     QTest::addColumn<qreal>( "lonRadian" );
421 
422     QTest::newRow( "half east" ) << qreal(M_PI / 2);
423     QTest::newRow( "half west" ) << qreal(- M_PI / 2);
424     QTest::newRow( "somewhere" ) << qreal(1.0);
425     QTest::newRow( "date line east" ) << qreal(M_PI);
426     QTest::newRow( "date line west" ) << - qreal(M_PI);
427 
428 }
429 
testNormalizeLon()430 void TestGeoDataCoordinates::testNormalizeLon()
431 {
432     QFETCH( qreal, lonRadian );
433 
434     qreal lonDegree = RAD2DEG * lonRadian;
435     for ( int i = 1; i < 10; ++i ) {
436         if ( lonRadian == qreal(M_PI) || lonRadian == qreal(-M_PI) ) {
437             int lonRadianLarge = qRound( lonRadian * 1000 );
438             int lonDegreeLarge = qRound( lonDegree * 1000 );
439             if ( qRound( GeoDataCoordinates::normalizeLon( lonRadian + i * 2 * M_PI ) * 1000 ) != lonRadianLarge
440                  && qRound( GeoDataCoordinates::normalizeLon( lonRadian + i * 2 * M_PI ) * 1000 ) != -lonRadianLarge )
441             {
442                 QFAIL( "Error at M_PI/-M_PI" );
443             }
444             if ( qRound( GeoDataCoordinates::normalizeLon( lonRadian + i * 2 * M_PI, GeoDataCoordinates::Radian ) * 1000 ) != lonRadianLarge
445                  && qRound( GeoDataCoordinates::normalizeLon( lonRadian + i * 2 * M_PI, GeoDataCoordinates::Radian ) * 1000 ) != -lonRadianLarge )
446             {
447                 QFAIL( "Error at M_PI/-M_PI" );
448             }
449             if ( qRound( GeoDataCoordinates::normalizeLon( lonDegree + i * 360, GeoDataCoordinates::Degree ) * 1000 ) != lonDegreeLarge
450                  && qRound( GeoDataCoordinates::normalizeLon( lonDegree + i * 360, GeoDataCoordinates::Degree ) * 1000 ) != -lonDegreeLarge )
451             {
452                 QFAIL( "Error at M_PI/-M_PI" );
453             }
454         }
455         else {
456             QCOMPARE( GeoDataCoordinates::normalizeLon( lonRadian + i * 2 * M_PI, GeoDataCoordinates::Radian ), lonRadian );
457             QCOMPARE( GeoDataCoordinates::normalizeLon( lonRadian + i * 2 * M_PI ), lonRadian );
458             QCOMPARE( GeoDataCoordinates::normalizeLon( lonDegree + i * 360, GeoDataCoordinates::Degree ), lonDegree );
459         }
460     }
461 }
462 
463 /*
464  * test data for testNormalize()
465  */
testNormalizeDegree_data()466 void TestGeoDataCoordinates::testNormalizeDegree_data()
467 {
468     QTest::addColumn<qreal>("lon");
469     QTest::addColumn<qreal>("lat");
470 
471     QTest::newRow("deg") << qreal(200.0) << qreal(130.0);
472 }
473 
474 /*
475  * test normalizeLon(), normalizeLat() and normalizeLonLat()
476  */
testNormalizeDegree()477 void TestGeoDataCoordinates::testNormalizeDegree()
478 {
479     QFETCH(qreal, lon);
480     QFETCH(qreal, lat);
481 
482     QCOMPARE(GeoDataCoordinates::normalizeLon(lon, GeoDataCoordinates::Degree), qreal(-160));
483     QCOMPARE(GeoDataCoordinates::normalizeLat(lat, GeoDataCoordinates::Degree), qreal(50));
484 
485     qreal normalized_lon = lon;
486     qreal normalized_lat = lat;
487 
488     GeoDataCoordinates::normalizeLonLat( normalized_lon, normalized_lat, GeoDataCoordinates::Degree);
489     QCOMPARE(normalized_lon, qreal(20));
490     QCOMPARE(normalized_lat, qreal(50));
491 }
492 
493 /*
494  * test data for testNormalize()
495  */
testNormalizeRadian_data()496 void TestGeoDataCoordinates::testNormalizeRadian_data()
497 {
498     QTest::addColumn<qreal>("lon");
499     QTest::addColumn<qreal>("lat");
500 
501     QTest::newRow("rad") << qreal(3.6) << qreal(2.7);
502 }
503 
504 /*
505  * test normalizeLon(), normalizeLat() and normalizeLonLat()
506  */
testNormalizeRadian()507 void TestGeoDataCoordinates::testNormalizeRadian()
508 {
509     QFETCH(qreal, lon);
510     QFETCH(qreal, lat);
511 
512     // Compare up to three decimals
513     qreal value = GeoDataCoordinates::normalizeLon(lon, GeoDataCoordinates::Radian);
514     QCOMPARE(ceil(value * 1000) / 1000, qreal(-2.683));
515 
516     value = GeoDataCoordinates::normalizeLat(lat, GeoDataCoordinates::Radian);
517     QCOMPARE(ceil(value * 1000) / 1000, qreal(0.442));
518 
519     qreal normalized_lon = lon;
520     qreal normalized_lat = lat;
521 
522     GeoDataCoordinates::normalizeLonLat( normalized_lon, normalized_lat, GeoDataCoordinates::Radian);
523     QCOMPARE(ceil(normalized_lon * 1000) / 1000, qreal(0.459));
524     QCOMPARE(ceil(normalized_lat * 1000) / 1000, qreal(0.442));
525 }
526 
527 enum SignType {NoSign, PositiveSign, NegativeSign};
528 enum SphereType {PosSphere, NegSphere};
529 enum UnitsType {NoUnits, WithUnits};
530 enum SpacesType {NoSpaces, WithSpaces};
531 enum LocaleType {CLocale, SystemLocale};
532 
533 static QString
createDegreeString(SignType signType,int degreeValue,int minutesValue,qreal secondsValue,LocaleType locale,UnitsType unitsType,SpacesType spacesType)534 createDegreeString(SignType signType,
535                    int degreeValue, int minutesValue, qreal secondsValue,
536                    LocaleType locale,
537                    UnitsType unitsType, SpacesType spacesType)
538 {
539     QString string;
540 
541     // add degree
542     if (signType != NoSign) string.append(QLatin1Char(signType==PositiveSign?'+':'-'));
543     string.append(QString::number(degreeValue));
544     if (unitsType == WithUnits) string.append(QChar(0xb0));
545 
546     // add minutes
547     string.append(QLatin1Char(' ') + QString::number(minutesValue));
548     if (unitsType == WithUnits) string.append(QLatin1Char('\''));
549 
550     // add seconds
551     if (locale == CLocale) {
552         string.append(QLatin1Char(' ') + QString::number(secondsValue, 'f', 10));
553     } else {
554         string.append(QLatin1Char(' ') + QLocale::system().toString(secondsValue, 'f', 10));
555     }
556     if (unitsType == WithUnits) string.append(QLatin1Char('"'));
557 
558     if (spacesType == WithSpaces) string.append(QLatin1Char(' '));
559 
560     return string;
561 }
562 
563 static QString
createDegreeString(SignType signType,int degreeValue,qreal minutesValue,LocaleType locale,UnitsType unitsType,SpacesType spacesType)564 createDegreeString(SignType signType,
565                    int degreeValue, qreal minutesValue,
566                    LocaleType locale,
567                    UnitsType unitsType, SpacesType spacesType)
568 {
569     QString string;
570 
571     // add degree
572     if (signType != NoSign) string.append(QLatin1Char(signType==PositiveSign?'+':'-'));
573     string.append(QString::number(degreeValue));
574     if (unitsType == WithUnits) string.append(QChar(0xb0));
575 
576     // add minutes
577     if (locale == CLocale) {
578         string.append(QLatin1Char(' ') + QString::number(minutesValue, 'f', 10));
579     } else {
580         string.append(QLatin1Char(' ') + QLocale::system().toString(minutesValue, 'f', 10));
581     }
582     if (unitsType == WithUnits) string.append(QLatin1Char('\''));
583 
584     if (spacesType == WithSpaces) string.append(QLatin1Char(' '));
585 
586     return string;
587 }
588 
589 static QString
createDegreeString(SignType signType,qreal degreeValue,LocaleType locale,UnitsType unitsType,SpacesType spacesType)590 createDegreeString(SignType signType,
591                    qreal degreeValue,
592                    LocaleType locale,
593                    UnitsType unitsType, SpacesType spacesType)
594 {
595     QString string;
596 
597     // add degree
598     if (signType != NoSign) string.append(QLatin1Char(signType==PositiveSign?'+':'-'));
599     if (locale == CLocale) {
600         string.append(QString::number(degreeValue, 'f', 10));
601     } else {
602         string.append(QLocale::system().toString(degreeValue, 'f', 10));
603     }
604     if (unitsType == WithUnits) string.append(QChar(0xb0));
605 
606     if (spacesType == WithSpaces) string.append(QLatin1Char(' '));
607 
608     return string;
609 }
610 
611 /*
612  * test data for testStringDMS()
613  */
testFromStringDMS_data()614 void TestGeoDataCoordinates::testFromStringDMS_data()
615 {
616     QTest::addColumn<QString>("string");
617     QTest::addColumn<qreal>("lon");
618     QTest::addColumn<qreal>("lat");
619 
620     const QVector<SignType> signTypes = QVector<SignType>()
621         << NoSign << PositiveSign << NegativeSign;
622     const QVector<SphereType> sphereTypes = QVector<SphereType>()
623         << PosSphere << NegSphere;
624     const QVector<UnitsType> unitsTypes = QVector<UnitsType>()
625         << NoUnits << WithUnits;
626     const QVector<SpacesType> spacesTypes = QVector<SpacesType>()
627         << NoSpaces << WithSpaces;
628     const QVector<LocaleType> localeTypes = QVector<LocaleType>()
629         << CLocale << SystemLocale;
630 
631     const QVector<uint> degreeSamples = QVector<uint>()
632         << 0 << 140 << 180;
633     const QVector<uint> minutesSamples = QVector<uint>()
634         << 0 << 23 << 59;
635     const QVector<qreal> secondsSamples = QVector<qreal>()
636         << 0.0 << 3.14159 << 59.9999999;
637 
638     foreach(const UnitsType unitsType, unitsTypes) {
639     foreach(const SpacesType spacesType, spacesTypes) {
640     // lon
641     foreach(const SphereType lonSphere, sphereTypes) {
642     foreach(const SignType lonSignType, signTypes) {
643         const bool lonIsPositive =
644             (lonSphere==PosSphere && lonSignType!=NegativeSign) ||
645             (lonSphere==NegSphere && lonSignType==NegativeSign);
646     foreach(const uint lonDegree, degreeSamples) {
647     foreach(const uint lonMinutes, minutesSamples) {
648         if(lonDegree == 180 && lonMinutes != 0) continue;
649     foreach(const qreal lonSeconds, secondsSamples) {
650         if(lonDegree == 180 && lonSeconds != 0.0) continue;
651     // lat
652     foreach(const SphereType latSphere, sphereTypes) {
653     foreach(const SignType latSignType, signTypes) {
654         const bool latIsPositive =
655             (latSphere==PosSphere && latSignType!=NegativeSign) ||
656             (latSphere==NegSphere && latSignType==NegativeSign);
657     foreach(const uint latDegree, degreeSamples) {
658     foreach(const uint latMinutes, minutesSamples) {
659         if(latDegree == 180 && latMinutes != 0) continue;
660     foreach(const qreal latSeconds, secondsSamples) {
661         if(latDegree == 180 && latSeconds != 0.0) continue;
662     // locale
663     foreach(const LocaleType locale, localeTypes) {
664 
665     // actual construction
666         // Create lon & lat values
667         qreal lon = (qreal)lonDegree + lonMinutes*MIN2HOUR + lonSeconds*SEC2HOUR;
668         if( ! lonIsPositive )
669             lon *= -1;
670         qreal lat = (qreal)latDegree + latMinutes*MIN2HOUR + latSeconds*SEC2HOUR;
671         if( ! latIsPositive )
672             lat *= -1;
673 
674         // Create string
675         QString string;
676         string.append(createDegreeString(latSignType,
677                                          latDegree, latMinutes, latSeconds,
678                                          locale,
679                                          unitsType, spacesType));
680         string.append(QLatin1Char(latSphere==PosSphere?'N':'S'));
681         string.append(QLatin1Char(' '));
682         string.append(createDegreeString(lonSignType,
683                                          lonDegree, lonMinutes, lonSeconds,
684                                          locale,
685                                          unitsType, spacesType));
686         string.append(QLatin1Char(lonSphere==PosSphere?'E':'W'));
687 
688         // Create row title
689         QString rowTitle;
690         rowTitle.append(QLatin1String(spacesType==WithSpaces?"spaced dir":"unspaced dir"))
691                 .append(QLatin1String(unitsType==WithUnits?"|units":"|no units"))
692                 .append(QLatin1String("|lon:"))
693                 .append(QLatin1Char(lonIsPositive?'+':'-'))
694                 .append(QString::number(lonDegree)+QChar(0xb0))
695                 .append(QString::number(lonMinutes)+QLatin1Char('\''))
696                 .append(QString::number(lonSeconds, 'f', 10)+QLatin1Char('"'))
697                 .append(QLatin1Char(lonSphere==PosSphere?'P':'N'))
698                 .append(QLatin1String("|lat:"))
699                 .append(QLatin1Char(latIsPositive?'+':'-'))
700                 .append(QString::number(latDegree)+QChar(0xb0))
701                 .append(QString::number(latMinutes)+QLatin1Char('\''))
702                 .append(QString::number(latSeconds, 'f', 10)+QLatin1Char('"'))
703                 .append(QLatin1Char(latSphere==PosSphere?'P':'N'))
704                 .append(QLatin1Char('|')).append(QLatin1Char(locale==CLocale?'C':'L'))
705                 .append(QLatin1Char('|')).append(string).append(QLatin1Char('|'));
706         QTest::newRow(rowTitle.toLatin1().constData())
707             << string
708             << lon
709             << lat;
710     }
711     }
712     }
713     }
714     }
715     }
716     }
717     }
718     }
719     }
720     }
721     }
722     }
723 }
724 
725 /*
726  * test fromString() with DMS notation
727  */
testFromStringDMS()728 void TestGeoDataCoordinates::testFromStringDMS()
729 {
730     // only run random 5% of all possible permutations
731     if ((qreal(qrand()) / RAND_MAX) > 0.05) {
732         QSKIP("Not picked for this run.");
733     }
734 
735     QFETCH(QString, string);
736     QFETCH(qreal, lon);
737     QFETCH(qreal, lat);
738 
739     bool succeeded = false;
740     const GeoDataCoordinates coords = GeoDataCoordinates::fromString(string, succeeded);
741 
742     if(! succeeded)
743         qWarning() << "Could not parse"<<string <<"for"<<lon<<lat;
744 
745     QVERIFY(succeeded);
746     QCOMPARE(coords.longitude(GeoDataCoordinates::Degree), lon);
747     QCOMPARE(coords.latitude(GeoDataCoordinates::Degree),  lat);
748 }
749 
750 /*
751  * test data for testStringDM()
752  */
testFromStringDM_data()753 void TestGeoDataCoordinates::testFromStringDM_data()
754 {
755     QTest::addColumn<QString>("string");
756     QTest::addColumn<qreal>("lon");
757     QTest::addColumn<qreal>("lat");
758 
759     const QVector<SignType> signTypes = QVector<SignType>()
760         << NoSign << PositiveSign << NegativeSign;
761     const QVector<SphereType> sphereTypes = QVector<SphereType>()
762         << PosSphere << NegSphere;
763     const QVector<UnitsType> unitsTypes = QVector<UnitsType>()
764         << NoUnits << WithUnits;
765     const QVector<SpacesType> spacesTypes = QVector<SpacesType>()
766         << NoSpaces << WithSpaces;
767     const QVector<LocaleType> localeTypes = QVector<LocaleType>()
768         << CLocale << SystemLocale;
769 
770     const QVector<uint> degreeSamples = QVector<uint>()
771         << 0 << 140 << 180;
772     const QVector<qreal> minutesSamples = QVector<qreal>()
773         << 0.0 << 3.14159 << 59.9999999;
774 
775     foreach(const UnitsType unitsType, unitsTypes) {
776     foreach(const SpacesType spacesType, spacesTypes) {
777     // lon
778     foreach(const SphereType lonSphere, sphereTypes) {
779     foreach(const SignType lonSignType, signTypes) {
780         const bool lonIsPositive =
781             (lonSphere==PosSphere && lonSignType!=NegativeSign) ||
782             (lonSphere==NegSphere && lonSignType==NegativeSign);
783     foreach(const uint lonDegree, degreeSamples) {
784     foreach(const qreal lonMinutes, minutesSamples) {
785         if(lonDegree == 180 && lonMinutes != 0.0) continue;
786     // lat
787     foreach(const SphereType latSphere, sphereTypes) {
788     foreach(const SignType latSignType, signTypes) {
789         const bool latIsPositive =
790             (latSphere==PosSphere && latSignType!=NegativeSign) ||
791             (latSphere==NegSphere && latSignType==NegativeSign);
792     foreach(const uint latDegree, degreeSamples) {
793     foreach(const qreal latMinutes, minutesSamples) {
794         if(latDegree == 180 && latMinutes != 0.0) continue;
795     // locale
796     foreach(const LocaleType locale, localeTypes) {
797 
798     // actual construction
799         // Create lon & lat values
800         qreal lon = (qreal)lonDegree + lonMinutes*MIN2HOUR;
801         if( ! lonIsPositive )
802             lon *= -1;
803         qreal lat = (qreal)latDegree + latMinutes*MIN2HOUR;
804         if( ! latIsPositive )
805             lat *= -1;
806 
807         // Create string
808         QString string;
809         string.append(createDegreeString(latSignType,
810                                          latDegree, latMinutes,
811                                          locale,
812                                          unitsType, spacesType));
813         string.append(QLatin1Char(latSphere==PosSphere?'N':'S'));
814         string.append(QLatin1Char(' '));
815         string.append(createDegreeString(lonSignType,
816                                          lonDegree, lonMinutes,
817                                          locale,
818                                          unitsType, spacesType));
819         string.append(QLatin1Char(lonSphere==PosSphere?'E':'W'));
820 
821         // Create row title
822         QString rowTitle;
823         rowTitle.append(QLatin1String(spacesType==WithSpaces?"spaced dir":"unspaced dir"))
824                 .append(QLatin1String(unitsType==WithUnits?"|units":"|no units"))
825                 .append(QLatin1String("|lon:"))
826                 .append(QLatin1Char(lonIsPositive?'+':'-'))
827                 .append(QString::number(lonDegree)+QChar(0xb0))
828                 .append(QString::number(lonMinutes, 'f', 10)+QLatin1Char('\''))
829                 .append(QLatin1Char(lonSphere==PosSphere?'P':'N'))
830                 .append(QLatin1String("|lat:"))
831                 .append(QLatin1Char(latIsPositive?'+':'-'))
832                 .append(QString::number(latDegree)+QChar(0xb0))
833                 .append(QString::number(latMinutes, 'f', 10)+QLatin1Char('\''))
834                 .append(QLatin1Char(latSphere==PosSphere?'P':'N'))
835                 .append(QLatin1Char('|')).append(QLatin1Char(locale==CLocale?'C':'L'))
836                 .append(QLatin1Char('|')).append(string).append(QLatin1Char('|'));
837         QTest::newRow(rowTitle.toLatin1().constData())
838             << string
839             << lon
840             << lat;
841     }
842     }
843     }
844     }
845     }
846     }
847     }
848     }
849     }
850     }
851     }
852 }
853 
854 /*
855  * test fromString() with DM notation
856  */
testFromStringDM()857 void TestGeoDataCoordinates::testFromStringDM()
858 {
859     // only run random 5% of all possible permutations
860     if ((qreal(qrand()) / RAND_MAX) > 0.05) {
861         QSKIP("Not picked for this run.");
862     }
863 
864 
865     QFETCH(QString, string);
866     QFETCH(qreal, lon);
867     QFETCH(qreal, lat);
868 
869     bool succeeded = false;
870     const GeoDataCoordinates coords = GeoDataCoordinates::fromString(string, succeeded);
871 
872     if(! succeeded)
873         qWarning() << "Could not parse"<<string <<"for"<<lon<<lat;
874 
875     QVERIFY(succeeded);
876     QCOMPARE(coords.longitude(GeoDataCoordinates::Degree), lon);
877     QCOMPARE(coords.latitude(GeoDataCoordinates::Degree),  lat);
878 }
879 
880 /*
881  * test data for testStringDM()
882  */
testFromStringD_data()883 void TestGeoDataCoordinates::testFromStringD_data()
884 {
885     QTest::addColumn<QString>("string");
886     QTest::addColumn<qreal>("lon");
887     QTest::addColumn<qreal>("lat");
888 
889     const QVector<SignType> signTypes = QVector<SignType>()
890         << NoSign << PositiveSign << NegativeSign;
891     const QVector<SphereType> sphereTypes = QVector<SphereType>()
892         << PosSphere << NegSphere;
893     const QVector<UnitsType> unitsTypes = QVector<UnitsType>()
894         << NoUnits << WithUnits;
895     const QVector<SpacesType> spacesTypes = QVector<SpacesType>()
896         << NoSpaces << WithSpaces;
897     const QVector<LocaleType> localeTypes = QVector<LocaleType>()
898         << CLocale << SystemLocale;
899 
900     const QVector<qreal> degreeSamples = QVector<qreal>()
901         << qreal(0.0) << qreal(3.14159) << qreal(180.0);
902 
903     foreach(const UnitsType unitsType, unitsTypes) {
904     foreach(const SpacesType spacesType, spacesTypes) {
905     // lon
906     foreach(const SphereType lonSphere, sphereTypes) {
907     foreach(const SignType lonSignType, signTypes) {
908         const bool lonIsPositive =
909             (lonSphere==PosSphere && lonSignType!=NegativeSign) ||
910             (lonSphere==NegSphere && lonSignType==NegativeSign);
911     foreach(const qreal lonDegree, degreeSamples) {
912     // lat
913     foreach(const SphereType latSphere, sphereTypes) {
914     foreach(const SignType latSignType, signTypes) {
915         const bool latIsPositive =
916             (latSphere==PosSphere && latSignType!=NegativeSign) ||
917             (latSphere==NegSphere && latSignType==NegativeSign);
918     foreach(const qreal latDegree, degreeSamples) {
919     // locale
920     foreach(const LocaleType locale, localeTypes) {
921 
922     // actual construction
923         // Create lon & lat values
924         qreal lon = lonDegree;
925         if (! lonIsPositive)
926             lon *= -1;
927         qreal lat = latDegree;
928         if (! latIsPositive)
929             lat *= -1;
930 
931         // Create string
932         QString string;
933         string.append(createDegreeString(latSignType,
934                                          latDegree,
935                                          locale,
936                                          unitsType, spacesType));
937         string.append(QLatin1Char(latSphere==PosSphere?'N':'S'));
938         string.append(QLatin1Char(' '));
939         string.append(createDegreeString(lonSignType,
940                                          lonDegree,
941                                          locale,
942                                          unitsType, spacesType));
943         string.append(QLatin1Char(lonSphere==PosSphere?'E':'W'));
944 
945         // Create row title
946         QString rowTitle;
947         rowTitle.append(QLatin1String(spacesType==WithSpaces?"spaced dir":"unspaced dir"))
948                 .append(QLatin1String(unitsType==WithUnits?"|units":"|no units"))
949                 .append(QLatin1String("|lon:"))
950                 .append(QLatin1Char(lonIsPositive?'+':'-'))
951                 .append(QString::number(lonDegree, 'f', 10)+QChar(0xb0))
952                 .append(QLatin1Char(lonSphere==PosSphere?'P':'N'))
953                 .append(QLatin1String("|lat:"))
954                 .append(QLatin1Char(latIsPositive?'+':'-'))
955                 .append(QString::number(latDegree, 'f', 10)+QChar(0xb0))
956                 .append(QLatin1Char(latSphere==PosSphere?'P':'N'))
957                 .append(QLatin1Char('|')).append(QLatin1Char(locale==CLocale?'C':'L'))
958                 .append(QLatin1Char('|')).append(string).append(QLatin1Char('|'));
959         QTest::newRow(rowTitle.toLatin1().constData())
960             << string
961             << lon
962             << lat;
963     }
964     }
965     }
966     }
967     }
968     }
969     }
970     }
971     }
972 
973     QTest::newRow("scientific notation") << "0.0,1.0e-2" << qreal(1.0e-2) << qreal(0.0);
974     QTest::newRow("scientific notation") << "-2.4E0 1.0e-18" << qreal(1e-18) << qreal(-2.4e0);
975     QTest::newRow("scientific notation") << "1.14e-02;1.33e+01" << qreal(1.33e1) << qreal(1.14e-2);
976 }
977 
978 /*
979  * test fromString() with DM notation
980  */
testFromStringD()981 void TestGeoDataCoordinates::testFromStringD()
982 {
983     QFETCH(QString, string);
984     QFETCH(qreal, lon);
985     QFETCH(qreal, lat);
986 
987     bool succeeded = false;
988     const GeoDataCoordinates coords = GeoDataCoordinates::fromString(string, succeeded);
989 
990     if(! succeeded)
991         qWarning() << "Could not parse"<<string <<"for"<<lon<<lat;
992 
993     QVERIFY(succeeded);
994     QCOMPARE(coords.longitude(GeoDataCoordinates::Degree), lon);
995     QCOMPARE(coords.latitude(GeoDataCoordinates::Degree),  lat);
996 }
997 
998 class FromStringRegExpTranslator : public QTranslator
999 {
1000 public:
FromStringRegExpTranslator(const QString & _degree,const QString & _minutes,const QString & _seconds,const QString & _north,const QString & _south,const QString & _east,const QString & _west)1001     FromStringRegExpTranslator(const QString& _degree, const QString& _minutes, const QString& _seconds,
1002                                const QString& _north, const QString& _south,
1003                                const QString& _east, const QString& _west)
1004     : QTranslator((QObject*)nullptr)
1005     , degree( _degree )
1006     , minutes( _minutes )
1007     , seconds( _seconds )
1008     , north( _north )
1009     , south( _south )
1010     , east( _east )
1011     , west( _west )
1012     {}
1013 
1014 public: // QTranslator API
isEmpty() const1015     bool isEmpty() const override { return false; }
1016     QString translate( const char* context, const char* sourceText,
1017                                const char* disambiguation = nullptr, int n = -1 ) const override;
1018 private:
1019     const QString degree;
1020     const QString minutes;
1021     const QString seconds;
1022     const QString north;
1023     const QString south;
1024     const QString east;
1025     const QString west;
1026 };
1027 
translate(const char * context,const char * sourceText,const char * disambiguation,int n) const1028 QString FromStringRegExpTranslator::translate(const char* context, const char* sourceText,
1029                                                const char* disambiguation , int n) const
1030 {
1031     Q_UNUSED(n);
1032     if (qstrcmp(context, "GeoDataCoordinates") != 0 )
1033         return QString();
1034 
1035     if (qstrcmp(sourceText, "*") != 0 )
1036         return QString();
1037 
1038     if (qstrcmp(disambiguation, "North direction terms") == 0 )
1039         return north;
1040     if (qstrcmp(disambiguation, "South direction terms") == 0 )
1041         return south;
1042     if (qstrcmp(disambiguation, "East direction terms") == 0 )
1043         return east;
1044     if (qstrcmp(disambiguation, "West direction terms") == 0 )
1045         return west;
1046     if (qstrcmp(disambiguation, "Degree symbol terms") == 0 )
1047         return degree;
1048     if (qstrcmp(disambiguation, "Minutes symbol terms") == 0 )
1049         return minutes;
1050     if (qstrcmp(disambiguation, "Seconds symbol terms") == 0 )
1051         return seconds;
1052 
1053     return QString();
1054 }
1055 
1056 class Sample
1057 {
1058 public:
Sample()1059     Sample() {}
Sample(const char * _name,const char * _string,qreal _lon,qreal _lat)1060     Sample(const char* _name, const char* _string, qreal _lon, qreal _lat)
1061     : name(QString::fromUtf8(_name))
1062     , string(QString::fromUtf8(_string))
1063     , lon(_lon)
1064     , lat(_lat)
1065     {}
1066     QString name;
1067     QString string;
1068     qreal lon;
1069     qreal lat;
1070 };
1071 
1072 class Language {
1073 public:
Language()1074     Language() {}
Language(const char * _name,const char * _degree,const char * _minutes,const char * _seconds,const char * _north,const char * _south,const char * _east,const char * _west,const QVector<Sample> & _samples)1075     Language(const char* _name,
1076              const char* _degree, const char* _minutes, const char* _seconds,
1077              const char* _north, const char* _south, const char* _east, const char* _west,
1078              const QVector<Sample>& _samples)
1079     : name(QString::fromUtf8(_name))
1080     , degree(QString::fromUtf8(_degree))
1081     , minutes(QString::fromUtf8(_minutes))
1082     , seconds(QString::fromUtf8(_seconds))
1083     , north(QString::fromUtf8(_north))
1084     , south(QString::fromUtf8(_south))
1085     , east(QString::fromUtf8(_east))
1086     , west(QString::fromUtf8(_west))
1087     , samples(_samples)
1088     {}
1089     QString name;
1090     QString degree;
1091     QString minutes;
1092     QString seconds;
1093     QString north;
1094     QString south;
1095     QString east;
1096     QString west;
1097     QVector<Sample> samples;
1098 };
1099 
testFromLocaleString_data()1100 void TestGeoDataCoordinates::testFromLocaleString_data()
1101 {
1102     QTest::addColumn<QString>("degree");
1103     QTest::addColumn<QString>("minutes");
1104     QTest::addColumn<QString>("seconds");
1105     QTest::addColumn<QString>("north");
1106     QTest::addColumn<QString>("south");
1107     QTest::addColumn<QString>("east");
1108     QTest::addColumn<QString>("west");
1109 
1110     QTest::addColumn<QString>("string");
1111     QTest::addColumn<qreal>("lon");
1112     QTest::addColumn<qreal>("lat");
1113 
1114     const QVector<Language> languages = QVector<Language>()
1115         << Language(
1116             "English",
1117             "*", // degree
1118             "*", // minutes
1119             "*", // seconds
1120             "*", // north
1121             "*", // south
1122             "*", // east
1123             "*", // west
1124             QVector<Sample>()
1125                 << Sample(
1126                     "London",
1127                     "N051 30.150′ W000 07.234′",
1128                     -0.12056666666666666921, 51.50249999999999772626)
1129                 << Sample(
1130                     "Ålgård",
1131                     "N58.764828 E5.855483",
1132                     5.85548300000000043752, 58.76482800000000139562))
1133 
1134         << Language(
1135             "Japanese",
1136             "度", // degree
1137             "分", // minutes
1138             "秒", // seconds
1139             "北緯", // north
1140             "南緯", // south
1141             "東経", // east
1142             "西経", // west
1143             QVector<Sample>()
1144                 << Sample(
1145                     "London",
1146                     "北緯51度30分28秒 西経0度07分41秒",
1147                     -0.12805555555555556135, 51.50777777777777544088)
1148                 << Sample(
1149                     "Sydney",
1150                     "南緯33度52分06秒 東経151度12分31秒",
1151                     151.20861111111111085847, -33.86833333333333229120))
1152         << Language(
1153             "Korean",
1154             "도", // degree
1155             "분", // minutes
1156             "초", // seconds
1157             "북위", // north
1158             "남위", // south
1159             "동경", // east
1160             "서경", // west
1161             QVector<Sample>()
1162                 << Sample(
1163                     "London",
1164                     "북위 51도 30분 26초, 서경 0도 7분 39초",
1165                     -0.12750000000000000222, 51.50722222222222512755)
1166                 << Sample(
1167                     "Sydney",
1168                     "남위 33도 31분 56초, 동경 151도 12분 40초",
1169                     151.21111111111110858474, -33.53222222222222370647))
1170 
1171 // TODO: allow test control for parsing float in given locale
1172 #if 0
1173         << Language(
1174             "Galician",
1175             "", // degree
1176             "", // minutes
1177             "", // seconds
1178             "N", //north
1179             "S", // south
1180             "L|E", // east
1181             "O|W", // west
1182             QVector<Sample>()
1183                 << Sample(
1184                     "Campamento",
1185                     "36º10,67´N 5º24,29´W",
1186                     -5.40483333333333337833, 36.17783333333333217752))
1187 #endif
1188 
1189         << Language(
1190             "German",
1191             "*", // degree
1192             "*", // minutes
1193             "*", // seconds
1194             "N", //north
1195             "S", // south
1196             "O", // east
1197             "W", // west
1198             QVector<Sample>()
1199                 << Sample(
1200                     "London",
1201                     "51° 31′ N, 0° 7′ W",
1202                     -0.11666666666666666852, 51.51666666666666571928))
1203 
1204         << Language(
1205             "Greek",
1206             "", // degree
1207             "", // minutes
1208             "", // seconds
1209             "Β", // north
1210             "Ν", // south
1211             "Α", // east
1212             "Δ", // west
1213             QVector<Sample>()
1214                 << Sample(
1215                     "Χαλκίδα",
1216                     "38° 28′ Β 23° 36′ Α",
1217                     23.6, 38.46666666666666856))
1218 
1219         << Language(
1220             "Dutch",
1221             "", // degree
1222             "", // minutes
1223             "", // seconds
1224             "N|NB", // north
1225             "Z|ZB", // south
1226             "O|OL", // east
1227             "W|WL", // west
1228             QVector<Sample>()
1229                 << Sample(
1230                     "Amersfoort",
1231                     "N 52° 8′ 32.14″ , E 5° 24′ 56.09″",
1232                     5.41558055555555561966, 52.14226111111111094942)
1233 // TODO: allow test control for parsing float in given locale
1234 #if 0
1235                 << Sample(
1236                     "London",
1237                     "51°30'00,55\" NB 0°07'34,45\" WL",
1238                     -0.12623611111111110450, 51.50015277777777811252)
1239                 << Sample(
1240                     "Amsterdam",
1241                     "52°22'12,78\" NB 4°53'42,60\" OL",
1242                     4.89516666666666644403, 52.37021666666666419587)
1243                 << Sample(
1244                     "Capetown",
1245                     "33°55'29,52\" ZB 18°25'26,60\" OL",
1246                     18.42405555555555451974, -33.92486666666666650372)
1247 #endif
1248                )
1249 
1250         << Language(
1251             "Polish",
1252             "", // degree
1253             "", // minutes
1254             "", // seconds
1255             "Pn.|Pn", // north
1256             "Płd.|Płd", // south
1257             "Wschod.|Wschod|Wsch.|Wsch|Ws.|Ws", // east
1258             "Zach.|Zach|Z", // west
1259             QVector<Sample>()
1260                 << Sample(
1261                     "Warsaw",
1262                     "52°13′56″Pn. 21°00′30″Ws.",
1263                     21.00833333333333285964, 52.23222222222221944321))
1264 
1265 // TODO: allow test control for parsing float in given locale
1266 #if 0
1267         << Language(
1268             "Esperanto",
1269             "", // degree
1270             "", // minutes
1271             "", // seconds
1272             "N", // north
1273             "S", // south
1274             "Or", // east
1275             "Ok", // west
1276             QVector<Sample>()
1277                 << Sample(
1278                     "London",
1279                     "52° 8′ 32,14″ N; 5° 24′ 56,09″ Or",
1280                     5.41558055555555561966, 52.14226111111111094942))
1281 #endif
1282 
1283         << Language(
1284             "Norwegian",
1285             "", // degree
1286             "", // minutes
1287             "", // seconds
1288             "N", // north
1289             "S", // south
1290             "Ø", // east
1291             "V", // west
1292             QVector<Sample>()
1293                 << Sample(
1294                     "London",
1295                     "51° 30′ 25” N 0° 7′ 39” V",
1296                     -0.12750000000000000222, 51.50694444444444286546)
1297                 << Sample(
1298                     "Ålgård",
1299                     "58° 45′ 53.38″ N 5° 51′ 19.74″ Ø",
1300                     5.85548333333333292927, 58.76482777777777499750))
1301 
1302         << Language(
1303             "Swedish",
1304             "", // degree
1305             "", // minutes
1306             "", // seconds
1307             "N", // north
1308             "S", // south
1309             "O", // east
1310             "V", // west
1311             QVector<Sample>()
1312                 << Sample(
1313                     "London",
1314                     "51°30′29″N 0°7′29″V",
1315                     -0.12472222222222222043, 51.50805555555555770297)
1316                 << Sample(
1317                     "Sydney",
1318                     "33°31′56″S 151°12′40″O",
1319                     151.21111111111110858474, -33.53222222222222370647))
1320 
1321         << Language(
1322             "Icelandic",
1323             "", // degree
1324             "", // minutes
1325             "", // seconds
1326             "N", //north
1327             "S", // south
1328             "A", // east
1329             "V", // west
1330 //TODO:     "breidd 51°30'26\" N, lengd 0°7'39\" V" // London
1331             QVector<Sample>()
1332                 << Sample(
1333                     "Sydney",
1334                     "33°31'56\" S, 151°12'40\" A",
1335                     151.21111111111110858474, -33.53222222222222370647))
1336 
1337         << Language(
1338             "Turkish",
1339             "", // degree
1340             "", // minutes
1341             "", // seconds
1342             "K", // north
1343             "G", // south
1344             "D", // east
1345             "B", // west
1346             QVector<Sample>()
1347                 << Sample(
1348                     "London",
1349                     "51° 30′ 28″ K, 0° 7′ 41″ B",
1350                     -0.12805555555555556135, 51.50777777777777544088))
1351 
1352         << Language(
1353             "Spanish", // (incl. Latin America)
1354             "", // degree
1355             "", // minutes
1356             "", // seconds
1357             "N", // north
1358             "S", // south
1359             "E", // east
1360             "O|W", // west
1361             QVector<Sample>()
1362                 << Sample(
1363                     "London",
1364                     "51°30′25″N 00°07′39″O",
1365                     -0.12750000000000000222, 51.50694444444444286546)
1366                 << Sample(
1367                     "Else",
1368                     "52° 8′ 32.14″ N, 5° 24′ 56.09″ W",
1369                     -5.41558055555555561966, 52.14226111111111094942)
1370                 << Sample(
1371                     "Bogotá",
1372                     "4°35’53″N 74°4’33″O",
1373                     -74.07583333333333541759, 4.59805555555555667269))
1374 
1375         << Language(
1376             "French",
1377             "", // degree
1378             "", // minutes
1379             "", // seconds
1380             "N", // north
1381             "S", // south
1382             "E", // east
1383             "O", // west
1384             QVector<Sample>()
1385                 << Sample(
1386                     "London",
1387                     "51° 30′ 18″ N 0° 04′ 43″ O",
1388                     -0.07861111111111110383, 51.50500000000000255795))
1389 
1390         << Language(
1391             "Portuguese", // incl. Brazilian Portuguese
1392             "", // degree
1393             "", // minutes
1394             "", // seconds
1395             "N", // north
1396             "S", // south
1397             "E|L", // east
1398             "O", // west
1399             QVector<Sample>()
1400                 << Sample(
1401                     "London",
1402                     "52° 8′ 32.14″ N, 5° 24′ 56.09″ E",
1403                     5.41558055555555561966, 52.14226111111111094942))
1404 
1405         << Language(
1406             "Arabic",
1407             "", // degree
1408             "", // minutes
1409             "", // seconds
1410     "شمال", // north
1411     "جنوب", // south
1412     "شرق", // east
1413     "غرب", // west
1414             QVector<Sample>()
1415                 << Sample(
1416                     "Warsaw",
1417     "52°13′56″ شمال 21°00′30″ شرق",
1418                     21.00833333333333285964, 52.23222222222221944321))
1419 
1420         << Language(
1421             "Russian",
1422             "", //"град", "градусов" // degree
1423             "", //"мин", "минут" // minutes
1424             "", //"сек", "секунд" // seconds
1425             "с. ш.", // north
1426             "ю. ш.", // south
1427             "в. д.", // east
1428             "з. д.", // west
1429             QVector<Sample>()
1430                 << Sample(
1431                     "London",
1432                     "51°30′26″ с. ш. 0°07′39″ з. д.",
1433                     -0.12750000000000000222, 51.50722222222222512755))
1434 
1435         << Language(
1436             "Ukrainian",
1437             "", // degree
1438             "", // minutes
1439             "", // seconds
1440             "пн. ш.", // north
1441             "пд. ш.", // south
1442             "сх. д.", // east
1443             "зх. д.", // west
1444             QVector<Sample>()
1445                 << Sample(
1446                     "London",
1447                     "51°30' пн. ш. 0°07' сх. д.",
1448                     0.11666666666666666852, 51.50000000000000000000)
1449                 << Sample(
1450                     "Sydney",
1451                     "33°52'10'' пд. ш. 151°12'30'' сх. д.",
1452                     151.20833333333334280724, -33.86944444444444712872)
1453                 << Sample(
1454                     "Rio de Janeiro",
1455                     "22°54'30'' пд. ш. 43°11'47'' зх. д.",
1456                     -43.19638888888889027839, -22.90833333333333499127))
1457 
1458         << Language(
1459             "Bulgarian",
1460             "", // degree
1461             "", // minutes
1462             "", // seconds
1463             "с. ш.", // north
1464             "ю. ш.", // south
1465             "и. д.", // east
1466             "и. д.", // west
1467             QVector<Sample>()
1468                 << Sample(
1469                     "London",
1470                     "51°30′26″ с. ш. 0°07′39″ и. д.",
1471                     0.12750000000000000222, 51.50722222222222512755))
1472 
1473         << Language(
1474             "Czech",
1475             "", // degree
1476             "", // minutes
1477             "", // seconds
1478             "s. š.", // north
1479             "j. š.", // south
1480             "z. d.", // east
1481             "v. d.", // west
1482             QVector<Sample>()
1483                 << Sample(
1484                     "London",
1485                     "51°30′42″ s. š., 0°02′56″ z. d.",
1486                     0.04888888888888889145, 51.51166666666666316132)
1487                 << Sample(
1488                     "Sydney",
1489                     "33° 52′ j. š., 151° 13′ v. d.",
1490                     -151.21666666666669698316, -33.86666666666666714036))
1491 
1492 
1493         << Language(
1494             "Hindi",
1495             "", // degree
1496             "", // minutes
1497             "", // seconds
1498             "उ", // north
1499             "द", // south
1500             "पू", // east
1501             "प", // west
1502             QVector<Sample>()
1503                 << Sample(
1504                     "London",
1505                     "51°30′25″उ 00°07′39″पू",
1506                     0.12750000000000000222, 51.50694444444444286546))
1507 
1508         << Language(
1509             "Tamil",
1510             "", // degree
1511             "", // minutes
1512             "", // seconds
1513             "வ", // north
1514             "தெ", // south
1515             "கி", // east
1516             "மே", // west
1517             QVector<Sample>()
1518                 << Sample(
1519                     "London",
1520                     "51°30′25″ வ 00°07′39″ கி",
1521                     0.12750000000000000222, 51.50694444444444286546))
1522         ;
1523 
1524     foreach( const Language& language, languages ) {
1525     foreach( const Sample& sample, language.samples ) {
1526         const QString rowTitle =
1527             language.name +
1528             QLatin1String("|") + sample.name +
1529             QLatin1String("|lon:") +
1530             QString::number(sample.lon, 'f', 10) +
1531             QLatin1String("|lat:") +
1532             QString::number(sample.lat, 'f', 10);
1533 
1534         QTest::newRow(rowTitle.toLatin1().constData())
1535             << language.degree
1536             << language.minutes
1537             << language.seconds
1538             << language.north
1539             << language.south
1540             << language.east
1541             << language.west
1542             << sample.string
1543             << sample.lon
1544             << sample.lat;
1545         }
1546     }
1547 }
1548 
1549 
testFromLocaleString()1550 void TestGeoDataCoordinates::testFromLocaleString()
1551 {
1552     QFETCH(QString, degree);
1553     QFETCH(QString, minutes);
1554     QFETCH(QString, seconds);
1555     QFETCH(QString, north);
1556     QFETCH(QString, south);
1557     QFETCH(QString, east);
1558     QFETCH(QString, west);
1559 
1560     QFETCH(QString, string);
1561     QFETCH(qreal, lon);
1562     QFETCH(qreal, lat);
1563 
1564     FromStringRegExpTranslator translator(degree, minutes, seconds, north, south, east, west);
1565     QCoreApplication::installTranslator(&translator);
1566 
1567     bool succeeded = false;
1568     const GeoDataCoordinates coords = GeoDataCoordinates::fromString(string, succeeded);
1569 
1570     if(! succeeded)
1571         qWarning() << "Could not parse"<<string <<"for"<<lon<<lat;
1572 
1573     QVERIFY(succeeded);
1574 
1575 // Uncomment to get the lon and lat values with more precision
1576 // qWarning() << "lon"<<QString::number(coords.longitude(GeoDataCoordinates::Degree), 'f', 20)
1577 //            << "lat"<<QString::number(coords.latitude(GeoDataCoordinates::Degree), 'f', 20);
1578 
1579     QCOMPARE(coords.longitude(GeoDataCoordinates::Degree), lon);
1580     QCOMPARE(coords.latitude(GeoDataCoordinates::Degree),  lat);
1581 
1582     QCoreApplication::removeTranslator(&translator);
1583 }
1584 
1585 /*
1586  * test data for toString()
1587  */
testToString_Decimal_data()1588 void TestGeoDataCoordinates::testToString_Decimal_data()
1589 {
1590     QTest::addColumn<qreal>("lon");
1591     QTest::addColumn<qreal>("lat");
1592     QTest::addColumn<int>("precision");
1593     QTest::addColumn<QString>("expected");
1594 
1595     addRow() << qreal(150.0) << qreal(80.0) << 0 << QString::fromUtf8( " 150°E,   80°N" );
1596     addRow() << qreal(150.0) << qreal(80.0) << 1 << QString::fromUtf8( "150.0°E,  80.0°N" );
1597     addRow() << qreal(150.0) << qreal(80.0) << 2 << QString::fromUtf8( "150.00°E,  80.00°N" );
1598     addRow() << qreal(150.0) << qreal(80.0) << 3 << QString::fromUtf8( "150.000°E,  80.000°N" );
1599     addRow() << qreal(150.0) << qreal(80.0) << 4 << QString::fromUtf8( "150.0000°E,  80.0000°N" );
1600     addRow() << qreal(150.0) << qreal(80.0) << 5 << QString::fromUtf8( "150.00000°E,  80.00000°N" );
1601 
1602     addRow() << qreal(149.6)       << qreal(79.6)       << 0 << QString::fromUtf8( " 150°E,   80°N" );
1603     addRow() << qreal(149.96)      << qreal(79.96)      << 0 << QString::fromUtf8( " 150°E,   80°N" );
1604 
1605     addRow() << qreal(149.6)       << qreal(79.6)       << 1 << QString::fromUtf8( "149.6°E,  79.6°N" );
1606     addRow() << qreal(149.96)      << qreal(79.96)      << 1 << QString::fromUtf8( "150.0°E,  80.0°N" );
1607     addRow() << qreal(149.996)     << qreal(79.996)     << 1 << QString::fromUtf8( "150.0°E,  80.0°N" );
1608 
1609     addRow() << qreal(149.96)      << qreal(79.96)      << 2 << QString::fromUtf8( "149.96°E,  79.96°N" );
1610     addRow() << qreal(149.996)     << qreal(79.996)     << 2 << QString::fromUtf8( "150.00°E,  80.00°N" );
1611     addRow() << qreal(149.9996)    << qreal(79.9996)    << 2 << QString::fromUtf8( "150.00°E,  80.00°N" );
1612 
1613     addRow() << qreal(149.996)     << qreal(79.996)     << 3 << QString::fromUtf8( "149.996°E,  79.996°N" );
1614     addRow() << qreal(149.9996)    << qreal(79.9996)    << 3 << QString::fromUtf8( "150.000°E,  80.000°N" );
1615     addRow() << qreal(149.99996)   << qreal(79.99996)   << 3 << QString::fromUtf8( "150.000°E,  80.000°N" );
1616 
1617     addRow() << qreal(149.9996)    << qreal(79.9996)    << 4 << QString::fromUtf8( "149.9996°E,  79.9996°N" );
1618     addRow() << qreal(149.99996)   << qreal(79.99996)   << 4 << QString::fromUtf8( "150.0000°E,  80.0000°N" );
1619     addRow() << qreal(149.999996)  << qreal(79.999996)  << 4 << QString::fromUtf8( "150.0000°E,  80.0000°N" );
1620 
1621     addRow() << qreal(149.99996)   << qreal(79.99996)   << 5 << QString::fromUtf8( "149.99996°E,  79.99996°N" );
1622     addRow() << qreal(149.999996)  << qreal(79.999996)  << 5 << QString::fromUtf8( "150.00000°E,  80.00000°N" );
1623     addRow() << qreal(149.9999996) << qreal(79.9999996) << 5 << QString::fromUtf8( "150.00000°E,  80.00000°N" );
1624 
1625     addRow() << qreal(149.999996)  << qreal(79.999996)  << 6 << QString::fromUtf8( "149.999996°E,  79.999996°N" );
1626     addRow() << qreal(149.9999996) << qreal(79.9999996) << 6 << QString::fromUtf8( "150.000000°E,  80.000000°N" );
1627 
1628 
1629     addRow() << qreal(150.1)       << qreal(80.1)       << 0 << QString::fromUtf8( " 150°E,   80°N" );
1630     addRow() << qreal(150.01)      << qreal(80.01)      << 0 << QString::fromUtf8( " 150°E,   80°N" );
1631 
1632     addRow() << qreal(150.1)       << qreal(80.1)       << 1 << QString::fromUtf8( "150.1°E,  80.1°N" );
1633     addRow() << qreal(150.01)      << qreal(80.01)      << 1 << QString::fromUtf8( "150.0°E,  80.0°N" );
1634     addRow() << qreal(150.001)     << qreal(80.001)     << 1 << QString::fromUtf8( "150.0°E,  80.0°N" );
1635 
1636     addRow() << qreal(150.01)      << qreal(80.01)      << 2 << QString::fromUtf8( "150.01°E,  80.01°N" );
1637     addRow() << qreal(150.001)     << qreal(80.001)     << 2 << QString::fromUtf8( "150.00°E,  80.00°N" );
1638     addRow() << qreal(150.0001)    << qreal(80.0001)    << 2 << QString::fromUtf8( "150.00°E,  80.00°N" );
1639 
1640     addRow() << qreal(150.001)     << qreal(80.001)     << 3 << QString::fromUtf8( "150.001°E,  80.001°N" );
1641     addRow() << qreal(150.0001)    << qreal(80.0001)    << 3 << QString::fromUtf8( "150.000°E,  80.000°N" );
1642     addRow() << qreal(150.00001)   << qreal(80.00001)   << 3 << QString::fromUtf8( "150.000°E,  80.000°N" );
1643 
1644     addRow() << qreal(150.0001)    << qreal(80.0001)    << 4 << QString::fromUtf8( "150.0001°E,  80.0001°N" );
1645     addRow() << qreal(150.00001)   << qreal(80.00001)   << 4 << QString::fromUtf8( "150.0000°E,  80.0000°N" );
1646     addRow() << qreal(150.000001)  << qreal(80.000001)  << 4 << QString::fromUtf8( "150.0000°E,  80.0000°N" );
1647 
1648     addRow() << qreal(150.00001)   << qreal(80.00001)   << 5 << QString::fromUtf8( "150.00001°E,  80.00001°N" );
1649     addRow() << qreal(150.000001)  << qreal(80.000001)  << 5 << QString::fromUtf8( "150.00000°E,  80.00000°N" );
1650     addRow() << qreal(150.0000001) << qreal(80.0000001) << 5 << QString::fromUtf8( "150.00000°E,  80.00000°N" );
1651 
1652     addRow() << qreal(150.000001)  << qreal(80.000001)  << 6 << QString::fromUtf8( "150.000001°E,  80.000001°N" );
1653     addRow() << qreal(150.0000001) << qreal(80.0000001) << 6 << QString::fromUtf8( "150.000000°E,  80.000000°N" );
1654 }
1655 
1656 /*
1657  * test toString()
1658  */
testToString_Decimal()1659 void TestGeoDataCoordinates::testToString_Decimal()
1660 {
1661     QFETCH( qreal, lon );
1662     QFETCH( qreal, lat );
1663     QFETCH( int, precision );
1664     QFETCH( QString, expected );
1665 
1666     const GeoDataCoordinates coordinates( lon, lat, 0, GeoDataCoordinates::Degree );
1667 
1668     const QString result = coordinates.toString( GeoDataCoordinates::Decimal, precision );
1669     QCOMPARE( result, expected );
1670 }
1671 
1672 /*
1673  * test data for toString()
1674  */
testToString_DMS_data()1675 void TestGeoDataCoordinates::testToString_DMS_data()
1676 {
1677     QTest::addColumn<qreal>("lon");
1678     QTest::addColumn<qreal>("lat");
1679     QTest::addColumn<int>("precision");
1680     QTest::addColumn<QString>("expected");
1681 
1682     addRow() << qreal(0.)                         << qreal(0.)                        << 0 << QString::fromUtf8( "  0°E,   0°S" );
1683     addRow() << qreal(150.)                       << qreal(80.)                       << 0 << QString::fromUtf8( "150°E,  80°N" );
1684     addRow() << qreal(149. + 31./60)              << qreal(79. + 31./60)              << 0 << QString::fromUtf8( "150°E,  80°N" );
1685     addRow() << qreal(149. + 30./60 + 31./3600)   << qreal(79. + 30./60 + 31./3600)   << 0 << QString::fromUtf8( "150°E,  80°N" );
1686     addRow() << qreal(149. + 30./60 + 30.51/3600) << qreal(79. + 30./60 + 30.51/3600) << 0 << QString::fromUtf8( "150°E,  80°N" );
1687     addRow() << qreal(150. + 29./60)              << qreal(80. + 29./60)              << 0 << QString::fromUtf8( "150°E,  80°N" );
1688     addRow() << qreal(150. + 29./60 + 29./3600)   << qreal(80. + 29./60 + 29./3600)   << 0 << QString::fromUtf8( "150°E,  80°N" );
1689     addRow() << qreal(150. + 29./60 + 29.49/3600) << qreal(80. + 29./60 + 29.49/3600) << 0 << QString::fromUtf8( "150°E,  80°N" );
1690 
1691     addRow() << qreal(0.)                         << qreal(0.)                        << 1 << QString::fromUtf8( "  0° 00'E,   0° 00'S" );
1692     addRow() << qreal(150.)                       << qreal(80.)                       << 1 << QString::fromUtf8( "150° 00'E,  80° 00'N" );
1693     addRow() << qreal(149. + 59./60 + 31./3600)   << qreal(79. + 59./60 + 31./3600)   << 1 << QString::fromUtf8( "150° 00'E,  80° 00'N" );
1694     addRow() << qreal(149. + 59./60 + 30.51/3600) << qreal(79. + 59./60 + 30.51/3600) << 1 << QString::fromUtf8( "150° 00'E,  80° 00'N" );
1695     addRow() << qreal(150.          + 29./3600)   << qreal(80.          + 29./3600)   << 1 << QString::fromUtf8( "150° 00'E,  80° 00'N" );
1696     addRow() << qreal(150.          + 29.49/3600) << qreal(80.          + 29.49/3600) << 1 << QString::fromUtf8( "150° 00'E,  80° 00'N" );
1697 
1698     addRow() << qreal(0.)                         << qreal(0.)                        << 2 << QString::fromUtf8( "  0° 00'E,   0° 00'S" );
1699     addRow() << qreal(150.)                       << qreal(80.)                       << 2 << QString::fromUtf8( "150° 00'E,  80° 00'N" );
1700     addRow() << qreal(149. + 59./60 + 31./3600)   << qreal(79. + 59./60 + 31./3600)   << 2 << QString::fromUtf8( "150° 00'E,  80° 00'N" );
1701     addRow() << qreal(149. + 59./60 + 30.51/3600) << qreal(79. + 59./60 + 30.51/3600) << 2 << QString::fromUtf8( "150° 00'E,  80° 00'N" );
1702     addRow() << qreal(150.          + 29./3600)   << qreal(80.          + 29./3600)   << 2 << QString::fromUtf8( "150° 00'E,  80° 00'N" );
1703     addRow() << qreal(150.          + 29.49/3600) << qreal(80.          + 29.49/3600) << 2 << QString::fromUtf8( "150° 00'E,  80° 00'N" );
1704 
1705     addRow() << qreal(0.)                         << qreal(0.)                        << 3 << QString::fromUtf8( "  0° 00' 00\"E,   0° 00' 00\"S" );
1706     addRow() << qreal(150.)                       << qreal(80.)                       << 3 << QString::fromUtf8( "150° 00' 00\"E,  80° 00' 00\"N" );
1707     addRow() << qreal(149. + 59./60 + 59.51/3600) << qreal(79. + 59./60 + 59.51/3600) << 3 << QString::fromUtf8( "150° 00' 00\"E,  80° 00' 00\"N" );
1708     addRow() << qreal(150.          +  0.49/3600) << qreal(80.          +  0.49/3600) << 3 << QString::fromUtf8( "150° 00' 00\"E,  80° 00' 00\"N" );
1709 
1710     addRow() << qreal(0.)                         << qreal(0.)                        << 4 << QString::fromUtf8( "  0° 00' 00\"E,   0° 00' 00\"S" );
1711     addRow() << qreal(150.)                       << qreal(80.)                       << 4 << QString::fromUtf8( "150° 00' 00\"E,  80° 00' 00\"N" );
1712     addRow() << qreal(149. + 59./60 + 59.51/3600) << qreal(79. + 59./60 + 59.51/3600) << 4 << QString::fromUtf8( "150° 00' 00\"E,  80° 00' 00\"N" );
1713     addRow() << qreal(150.          +  0.49/3600) << qreal(80.          +  0.49/3600) << 4 << QString::fromUtf8( "150° 00' 00\"E,  80° 00' 00\"N" );
1714 
1715     addRow() << qreal(0.)                          << qreal(0.)                         << 5 << QString::fromUtf8( "  0° 00' 00.0\"E,   0° 00' 00.0\"S" );
1716     addRow() << qreal(150.)                        << qreal(80.)                        << 5 << QString::fromUtf8( "150° 00' 00.0\"E,  80° 00' 00.0\"N" );
1717     addRow() << qreal(149. + 59./60 + 59.951/3600) << qreal(79. + 59./60 + 59.951/3600) << 5 << QString::fromUtf8( "150° 00' 00.0\"E,  80° 00' 00.0\"N" );
1718     addRow() << qreal(150.          +  0.049/3600) << qreal(80.          +  0.049/3600) << 5 << QString::fromUtf8( "150° 00' 00.0\"E,  80° 00' 00.0\"N" );
1719 
1720     addRow() << qreal(0.)                           << qreal(0.)                          << 6 << QString::fromUtf8( "  0° 00' 00.00\"E,   0° 00' 00.00\"S" );
1721     addRow() << qreal(150.)                         << qreal(80.)                         << 6 << QString::fromUtf8( "150° 00' 00.00\"E,  80° 00' 00.00\"N" );
1722     addRow() << qreal(149. + 59./60 + 59.9951/3600) << qreal(79. + 59./60 + 59.9951/3600) << 6 << QString::fromUtf8( "150° 00' 00.00\"E,  80° 00' 00.00\"N" );
1723     addRow() << qreal(150.          +  0.0049/3600) << qreal(80.          +  0.0049/3600) << 6 << QString::fromUtf8( "150° 00' 00.00\"E,  80° 00' 00.00\"N" );
1724 }
1725 
1726 /*
1727  * test toString()
1728  */
testToString_DMS()1729 void TestGeoDataCoordinates::testToString_DMS()
1730 {
1731     QFETCH( qreal, lon );
1732     QFETCH( qreal, lat );
1733     QFETCH( int, precision );
1734     QFETCH( QString, expected );
1735 
1736     const GeoDataCoordinates coordinates( lon, lat, 0, GeoDataCoordinates::Degree );
1737 
1738     const QString result = coordinates.toString( GeoDataCoordinates::DMS, precision );
1739     QCOMPARE( result, expected );
1740 }
1741 
1742 /*
1743  * test data for toString()
1744  */
testToString_DM_data()1745 void TestGeoDataCoordinates::testToString_DM_data()
1746 {
1747     QTest::addColumn<qreal>("lon");
1748     QTest::addColumn<qreal>("lat");
1749     QTest::addColumn<int>("precision");
1750     QTest::addColumn<QString>("expected");
1751 
1752     addRow() << qreal(0.)              << qreal(0.)             << 0 << QString::fromUtf8( "  0°E,   0°S" );
1753     addRow() << qreal(150.)            << qreal(80.)            << 0 << QString::fromUtf8( "150°E,  80°N" );
1754     addRow() << qreal(149. + 31./60)   << qreal(79. + 31./60)   << 0 << QString::fromUtf8( "150°E,  80°N" );
1755     addRow() << qreal(149. + 30.51/60) << qreal(79. + 30.51/60) << 0 << QString::fromUtf8( "150°E,  80°N" );
1756     addRow() << qreal(150. + 29./60)   << qreal(80. + 29./60)   << 0 << QString::fromUtf8( "150°E,  80°N" );
1757     addRow() << qreal(150. + 29.49/60) << qreal(80. + 29.49/60) << 0 << QString::fromUtf8( "150°E,  80°N" );
1758 
1759     addRow() << qreal(0.)              << qreal(0.)             << 1 << QString::fromUtf8( "  0° 00'E,   0° 00'S" );
1760     addRow() << qreal(150.)            << qreal(80.)            << 1 << QString::fromUtf8( "150° 00'E,  80° 00'N" );
1761     addRow() << qreal(149. + 59.51/60) << qreal(79. + 59.51/60) << 1 << QString::fromUtf8( "150° 00'E,  80° 00'N" );
1762     addRow() << qreal(150. +  0.49/60) << qreal(80. +  0.49/60) << 1 << QString::fromUtf8( "150° 00'E,  80° 00'N" );
1763 
1764     addRow() << qreal(0.)              << qreal(0.)             << 2 << QString::fromUtf8( "  0° 00'E,   0° 00'S" );
1765     addRow() << qreal(150.)            << qreal(80.)            << 2 << QString::fromUtf8( "150° 00'E,  80° 00'N" );
1766     addRow() << qreal(149. + 59.51/60) << qreal(79. + 59.51/60) << 2 << QString::fromUtf8( "150° 00'E,  80° 00'N" );
1767     addRow() << qreal(150. +  0.49/60) << qreal(80. +  0.49/60) << 2 << QString::fromUtf8( "150° 00'E,  80° 00'N" );
1768 
1769     addRow() << qreal(0.)               << qreal(0.)              << 3 << QString::fromUtf8( "  0° 00.0'E,   0° 00.0'S" );
1770     addRow() << qreal(150.)             << qreal(80.)             << 3 << QString::fromUtf8( "150° 00.0'E,  80° 00.0'N" );
1771     addRow() << qreal(149. + 59.951/60) << qreal(79. + 59.951/60) << 3 << QString::fromUtf8( "150° 00.0'E,  80° 00.0'N" );
1772     addRow() << qreal(150. +  0.049/60) << qreal(80. +  0.049/60) << 3 << QString::fromUtf8( "150° 00.0'E,  80° 00.0'N" );
1773 
1774     addRow() << qreal(0.)                << qreal(0.)               << 4 << QString::fromUtf8( "  0° 00.00'E,   0° 00.00'S" );
1775     addRow() << qreal(150.)              << qreal(80.)              << 4 << QString::fromUtf8( "150° 00.00'E,  80° 00.00'N" );
1776     addRow() << qreal(149. + 59.9951/60) << qreal(79. + 59.9951/60) << 4 << QString::fromUtf8( "150° 00.00'E,  80° 00.00'N" );
1777     addRow() << qreal(150. +  0.0049/60) << qreal(80. +  0.0049/60) << 4 << QString::fromUtf8( "150° 00.00'E,  80° 00.00'N" );
1778 }
1779 
1780 /*
1781  * test toString()
1782  */
testToString_DM()1783 void TestGeoDataCoordinates::testToString_DM()
1784 {
1785     QFETCH( qreal, lon );
1786     QFETCH( qreal, lat );
1787     QFETCH( int, precision );
1788     QFETCH( QString, expected );
1789 
1790     const GeoDataCoordinates coordinates( lon, lat, 0, GeoDataCoordinates::Degree );
1791 
1792     const QString result = coordinates.toString( GeoDataCoordinates::DM, precision );
1793     QCOMPARE( result, expected );
1794 }
1795 
1796 /*
1797  * test data for testPack()
1798  */
testPack_data()1799 void TestGeoDataCoordinates::testPack_data()
1800 {
1801     QTest::addColumn<qreal>("lon");
1802     QTest::addColumn<qreal>("lat");
1803     QTest::addColumn<qreal>("alt");
1804 
1805     QTest::newRow("deg") << qreal(180.0) << qreal(90.0) << qreal(400.0);
1806 }
1807 
1808 /*
1809  * test pack() and unPack()
1810  */
testPack()1811 void TestGeoDataCoordinates::testPack()
1812 {
1813     QFETCH(qreal, lon);
1814     QFETCH(qreal, lat);
1815     QFETCH(qreal, alt);
1816 
1817     GeoDataCoordinates coordinates1,coordinates2;
1818     coordinates1.set(lon, lat, alt, GeoDataCoordinates::Degree);
1819 
1820     QTemporaryFile file;
1821     if(file.open()) {
1822         QDataStream out(&file);
1823         coordinates1.pack(out);
1824     }
1825     file.close();
1826 
1827     if(file.open()) {
1828         QDataStream in(&file);
1829         coordinates2.unpack(in);
1830     }
1831     file.close();
1832 
1833     QCOMPARE(coordinates1.longitude(GeoDataCoordinates::Degree), coordinates2.longitude(GeoDataCoordinates::Degree));
1834     QCOMPARE(coordinates1.latitude(GeoDataCoordinates::Degree), coordinates2.latitude(GeoDataCoordinates::Degree));
1835     QCOMPARE(coordinates1.altitude(), coordinates2.altitude());
1836 }
1837 
1838 /*
1839  * test data for testUTM()
1840  */
testUTM_data()1841 void TestGeoDataCoordinates::testUTM_data()
1842 {
1843     QTest::addColumn<qreal>("lon");
1844     QTest::addColumn<qreal>("lat");
1845     QTest::addColumn<int>("zone");
1846     QTest::addColumn<QString>("latitudeBand");
1847     QTest::addColumn<int>("easting");
1848     QTest::addColumn<int>("northing");
1849 
1850     /* Randomly selected locations, converted to UTM with the following
1851      * tools to check their correctness:
1852      * http://home.hiwaay.net/~taylorc/toolbox/geography/geoutm.html
1853      * http://www.earthpoint.us/Convert.aspx
1854      * http://www.synnatschke.de/geo-tools/coordinate-converter.php
1855      * http://www.latlong.net/lat-long-utm.html
1856      * http://leware.net/geo/utmgoogle.htm
1857      * http://geographiclib.sourceforge.net/cgi-bin/GeoConvert
1858      */
1859 
1860     // Equator
1861     addRow() << qreal(-180.0)   << qreal(0.0)       << 1  << "N" << 16602144 << 0;
1862     addRow() << qreal(0)        << qreal(0.0)       << 31 << "N" << 16602144 << 0;
1863     addRow() << qreal(150.567)  << qreal(0.0)       << 56 << "N" << 22918607 << 0;
1864 
1865     // Zone borders
1866     int zoneNumber = 1;
1867     for ( int i = -180; i <= 180; i += 6 ){
1868         addRow() << qreal(i) << qreal(0.0) << zoneNumber << "N" << 16602144 << 0;
1869         zoneNumber++;
1870     }
1871 
1872     // Northern hemisphere
1873     addRow() << qreal(-180.0)   << qreal(15)        << 1  << "P" << 17734904 << 166051369;
1874     addRow() << qreal(0)        << qreal(60.5)      << 31 << "V" << 33523714 << 671085271;
1875     addRow() << qreal(150.567)  << qreal(75.123)    << 56 << "X" << 43029080 << 833876115;
1876 
1877     // Southern hemisphere
1878     addRow() << qreal(-3.5)     << qreal(-50)       << 30 << "F" << 46416654 << 446124952;
1879     addRow() << qreal(22.56)    << qreal(-62.456)   << 34 << "E" << 58047905 << 307404780;
1880 
1881     // Exceptions
1882 
1883     // North pole (no zone associated, so it returns 0)
1884     addRow() << qreal(-100.0)   << qreal(85.0)      << 0  << "Y" << 49026986 << 943981733;
1885     addRow() << qreal(100.0)    << qreal(85.0)      << 0  << "Z" << 50973014 << 943981733;
1886 
1887     // South pole (no zone associated, so it returns 0)
1888     addRow() << qreal(-100.0)   << qreal(-85.0)     << 0  << "A" << 49026986 << 56018267;
1889     addRow() << qreal(100.0)    << qreal(-85.0)     << 0  << "B" << 50973014 << 56018267;
1890 
1891     // Stavanger, in southwestern Norway, is in zone 32
1892     addRow() << qreal(5.73)     << qreal(58.97)     << 32 << "V" << 31201538 << 654131013;
1893     // Same longitude, at the equator, is in zone 31
1894     addRow() << qreal(5.73)     << qreal(0.0)       << 31 << "N" << 80389643 << 0;
1895 
1896     // Svalbard is in zone 33
1897     addRow() << qreal(10.55)    << qreal(78.88)     << 33 << "X" << 40427848 << 876023047;
1898     // Same longitude, at the equator, is in zone 32
1899     addRow() << qreal(10.55)    << qreal(0.0)       << 32 << "N" << 67249738 << 0;
1900 }
1901 
1902 /*
1903  * test UTM-related functions:
1904  *     - utmZone()
1905  *     - utmLatitudeBand()
1906  *     - utmEasting()
1907  *     - utmNorthing()
1908  */
testUTM()1909 void TestGeoDataCoordinates::testUTM(){
1910     QFETCH(qreal, lon);
1911     QFETCH(qreal, lat);
1912     QFETCH(int, zone);
1913     QFETCH(QString, latitudeBand);
1914     QFETCH(int, easting);
1915     QFETCH(int, northing);
1916 
1917     GeoDataCoordinates coordinates;
1918     coordinates.set(lon, lat, 0, GeoDataCoordinates::Degree);
1919 
1920     QCOMPARE(coordinates.utmZone(), zone);
1921     QCOMPARE(coordinates.utmLatitudeBand(), latitudeBand);
1922 
1923     /* Comparing integers is safer than comparing qreals. As the expected
1924      * values are expressed in centimeters, the actual values are converted
1925      * to this unit.
1926      */
1927     int actualEasting = qRound( 100.0 * coordinates.utmEasting() );
1928     int actualNorthing = qRound( 100.0 * coordinates.utmNorthing() );
1929 
1930     QCOMPARE( actualEasting, easting );
1931     QCOMPARE( actualNorthing, northing );
1932 }
1933 
1934 QTEST_MAIN(TestGeoDataCoordinates)
1935 #include "TestGeoDataCoordinates.moc"
1936 
1937 
1938 
1939