Marble

GeoDataCoordinates.cpp
1 //
2 // This file is part of the Marble Virtual Globe.
3 //
4 // This program is free software licensed under the GNU LGPL. You can
5 // find a copy of this license in LICENSE.txt in the top directory of
6 // the source code.
7 //
8 // Copyright 2004-2007 Torsten Rahn <[email protected]>
9 // Copyright 2007-2008 Inge Wallin <[email protected]>
10 // Copyright 2008 Patrick Spendrin <[email protected]>
11 // Copyright 2011 Friedrich W. H. Kossebau <[email protected]>
12 // Copyright 2011 Bernhard Beschow <[email protected]>
13 // Copyright 2015 Alejandro Garcia Montoro <[email protected]>
14 //
15 
16 
17 #include "GeoDataCoordinates.h"
18 #include "GeoDataCoordinates_p.h"
19 #include "LonLatParser_p.h"
20 
21 #include <qmath.h>
22 #include <QDataStream>
23 #include <QPointF>
24 
25 #include "MarbleGlobal.h"
26 #include "MarbleDebug.h"
27 #include "MarbleMath.h"
28 
29 #include "Quaternion.h"
30 
31 namespace Marble
32 {
33 
34 const qreal GeoDataCoordinatesPrivate::sm_semiMajorAxis = 6378137.0;
35 const qreal GeoDataCoordinatesPrivate::sm_semiMinorAxis = 6356752.314;
36 const qreal GeoDataCoordinatesPrivate::sm_eccentricitySquared = 6.69437999013e-03;
37 const qreal GeoDataCoordinatesPrivate::sm_utmScaleFactor = 0.9996;
38 GeoDataCoordinates::Notation GeoDataCoordinates::s_notation = GeoDataCoordinates::DMS;
39 
40 const GeoDataCoordinates GeoDataCoordinates::null = GeoDataCoordinates( 0, 0, 0 ); // don't use default constructor!
41 
42 GeoDataCoordinates::GeoDataCoordinates( qreal _lon, qreal _lat, qreal _alt, GeoDataCoordinates::Unit unit, int _detail )
43  : d( new GeoDataCoordinatesPrivate( _lon, _lat, _alt, unit, _detail ) )
44 {
45  d->ref.ref();
46 }
47 
48 /* simply copy the d pointer
49 * it will be replaced in the detach function instead
50 */
52  : d( other.d )
53 {
54  d->ref.ref();
55 }
56 
57 /* simply copy null's d pointer
58  * it will be replaced in the detach function
59  */
61  : d( null.d )
62 {
63  d->ref.ref();
64 }
65 
66 /*
67  * only delete the private d pointer if the number of references is 0
68  * remember that all copies share the same d pointer!
69  */
70 GeoDataCoordinates::~GeoDataCoordinates()
71 {
72  delete d->m_q;
73  d->m_q = nullptr;
74 
75  if (!d->ref.deref())
76  delete d;
77 #ifdef DEBUG_GEODATA
78 // mDebug() << "delete coordinates";
79 #endif
80 }
81 
83 {
84  return d != null.d;
85 }
86 
87 /*
88  * if only one copy exists, return
89  * else make a new private d pointer object and assign the values of the current
90  * one to it
91  * at the end, if the number of references thus reaches 0 delete it
92  * this state shouldn't happen, but if it does, we have to clean up behind us.
93  */
94 void GeoDataCoordinates::detach()
95 {
96  delete d->m_q;
97  d->m_q = nullptr;
98 
99  if(d->ref.load() == 1) {
100  return;
101  }
102 
103  GeoDataCoordinatesPrivate *new_d = new GeoDataCoordinatesPrivate( *d );
104 
105  if (!d->ref.deref()) {
106  delete d;
107  }
108 
109  d = new_d;
110  d->ref.ref();
111 }
112 
113 /*
114  * call detach() at the start of all non-static, non-const functions
115  */
116 void GeoDataCoordinates::set( qreal _lon, qreal _lat, qreal _alt, GeoDataCoordinates::Unit unit )
117 {
118  detach();
119  d->m_altitude = _alt;
120  switch( unit ){
121  default:
122  case Radian:
123  d->m_lon = _lon;
124  d->m_lat = _lat;
125  break;
126  case Degree:
127  d->m_lon = _lon * DEG2RAD;
128  d->m_lat = _lat * DEG2RAD;
129  break;
130  }
131 }
132 
133 /*
134  * call detach() at the start of all non-static, non-const functions
135  */
137 {
138  detach();
139  switch( unit ){
140  default:
141  case Radian:
142  d->m_lon = _lon;
143  break;
144  case Degree:
145  d->m_lon = _lon * DEG2RAD;
146  break;
147  }
148 }
149 
150 
151 /*
152  * call detach() at the start of all non-static, non-const functions
153  */
155 {
156  detach();
157  switch( unit ){
158  case Radian:
159  d->m_lat = _lat;
160  break;
161  case Degree:
162  d->m_lat = _lat * DEG2RAD;
163  break;
164  }
165 }
166 
167 void GeoDataCoordinates::geoCoordinates( qreal& lon, qreal& lat,
168  GeoDataCoordinates::Unit unit ) const
169 {
170  switch ( unit )
171  {
172  default:
173  case Radian:
174  lon = d->m_lon;
175  lat = d->m_lat;
176  break;
177  case Degree:
178  lon = d->m_lon * RAD2DEG;
179  lat = d->m_lat * RAD2DEG;
180  break;
181  }
182 }
183 
184 void GeoDataCoordinates::geoCoordinates(qreal &lon, qreal &lat) const
185 {
186  lon = d->m_lon;
187  lat = d->m_lat;
188 }
189 
190 void GeoDataCoordinates::geoCoordinates( qreal& lon, qreal& lat, qreal& alt,
191  GeoDataCoordinates::Unit unit ) const
192 {
193  geoCoordinates( lon, lat, unit );
194  alt = d->m_altitude;
195 }
196 
197 void GeoDataCoordinates::geoCoordinates(qreal &lon, qreal &lat, qreal &alt) const
198 {
199  lon = d->m_lon;
200  lat = d->m_lat;
201  alt = d->m_altitude;
202 }
203 
204 qreal GeoDataCoordinates::longitude( GeoDataCoordinates::Unit unit ) const
205 {
206  switch ( unit )
207  {
208  default:
209  case Radian:
210  return d->m_lon;
211  case Degree:
212  return d->m_lon * RAD2DEG;
213  }
214 }
215 
216 qreal GeoDataCoordinates::longitude() const
217 {
218  return d->m_lon;
219 }
220 
221 qreal GeoDataCoordinates::latitude( GeoDataCoordinates::Unit unit ) const
222 {
223  switch ( unit )
224  {
225  default:
226  case Radian:
227  return d->m_lat;
228  case Degree:
229  return d->m_lat * RAD2DEG;
230  }
231 }
232 
233 qreal GeoDataCoordinates::latitude() const
234 {
235  return d->m_lat;
236 }
237 
238 //static
240 {
241  return s_notation;
242 }
243 
244 //static
246 {
247  s_notation = notation;
248 }
249 
250 //static
252 {
253  qreal halfCircle;
254  if ( unit == GeoDataCoordinates::Radian ) {
255  halfCircle = M_PI;
256  }
257  else {
258  halfCircle = 180;
259  }
260 
261  if ( lon > halfCircle ) {
262  int cycles = (int)( ( lon + halfCircle ) / ( 2 * halfCircle ) );
263  return lon - ( cycles * 2 * halfCircle );
264  }
265  if ( lon < -halfCircle ) {
266  int cycles = (int)( ( lon - halfCircle ) / ( 2 * halfCircle ) );
267  return lon - ( cycles * 2 * halfCircle );
268  }
269 
270  return lon;
271 }
272 
273 //static
275 {
276  qreal halfCircle;
277  if ( unit == GeoDataCoordinates::Radian ) {
278  halfCircle = M_PI;
279  }
280  else {
281  halfCircle = 180;
282  }
283 
284  if ( lat > ( halfCircle / 2.0 ) ) {
285  int cycles = (int)( ( lat + halfCircle ) / ( 2 * halfCircle ) );
286  qreal temp;
287  if( cycles == 0 ) { // pi/2 < lat < pi
288  temp = halfCircle - lat;
289  } else {
290  temp = lat - ( cycles * 2 * halfCircle );
291  }
292  if ( temp > ( halfCircle / 2.0 ) ) {
293  return ( halfCircle - temp );
294  }
295  if ( temp < ( -halfCircle / 2.0 ) ) {
296  return ( -halfCircle - temp );
297  }
298  return temp;
299  }
300  if ( lat < ( -halfCircle / 2.0 ) ) {
301  int cycles = (int)( ( lat - halfCircle ) / ( 2 * halfCircle ) );
302  qreal temp;
303  if( cycles == 0 ) {
304  temp = -halfCircle - lat;
305  } else {
306  temp = lat - ( cycles * 2 * halfCircle );
307  }
308  if ( temp > ( +halfCircle / 2.0 ) ) {
309  return ( +halfCircle - temp );
310  }
311  if ( temp < ( -halfCircle / 2.0 ) ) {
312  return ( -halfCircle - temp );
313  }
314  return temp;
315  }
316  return lat;
317 }
318 
319 //static
321 {
322  qreal halfCircle;
323  if ( unit == GeoDataCoordinates::Radian ) {
324  halfCircle = M_PI;
325  }
326  else {
327  halfCircle = 180;
328  }
329 
330  if ( lon > +halfCircle ) {
331  int cycles = (int)( ( lon + halfCircle ) / ( 2 * halfCircle ) );
332  lon = lon - ( cycles * 2 * halfCircle );
333  }
334  if ( lon < -halfCircle ) {
335  int cycles = (int)( ( lon - halfCircle ) / ( 2 * halfCircle ) );
336  lon = lon - ( cycles * 2 * halfCircle );
337  }
338 
339  if ( lat > ( +halfCircle / 2.0 ) ) {
340  int cycles = (int)( ( lat + halfCircle ) / ( 2 * halfCircle ) );
341  qreal temp;
342  if( cycles == 0 ) { // pi/2 < lat < pi
343  temp = halfCircle - lat;
344  } else {
345  temp = lat - ( cycles * 2 * halfCircle );
346  }
347  if ( temp > ( +halfCircle / 2.0 ) ) {
348  lat = +halfCircle - temp;
349  }
350  if ( temp < ( -halfCircle / 2.0 ) ) {
351  lat = -halfCircle - temp;
352  }
353  lat = temp;
354  if( lon > 0 ) {
355  lon = -halfCircle + lon;
356  } else {
357  lon = halfCircle + lon;
358  }
359  }
360  if ( lat < ( -halfCircle / 2.0 ) ) {
361  int cycles = (int)( ( lat - halfCircle ) / ( 2 * halfCircle ) );
362  qreal temp;
363  if( cycles == 0 ) {
364  temp = -halfCircle - lat;
365  } else {
366  temp = lat - ( cycles * 2 * halfCircle );
367  }
368  if ( temp > ( +halfCircle / 2.0 ) ) {
369  lat = +halfCircle - temp;
370  }
371  if ( temp < ( -halfCircle / 2.0 ) ) {
372  lat = -halfCircle - temp;
373  }
374  lat = temp;
375  if( lon > 0 ) {
376  lon = -halfCircle + lon;
377  } else {
378  lon = halfCircle + lon;
379  }
380  }
381  return;
382 }
383 
385 {
386  LonLatParser parser;
387  successful = parser.parse(string);
388  if (successful) {
389  return GeoDataCoordinates( parser.lon(), parser.lat(), 0, GeoDataCoordinates::Degree );
390  } else {
391  return GeoDataCoordinates();
392  }
393 }
394 
395 
397 {
398  return GeoDataCoordinates::toString( s_notation );
399 }
400 
402 {
403  QString coordString;
404 
405  if( notation == GeoDataCoordinates::UTM ){
406  int zoneNumber = GeoDataCoordinatesPrivate::lonLatToZone(d->m_lon, d->m_lat);
407 
408  // Handle lack of UTM zone number in the poles
409  const QString zoneString = (zoneNumber > 0) ? QString::number(zoneNumber) : QString();
410 
411  QString bandString = GeoDataCoordinatesPrivate::lonLatToLatitudeBand(d->m_lon, d->m_lat);
412 
413  QString eastingString = QString::number(GeoDataCoordinatesPrivate::lonLatToEasting(d->m_lon, d->m_lat), 'f', 2);
414  QString northingString = QString::number(GeoDataCoordinatesPrivate::lonLatToNorthing(d->m_lon, d->m_lat), 'f', 2);
415 
416  return QString("%1%2 %3 m E, %4 m N").arg(zoneString, bandString, eastingString, northingString);
417  }
418  else{
419  coordString = lonToString( d->m_lon, notation, Radian, precision )
420  + QLatin1String(", ")
421  + latToString( d->m_lat, notation, Radian, precision );
422  }
423 
424  return coordString;
425 }
426 
429  int precision,
430  char format )
431 {
432  if( notation == GeoDataCoordinates::UTM ){
440  qreal lonRad = ( unit == Radian ) ? lon : lon * DEG2RAD;
441 
442  int zoneNumber = GeoDataCoordinatesPrivate::lonLatToZone(lonRad, 0);
443 
444  // Handle lack of UTM zone number in the poles
445  QString result = (zoneNumber > 0) ? QString::number(zoneNumber) : QString();
446 
447  if(precision > 0){
448  QString eastingString = QString::number( GeoDataCoordinatesPrivate::lonLatToEasting(lonRad, 0), 'f', 2 );
449  result += QString(" %1 m E").arg(eastingString);
450  }
451 
452  return result;
453  }
454 
455  QString weString = ( lon < 0 ) ? tr("W") : tr("E");
456 
457  QString lonString;
458 
459  qreal lonDegF = ( unit == Degree ) ? fabs( lon ) : fabs( (qreal)(lon) * RAD2DEG );
460 
461  // Take care of -1 case
462  precision = ( precision < 0 ) ? 5 : precision;
463 
464  if ( notation == DMS || notation == DM ) {
465  int lonDeg = (int) lonDegF;
466  qreal lonMinF = 60 * (lonDegF - lonDeg);
467  int lonMin = (int) lonMinF;
468  qreal lonSecF = 60 * (lonMinF - lonMin);
469  int lonSec = (int) lonSecF;
470 
471  // Adjustment for fuzziness (like 49.999999999999999999999)
472  if ( precision == 0 ) {
473  lonDeg = qRound( lonDegF );
474  } else if ( precision <= 2 ) {
475  lonMin = qRound( lonMinF );
476  } else if ( precision <= 4 && notation == DMS ) {
477  lonSec = qRound( lonSecF );
478  } else {
479  if ( notation == DMS ) {
480  lonSec = lonSecF = qRound( lonSecF * qPow( 10, precision - 4 ) ) / qPow( 10, precision - 4 );
481  }
482  else {
483  lonMin = lonMinF = qRound( lonMinF * qPow( 10, precision - 2 ) ) / qPow( 10, precision - 2 );
484  }
485  }
486 
487  if (lonSec > 59 && notation == DMS ) {
488  lonSec = lonSecF = 0;
489  lonMin = lonMinF = lonMinF + 1;
490  }
491  if (lonMin > 59) {
492  lonMin = lonMinF = 0;
493  lonDeg = lonDegF = lonDegF + 1;
494  }
495 
496  // Evaluate the string
497  lonString = QString::fromUtf8("%1\xc2\xb0").arg(lonDeg, 3, 10, QLatin1Char(' '));
498 
499  if ( precision == 0 ) {
500  return lonString + weString;
501  }
502 
503  if ( notation == DMS || precision < 3 ) {
504  lonString += QString(" %2\'").arg(lonMin, 2, 10, QLatin1Char('0'));
505  }
506 
507  if ( precision < 3 ) {
508  return lonString + weString;
509  }
510 
511  if ( notation == DMS ) {
512  // Includes -1 case!
513  if ( precision < 5 ) {
514  lonString += QString(" %3\"").arg(lonSec, 2, 'f', 0, QLatin1Char('0'));
515  return lonString + weString;
516  }
517 
518  lonString += QString(" %L3\"").arg(lonSecF, precision - 1, 'f', precision - 4, QLatin1Char('0'));
519  }
520  else {
521  lonString += QString(" %L3'").arg(lonMinF, precision + 1, 'f', precision - 2, QLatin1Char('0'));
522  }
523  }
524  else if ( notation == GeoDataCoordinates::Decimal )
525  {
526  lonString = QString::fromUtf8("%L1\xc2\xb0").arg(lonDegF, 4 + precision, format, precision, QLatin1Char(' '));
527  }
528  else if ( notation == GeoDataCoordinates::Astro )
529  {
530  if (lon < 0) {
531  lon += ( unit == Degree ) ? 360 : 2 * M_PI;
532  }
533 
534  qreal lonHourF = ( unit == Degree ) ? fabs( lon/15.0 ) : fabs( (qreal)(lon/15.0) * RAD2DEG );
535  int lonHour = (int) lonHourF;
536  qreal lonMinF = 60 * (lonHourF - lonHour);
537  int lonMin = (int) lonMinF;
538  qreal lonSecF = 60 * (lonMinF - lonMin);
539  int lonSec = (int) lonSecF;
540 
541  // Adjustment for fuzziness (like 49.999999999999999999999)
542  if ( precision == 0 ) {
543  lonHour = qRound( lonHourF );
544  } else if ( precision <= 2 ) {
545  lonMin = qRound( lonMinF );
546  } else if ( precision <= 4 ) {
547  lonSec = qRound( lonSecF );
548  } else {
549  lonSec = lonSecF = qRound( lonSecF * qPow( 10, precision - 4 ) ) / qPow( 10, precision - 4 );
550  }
551 
552  if (lonSec > 59 ) {
553  lonSec = lonSecF = 0;
554  lonMin = lonMinF = lonMinF + 1;
555  }
556  if (lonMin > 59) {
557  lonMin = lonMinF = 0;
558  lonHour = lonHourF = lonHourF + 1;
559  }
560 
561  // Evaluate the string
562  lonString = QString::fromUtf8("%1h").arg(lonHour, 3, 10, QLatin1Char(' '));
563 
564  if ( precision == 0 ) {
565  return lonString;
566  }
567 
568  lonString += QString(" %2\'").arg(lonMin, 2, 10, QLatin1Char('0'));
569 
570  if ( precision < 3 ) {
571  return lonString;
572  }
573 
574  // Includes -1 case!
575  if ( precision < 5 ) {
576  lonString += QString(" %3\"").arg(lonSec, 2, 'f', 0, QLatin1Char('0'));
577  return lonString;
578  }
579 
580  lonString += QString(" %L3\"").arg(lonSecF, precision - 1, 'f', precision - 4, QLatin1Char('0'));
581  return lonString;
582  }
583 
584  return lonString + weString;
585 }
586 
588 {
589  return GeoDataCoordinates::lonToString( d->m_lon , s_notation );
590 }
591 
594  int precision,
595  char format )
596 {
597  if( notation == GeoDataCoordinates::UTM ){
605  qreal latRad = ( unit == Radian ) ? lat : lat * DEG2RAD;
606 
607  QString result = GeoDataCoordinatesPrivate::lonLatToLatitudeBand(0, latRad);
608 
609  if ( precision > 0 ){
610  QString northingString = QString::number( GeoDataCoordinatesPrivate::lonLatToNorthing(0, latRad), 'f', 2 );
611  result += QString(" %1 m N").arg(northingString);
612  }
613 
614  return result;
615  }
616 
617  QString pmString;
618  QString nsString;
619 
620  if (notation == GeoDataCoordinates::Astro){
621  pmString = ( lat > 0 ) ? "+" : "-";
622  }
623  else {
624  nsString = ( lat > 0 ) ? tr("N") : tr("S");
625  }
626 
627  QString latString;
628 
629  qreal latDegF = ( unit == Degree ) ? fabs( lat ) : fabs( (qreal)(lat) * RAD2DEG );
630 
631  // Take care of -1 case
632  precision = ( precision < 0 ) ? 5 : precision;
633 
634  if ( notation == DMS || notation == DM || notation == Astro) {
635  int latDeg = (int) latDegF;
636  qreal latMinF = 60 * (latDegF - latDeg);
637  int latMin = (int) latMinF;
638  qreal latSecF = 60 * (latMinF - latMin);
639  int latSec = (int) latSecF;
640 
641  // Adjustment for fuzziness (like 49.999999999999999999999)
642  if ( precision == 0 ) {
643  latDeg = qRound( latDegF );
644  } else if ( precision <= 2 ) {
645  latMin = qRound( latMinF );
646  } else if ( precision <= 4 && notation == DMS ) {
647  latSec = qRound( latSecF );
648  } else {
649  if ( notation == DMS || notation == Astro ) {
650  latSec = latSecF = qRound( latSecF * qPow( 10, precision - 4 ) ) / qPow( 10, precision - 4 );
651  }
652  else {
653  latMin = latMinF = qRound( latMinF * qPow( 10, precision - 2 ) ) / qPow( 10, precision - 2 );
654  }
655  }
656 
657  if (latSec > 59 && ( notation == DMS || notation == Astro )) {
658  latSecF = 0;
659  latSec = latSecF;
660  latMin = latMin + 1;
661  }
662  if (latMin > 59) {
663  latMinF = 0;
664  latMin = latMinF;
665  latDeg = latDeg + 1;
666  }
667 
668  // Evaluate the string
669  latString = QString::fromUtf8("%1\xc2\xb0").arg(latDeg, 3, 10, QLatin1Char(' '));
670 
671  if ( precision == 0 ) {
672  return pmString + latString + nsString;
673  }
674 
675  if ( notation == DMS || notation == Astro || precision < 3 ) {
676  latString += QString(" %2\'").arg(latMin, 2, 10, QLatin1Char('0'));
677  }
678 
679  if ( precision < 3 ) {
680  return pmString + latString + nsString;
681  }
682 
683  if ( notation == DMS || notation == Astro ) {
684  // Includes -1 case!
685  if ( precision < 5 ) {
686  latString += QString(" %3\"").arg(latSec, 2, 'f', 0, QLatin1Char('0'));
687  return latString + nsString;
688  }
689 
690  latString += QString(" %L3\"").arg(latSecF, precision - 1, 'f', precision - 4, QLatin1Char('0'));
691  }
692  else {
693  latString += QString(" %L3'").arg(latMinF, precision + 1, 'f', precision - 2, QLatin1Char('0'));
694  }
695  }
696  else // notation = GeoDataCoordinates::Decimal
697  {
698  latString = QString::fromUtf8("%L1\xc2\xb0").arg(latDegF, 4 + precision, format, precision, QLatin1Char(' '));
699  }
700  return pmString + latString + nsString;
701 }
702 
704 {
705  return GeoDataCoordinates::latToString( d->m_lat, s_notation );
706 }
707 
708 bool GeoDataCoordinates::operator==( const GeoDataCoordinates &rhs ) const
709 {
710  return *d == *rhs.d;
711 }
712 
713 bool GeoDataCoordinates::operator!=( const GeoDataCoordinates &rhs ) const
714 {
715  return *d != *rhs.d;
716 }
717 
719 {
720  detach();
721  d->m_altitude = altitude;
722 }
723 
725 {
726  return d->m_altitude;
727 }
728 
730  return GeoDataCoordinatesPrivate::lonLatToZone(d->m_lon, d->m_lat);
731 }
732 
734  return GeoDataCoordinatesPrivate::lonLatToEasting(d->m_lon, d->m_lat);
735 }
736 
738  return GeoDataCoordinatesPrivate::lonLatToLatitudeBand(d->m_lon, d->m_lat);
739 }
740 
742  return GeoDataCoordinatesPrivate::lonLatToNorthing(d->m_lon, d->m_lat);
743 }
744 
746 {
747  return d->m_detail;
748 }
749 
751 {
752  detach();
753  d->m_detail = detail;
754 }
755 
757 {
758  const Quaternion quatAxis = Quaternion::fromEuler( -axis.latitude() , axis.longitude(), 0 );
759  const Quaternion rotationAmount = Quaternion::fromEuler( 0, 0, unit == Radian ? angle : angle * DEG2RAD );
760  const Quaternion resultAxis = quatAxis * rotationAmount * quatAxis.inverse();
761 
762  return rotateAround(resultAxis);
763 }
764 
765 GeoDataCoordinates GeoDataCoordinates::rotateAround(const Quaternion &rotAxis) const
766 {
767  Quaternion rotatedQuat = quaternion();
768  rotatedQuat.rotateAroundAxis(rotAxis);
769  qreal rotatedLon, rotatedLat;
770  rotatedQuat.getSpherical(rotatedLon, rotatedLat);
771  return GeoDataCoordinates(rotatedLon, rotatedLat, altitude());
772 }
773 
774 qreal GeoDataCoordinates::bearing( const GeoDataCoordinates &other, Unit unit, BearingType type ) const
775 {
776  if ( type == FinalBearing ) {
777  double const offset = unit == Degree ? 180.0 : M_PI;
778  return offset + other.bearing( *this, unit, InitialBearing );
779  }
780 
781  qreal const delta = other.d->m_lon - d->m_lon;
782  double const bearing = atan2( sin ( delta ) * cos ( other.d->m_lat ),
783  cos( d->m_lat ) * sin( other.d->m_lat ) - sin( d->m_lat ) * cos( other.d->m_lat ) * cos ( delta ) );
784  return unit == Radian ? bearing : bearing * RAD2DEG;
785 }
786 
788 {
789  qreal newLat = asin( sin(d->m_lat) * cos(distance) +
790  cos(d->m_lat) * sin(distance) * cos(bearing) );
791  qreal newLon = d->m_lon + atan2( sin(bearing) * sin(distance) * cos(d->m_lat),
792  cos(distance) - sin(d->m_lat) * sin(newLat) );
793 
794  return GeoDataCoordinates( newLon, newLat );
795 }
796 
797 const Quaternion& GeoDataCoordinates::quaternion() const
798 {
799  if (d->m_q == nullptr) {
800  d->m_q = new Quaternion(Quaternion::fromSpherical( d->m_lon , d->m_lat ));
801  }
802  return *d->m_q;
803 }
804 
806 {
807  double const t = qBound( 0.0, t_, 1.0 );
808  Quaternion const quat = Quaternion::slerp( quaternion(), target.quaternion(), t );
809  qreal lon, lat;
810  quat.getSpherical( lon, lat );
811  double const alt = (1.0-t) * d->m_altitude + t * target.d->m_altitude;
812  return GeoDataCoordinates( lon, lat, alt );
813 }
814 
816 {
817  qreal lon = 0.0;
818  qreal lat = 0.0;
819 
820  const Quaternion itpos = Quaternion::nlerp(quaternion(), target.quaternion(), t);
821  itpos.getSpherical(lon, lat);
822 
823  const qreal altitude = 0.5 * (d->m_altitude + target.altitude());
824 
825  return GeoDataCoordinates(lon, lat, altitude);
826 }
827 
829 {
830  double const t = qBound( 0.0, t_, 1.0 );
831  Quaternion const b1 = GeoDataCoordinatesPrivate::basePoint( before.quaternion(), quaternion(), target.quaternion() );
832  Quaternion const a2 = GeoDataCoordinatesPrivate::basePoint( quaternion(), target.quaternion(), after.quaternion() );
833  Quaternion const a = Quaternion::slerp( quaternion(), target.quaternion(), t );
834  Quaternion const b = Quaternion::slerp( b1, a2, t );
835  Quaternion c = Quaternion::slerp( a, b, 2 * t * (1.0-t) );
836  qreal lon, lat;
837  c.getSpherical( lon, lat );
838  // @todo spline interpolation of altitude?
839  double const alt = (1.0-t) * d->m_altitude + t * target.d->m_altitude;
840  return GeoDataCoordinates( lon, lat, alt );
841 }
842 
844 {
845  // Evaluate the most likely case first:
846  // The case where we haven't hit the pole and where our latitude is normalized
847  // to the range of 90 deg S ... 90 deg N
848  if ( fabs( (qreal) 2.0 * d->m_lat ) < M_PI ) {
849  return false;
850  }
851  else {
852  if ( fabs( (qreal) 2.0 * d->m_lat ) == M_PI ) {
853  // Ok, we have hit a pole. Now let's check whether it's the one we've asked for:
854  if ( pole == AnyPole ){
855  return true;
856  }
857  else {
858  if ( pole == NorthPole && 2.0 * d->m_lat == +M_PI ) {
859  return true;
860  }
861  if ( pole == SouthPole && 2.0 * d->m_lat == -M_PI ) {
862  return true;
863  }
864  return false;
865  }
866  }
867  //
868  else {
869  // FIXME: Should we just normalize latitude and longitude and be done?
870  // While this might work well for persistent data it would create some
871  // possible overhead for temporary data, so this needs careful thinking.
872  mDebug() << "GeoDataCoordinates not normalized!";
873 
874  // Only as a last resort we cover the unlikely case where
875  // the latitude is not normalized to the range of
876  // 90 deg S ... 90 deg N
877  if ( fabs( (qreal) 2.0 * normalizeLat( d->m_lat ) ) < M_PI ) {
878  return false;
879  }
880  else {
881  // Ok, we have hit a pole. Now let's check whether it's the one we've asked for:
882  if ( pole == AnyPole ){
883  return true;
884  }
885  else {
886  if ( pole == NorthPole && 2.0 * d->m_lat == +M_PI ) {
887  return true;
888  }
889  if ( pole == SouthPole && 2.0 * d->m_lat == -M_PI ) {
890  return true;
891  }
892  return false;
893  }
894  }
895  }
896  }
897 }
898 
900 {
901  qreal lon2, lat2;
902  other.geoCoordinates( lon2, lat2 );
903 
904  // FIXME: Take the altitude into account!
905 
906  return distanceSphere(d->m_lon, d->m_lat, lon2, lat2);
907 }
908 
909 GeoDataCoordinates& GeoDataCoordinates::operator=( const GeoDataCoordinates &other )
910 {
911  qAtomicAssign(d, other.d);
912  return *this;
913 }
914 
916 {
917  stream << d->m_lon;
918  stream << d->m_lat;
919  stream << d->m_altitude;
920 }
921 
923 {
924  // call detach even though it shouldn't be needed - one never knows
925  detach();
926  stream >> d->m_lon;
927  stream >> d->m_lat;
928  stream >> d->m_altitude;
929 }
930 
931 Quaternion GeoDataCoordinatesPrivate::basePoint( const Quaternion &q1, const Quaternion &q2, const Quaternion &q3 )
932 {
933  Quaternion const a = (q2.inverse() * q3).log();
934  Quaternion const b = (q2.inverse() * q1).log();
935  return q2 * ((a+b)*-0.25).exp();
936 }
937 
938 
939 
940 qreal GeoDataCoordinatesPrivate::arcLengthOfMeridian( qreal phi )
941 {
942  // Precalculate n
943  qreal const n = (GeoDataCoordinatesPrivate::sm_semiMajorAxis - GeoDataCoordinatesPrivate::sm_semiMinorAxis)
944  / (GeoDataCoordinatesPrivate::sm_semiMajorAxis + GeoDataCoordinatesPrivate::sm_semiMinorAxis);
945 
946  // Precalculate alpha
947  qreal const alpha = ( (GeoDataCoordinatesPrivate::sm_semiMajorAxis + GeoDataCoordinatesPrivate::sm_semiMinorAxis) / 2.0)
948  * (1.0 + (qPow (n, 2.0) / 4.0) + (qPow (n, 4.0) / 64.0) );
949 
950  // Precalculate beta
951  qreal const beta = (-3.0 * n / 2.0)
952  + (9.0 * qPow (n, 3.0) / 16.0)
953  + (-3.0 * qPow (n, 5.0) / 32.0);
954 
955  // Precalculate gamma
956  qreal const gamma = (15.0 * qPow (n, 2.0) / 16.0)
957  + (-15.0 * qPow (n, 4.0) / 32.0);
958 
959  // Precalculate delta
960  qreal const delta = (-35.0 * qPow (n, 3.0) / 48.0)
961  + (105.0 * qPow (n, 5.0) / 256.0);
962 
963  // Precalculate epsilon
964  qreal const epsilon = (315.0 * qPow (n, 4.0) / 512.0);
965 
966  // Now calculate the sum of the series and return
967  qreal const result = alpha * (phi + (beta * qSin (2.0 * phi))
968  + (gamma * qSin (4.0 * phi))
969  + (delta * qSin (6.0 * phi))
970  + (epsilon * qSin (8.0 * phi)));
971 
972  return result;
973 }
974 
975 qreal GeoDataCoordinatesPrivate::centralMeridianUTM( qreal zone )
976 {
977  return DEG2RAD*(-183.0 + (zone * 6.0));
978 }
979 
980 qreal GeoDataCoordinatesPrivate::footpointLatitude( qreal northing )
981 {
982  // Precalculate n (Eq. 10.18)
983  qreal const n = (GeoDataCoordinatesPrivate::sm_semiMajorAxis - GeoDataCoordinatesPrivate::sm_semiMinorAxis)
984  / (GeoDataCoordinatesPrivate::sm_semiMajorAxis + GeoDataCoordinatesPrivate::sm_semiMinorAxis);
985 
986  // Precalculate alpha (Eq. 10.22)
987  // (Same as alpha in Eq. 10.17)
988  qreal const alpha = ((GeoDataCoordinatesPrivate::sm_semiMajorAxis + GeoDataCoordinatesPrivate::sm_semiMinorAxis) / 2.0)
989  * (1 + (qPow (n, 2.0) / 4) + (qPow (n, 4.0) / 64));
990 
991  // Precalculate y (Eq. 10.23)
992  qreal const y = northing / alpha;
993 
994  // Precalculate beta (Eq. 10.22)
995  qreal const beta = (3.0 * n / 2.0) + (-27.0 * qPow (n, 3.0) / 32.0)
996  + (269.0 * qPow (n, 5.0) / 512.0);
997 
998  // Precalculate gamma (Eq. 10.22)
999  qreal const gamma = (21.0 * qPow (n, 2.0) / 16.0)
1000  + (-55.0 * qPow (n, 4.0) / 32.0);
1001 
1002  // Precalculate delta (Eq. 10.22)
1003  qreal const delta = (151.0 * qPow (n, 3.0) / 96.0)
1004  + (-417.0 * qPow (n, 5.0) / 128.0);
1005 
1006  // Precalculate epsilon (Eq. 10.22)
1007  qreal const epsilon = (1097.0 * qPow (n, 4.0) / 512.0);
1008 
1009  // Now calculate the sum of the series (Eq. 10.21)
1010  qreal const result = y + (beta * qSin (2.0 * y))
1011  + (gamma * qSin (4.0 * y))
1012  + (delta * qSin (6.0 * y))
1013  + (epsilon * qSin (8.0 * y));
1014 
1015  return result;
1016 }
1017 
1018 QPointF GeoDataCoordinatesPrivate::mapLonLatToXY( qreal lambda, qreal phi, qreal lambda0 )
1019 {
1020  // Equation (10.15)
1021 
1022  // Precalculate second numerical eccentricity
1023  const qreal ep2 = (qPow(GeoDataCoordinatesPrivate::sm_semiMajorAxis, 2.0) - qPow(GeoDataCoordinatesPrivate::sm_semiMinorAxis, 2.0))
1024  / qPow(GeoDataCoordinatesPrivate::sm_semiMinorAxis, 2.0);
1025 
1026  // Precalculate the square of nu, just an auxilar quantity
1027  const qreal nu2 = ep2 * qPow(qCos(phi), 2.0);
1028 
1029  // Precalculate the radius of curvature in prime vertical
1030  const qreal N = qPow(GeoDataCoordinatesPrivate::sm_semiMajorAxis, 2.0) / (GeoDataCoordinatesPrivate::sm_semiMinorAxis * qSqrt(1 + nu2));
1031 
1032  // Precalculate the tangent of phi and its square
1033  const qreal t = qTan(phi);
1034  const qreal t2 = t * t;
1035 
1036  // Precalculate longitude difference
1037  const qreal l = lambda - lambda0;
1038 
1039  /*
1040  * Precalculate coefficients for l**n in the equations below
1041  * so a normal human being can read the expressions for easting
1042  * and northing
1043  * -- l**1 and l**2 have coefficients of 1.0
1044  *
1045  * The actual used coefficients starts at coef[1], just to
1046  * follow the meaningful nomenclature in equation 10.15
1047  * (coef[n] corresponds to qPow(l,n) factor)
1048  */
1049 
1050  const qreal coef1 = 1;
1051  const qreal coef2 = 1;
1052  const qreal coef3 = 1.0 - t2 + nu2;
1053  const qreal coef4 = 5.0 - t2 + 9 * nu2 + 4.0 * (nu2 * nu2);
1054  const qreal coef5 = 5.0 - 18.0 * t2 + (t2 * t2) + 14.0 * nu2 - 58.0 * t2 * nu2;
1055  const qreal coef6 = 61.0 - 58.0 * t2 + (t2 * t2) + 270.0 * nu2 - 330.0 * t2 * nu2;
1056  const qreal coef7 = 61.0 - 479.0 * t2 + 179.0 * (t2 * t2) - (t2 * t2 * t2);
1057  const qreal coef8 = 1385.0 - 3111.0 * t2 + 543.0 * (t2 * t2) - (t2 * t2 * t2);
1058 
1059  // Calculate easting (x)
1060  const qreal easting = N * qCos(phi) * coef1 * l
1061  + (N / 6.0 * qPow(qCos(phi), 3.0) * coef3 * qPow(l, 3.0))
1062  + (N / 120.0 * qPow(qCos(phi), 5.0) * coef5 * qPow(l, 5.0))
1063  + (N / 5040.0 * qPow(qCos(phi), 7.0) * coef7 * qPow(l, 7.0));
1064 
1065  // Calculate northing (y)
1066  const qreal northing = arcLengthOfMeridian (phi)
1067  + (t / 2.0 * N * qPow(qCos(phi), 2.0) * coef2 * qPow(l, 2.0))
1068  + (t / 24.0 * N * qPow(qCos(phi), 4.0) * coef4 * qPow(l, 4.0))
1069  + (t / 720.0 * N * qPow(qCos(phi), 6.0) * coef6 * qPow(l, 6.0))
1070  + (t / 40320.0 * N * qPow(qCos(phi), 8.0) * coef8 * qPow(l, 8.0));
1071 
1072  return QPointF(easting, northing);
1073 }
1074 
1075 int GeoDataCoordinatesPrivate::lonLatToZone( qreal lon, qreal lat ){
1076  // Converts lon and lat to degrees
1077  qreal lonDeg = lon * RAD2DEG;
1078  qreal latDeg = lat * RAD2DEG;
1079 
1080  /* Round the value of the longitude when the distance to the nearest integer
1081  * is less than 0.0000001. This avoids fuzzy values such as -114.0000000001, which
1082  * can produce a misbehaviour when calculating the zone associated at the borders
1083  * of the zone intervals (for example, the interval [-114, -108[ is associated with
1084  * zone number 12; if the following rounding is not done, the value returned by
1085  * lonLatToZone(114,0) is 11 instead of 12, as the function actually receives
1086  * -114.0000000001, which is in the interval [-120,-114[, associated to zone 11
1087  */
1088  qreal precision = 0.0000001;
1089 
1090  if ( qAbs(lonDeg - qFloor(lonDeg)) < precision || qAbs(lonDeg - qCeil(lonDeg)) < precision ){
1091  lonDeg = qRound(lonDeg);
1092  }
1093 
1094  // There is no numbering associated to the poles, special value 0 is returned.
1095  if ( latDeg < -80 || latDeg > 84 ) {
1096  return 0;
1097  }
1098 
1099  // Obtains the zone number handling all the so called "exceptions"
1100  // See problem: https://en.wikipedia.org/wiki/Universal_Transverse_Mercator_coordinate_system#Exceptions
1101  // See solution: https://gis.stackexchange.com/questions/13291/computing-utm-zone-from-lat-long-point
1102 
1103  // General
1104  int zoneNumber = static_cast<int>( (lonDeg+180) / 6.0 ) + 1;
1105 
1106  // Southwest Norway
1107  if ( latDeg >= 56 && latDeg < 64 && lonDeg >= 3 && lonDeg < 12 ) {
1108  zoneNumber = 32;
1109  }
1110 
1111  // Svalbard
1112  if ( latDeg >= 72 && latDeg < 84 ) {
1113  if ( lonDeg >= 0 && lonDeg < 9 ) {
1114  zoneNumber = 31;
1115  } else if ( lonDeg >= 9 && lonDeg < 21 ) {
1116  zoneNumber = 33;
1117  } else if ( lonDeg >= 21 && lonDeg < 33 ) {
1118  zoneNumber = 35;
1119  } else if ( lonDeg >= 33 && lonDeg < 42 ) {
1120  zoneNumber = 37;
1121  }
1122  }
1123 
1124  return zoneNumber;
1125 }
1126 
1127 qreal GeoDataCoordinatesPrivate::lonLatToEasting( qreal lon, qreal lat ){
1128  int zoneNumber = lonLatToZone( lon, lat );
1129 
1130  if ( zoneNumber == 0 ){
1131  qreal lonDeg = lon * RAD2DEG;
1132  zoneNumber = static_cast<int>( (lonDeg+180) / 6.0 ) + 1;
1133  }
1134 
1135  QPointF coordinates = GeoDataCoordinatesPrivate::mapLonLatToXY( lon, lat, GeoDataCoordinatesPrivate::centralMeridianUTM(zoneNumber) );
1136 
1137  // Adjust easting and northing for UTM system
1138  qreal easting = coordinates.x() * GeoDataCoordinatesPrivate::sm_utmScaleFactor + 500000.0;
1139 
1140  return easting;
1141 }
1142 
1143 QString GeoDataCoordinatesPrivate::lonLatToLatitudeBand( qreal lon, qreal lat ){
1144  // Obtains the latitude bands handling all the so called "exceptions"
1145 
1146  // Converts lon and lat to degrees
1147  qreal lonDeg = lon * RAD2DEG;
1148  qreal latDeg = lat * RAD2DEG;
1149 
1150  // Regular latitude bands between 80 S and 80 N (that is, between 10 and 170 in the [0,180] interval)
1151  int bandLetterIndex = 24; //Avoids "may be used uninitialized" warning
1152 
1153  if ( latDeg < -80 ) {
1154  // South pole (A for zones 1-30, B for zones 31-60)
1155  bandLetterIndex = ( (lonDeg+180) < 6*31 ) ? 0 : 1;
1156  } else if ( latDeg >= -80 && latDeg <= 80 ) {
1157  // General (+2 because the general lettering starts in C)
1158  bandLetterIndex = static_cast<int>( (latDeg+80.0) / 8.0 ) + 2;
1159  } else if ( latDeg >= 80 && latDeg < 84 ) {
1160  // Band X is extended 4 more degrees
1161  bandLetterIndex = 21;
1162  } else if ( latDeg >= 84 ) {
1163  // North pole (Y for zones 1-30, Z for zones 31-60)
1164  bandLetterIndex = ((lonDeg+180) < 6*31) ? 22 : 23;
1165  }
1166 
1167  return QString( "ABCDEFGHJKLMNPQRSTUVWXYZ?" ).at( bandLetterIndex );
1168 }
1169 
1170 qreal GeoDataCoordinatesPrivate::lonLatToNorthing( qreal lon, qreal lat ){
1171  int zoneNumber = lonLatToZone( lon, lat );
1172 
1173  if ( zoneNumber == 0 ){
1174  qreal lonDeg = lon * RAD2DEG;
1175  zoneNumber = static_cast<int>( (lonDeg+180) / 6.0 ) + 1;
1176  }
1177 
1178  QPointF coordinates = GeoDataCoordinatesPrivate::mapLonLatToXY( lon, lat, GeoDataCoordinatesPrivate::centralMeridianUTM(zoneNumber) );
1179 
1180  qreal northing = coordinates.y() * GeoDataCoordinatesPrivate::sm_utmScaleFactor;
1181 
1182  if ( northing < 0.0 ) {
1183  northing += 10000000.0;
1184  }
1185 
1186  return northing;
1187 }
1188 
1189 uint qHash(const GeoDataCoordinates &coordinates)
1190 {
1191  uint seed = ::qHash(coordinates.altitude());
1192  seed = ::qHash(coordinates.latitude(), seed);
1193 
1194  return ::qHash(coordinates.longitude(), seed);
1195 }
1196 
1197 }
Unit
enum used constructor to specify the units used
void unpack(QDataStream &stream)
Unserialize the contents of the feature from stream.
Any pole.
Definition: MarbleGlobal.h:144
A 3d point representation.
Only South Pole.
Definition: MarbleGlobal.h:146
GeoDataCoordinates moveByBearing(qreal bearing, qreal distance) const
Returns the coordinates of the resulting point after moving this point according to the distance and ...
void setDetail(quint8 detail)
set the detail flag
void setAltitude(const qreal altitude)
set the altitude of the Point in meters
static qreal normalizeLon(qreal lon, GeoDataCoordinates::Unit=GeoDataCoordinates::Radian)
normalize the longitude to always be -M_PI <= lon <= +M_PI (Radian).
Binds a QML item to a specific geodetic location in screen coordinates.
GeoDataCoordinates()
constructs an invalid instance
"Decimal" notation (base-10)
qreal distanceSphere(qreal lon1, qreal lat1, qreal lon2, qreal lat2)
This method calculates the shortest distance between two points on a sphere.
Definition: MarbleMath.h:51
qreal utmEasting() const
retrieves the UTM easting of the GeoDataCoordinates object, in meters.
qreal sphericalDistanceTo(const GeoDataCoordinates &other) const
This method calculates the shortest distance between two points on a sphere.
BearingType
The BearingType enum specifies where to measure the bearing along great circle arcs.
qreal altitude() const
return the altitude of the Point in meters
QString number(int n, int base)
qreal x() const const
qreal y() const const
GeoDataCoordinates rotateAround(const GeoDataCoordinates &axis, qreal angle, Unit unit=Radian) const
Rotates one coordinate around another.
QString fromUtf8(const char *str, int size)
quint8 detail() const
return the detail flag detail range: 0 for most important points, 5 for least important ...
GeoDataCoordinates nlerp(const GeoDataCoordinates &target, double t) const
nlerp (normalized linear interpolation) between this coordinates and the given target coordinates ...
qreal bearing(const GeoDataCoordinates &other, Unit unit=Radian, BearingType type=InitialBearing) const
Returns the bearing (true bearing, the angle between the line defined by this point and the other and...
static qreal normalizeLat(qreal lat, GeoDataCoordinates::Unit=GeoDataCoordinates::Radian)
normalize latitude to always be in -M_PI / 2.
static void normalizeLonLat(qreal &lon, qreal &lat, GeoDataCoordinates::Unit=GeoDataCoordinates::Radian)
normalize both longitude and latitude at the same time This method normalizes both latitude and longi...
void set(qreal lon, qreal lat, qreal alt=0, GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian)
(re)set the coordinates in a GeoDataCoordinates object
QString latToString() const
return a string representation of latitude of the coordinate convenience function that uses the defau...
QString lonToString() const
return a string representation of longitude of the coordinate convenience function that uses the defa...
int utmZone() const
retrieves the UTM zone of the GeoDataCoordinates object.
KCALENDARCORE_EXPORT uint qHash(const KCalendarCore::Period &key)
qreal utmNorthing() const
retrieves the UTM northing of the GeoDataCoordinates object, in meters
void setLatitude(qreal lat, GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian)
set the longitude in a GeoDataCoordinates object
void setLongitude(qreal lon, GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian)
set the longitude in a GeoDataCoordinates object
Notation
enum used to specify the notation / numerical system
void geoCoordinates(qreal &lon, qreal &lat, GeoDataCoordinates::Unit unit) const
use this function to get the longitude and latitude with one call - use the unit parameter to switch ...
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
const QChar at(int position) const const
bool isPole(Pole=AnyPole) const
return whether our coordinates represent a pole This method can be used to check whether the coordina...
QString toString() const
return a string representation of the coordinate this is a convenience function which uses the defaul...
QString utmLatitudeBand() const
retrieves the UTM latitude band of the GeoDataCoordinates object
static void setDefaultNotation(GeoDataCoordinates::Notation notation)
set the Notation of the string representation
qreal longitude(GeoDataCoordinates::Unit unit) const
retrieves the longitude of the GeoDataCoordinates object use the unit parameter to switch between Rad...
static GeoDataCoordinates fromString(const QString &string, bool &successful)
try to parse the string into a coordinate pair
bool isValid() const
Returns.
qreal latitude(GeoDataCoordinates::Unit unit) const
retrieves the latitude of the GeoDataCoordinates object use the unit parameter to switch between Radi...
"Sexagesimal DMS" notation (base-60)
static GeoDataCoordinates::Notation defaultNotation()
return Notation of string representation
"Sexagesimal DM" notation (base-60)
QDebug mDebug()
a function to replace qDebug() in Marble library code
Definition: MarbleDebug.cpp:36
Only North Pole.
Definition: MarbleGlobal.h:145
GeoDataCoordinates interpolate(const GeoDataCoordinates &target, double t) const
slerp (spherical linear) interpolation between this coordinate and the given target coordinate ...
< "RA and DEC" notation (used for astronomical sky coordinates)
const Quaternion & quaternion() const
return a Quaternion with the used coordinates
void pack(QDataStream &stream) const
Serialize the contents of the feature to stream.
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Jul 13 2020 23:19:43 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.