1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2006-2007 Torsten Rahn <tackat@kde.org>
4 // SPDX-FileCopyrightText: 2007-2008 Inge Wallin <ingwa@kde.org>
5 // SPDX-FileCopyrightText: 2008 Patrick Spendrin <ps_ml@gmx.de>
6 // SPDX-FileCopyrightText: 2015 Alejandro Garcia Montoro <alejandro.garciamontoro@gmail.com>
7 //
8 
9 
10 #ifndef MARBLE_GEODATACOORDINATES_H
11 #define MARBLE_GEODATACOORDINATES_H
12 
13 #include <QCoreApplication>
14 #include <QMetaType>
15 #include <QVector>
16 
17 #include "geodata_export.h"
18 #include "MarbleGlobal.h"
19 
20 class QString;
21 
22 namespace Marble
23 {
24 
25 class GeoDataCoordinatesPrivate;
26 class Quaternion;
27 
28 /**
29  * @short A 3d point representation
30  *
31  * GeoDataCoordinates is the simple representation of a single three
32  * dimensional point. It can be used all through out marble as the data type
33  * for three dimensional objects. it comprises of a Quaternion for speed issues.
34  * This class was introduced to reflect the difference between a simple 3d point
35  * and the GeoDataGeometry object containing such a point. The latter is a
36  * GeoDataPoint and is simply derived from GeoDataCoordinates.
37  * @see GeoDataPoint
38 */
39 
40 class GEODATA_EXPORT GeoDataCoordinates
41 {
42  Q_DECLARE_TR_FUNCTIONS(GeoDataCoordinates)
43 
44  public:
45     /**
46      * @brief enum used constructor to specify the units used
47      *
48      * Internally we always use radian for mathematical convenience.
49      * However the Marble's interfaces to the outside should default
50      * to degrees.
51      */
52     enum Unit{
53         Radian,
54         Degree
55     };
56 
57     /**
58      * @brief enum used to specify the notation / numerical system
59      *
60      * For degrees there exist two notations:
61      * "Decimal" (base-10) and the "Sexagesimal DMS" (base-60) which is
62      * traditionally used in cartography. Decimal notation
63      * uses floating point numbers to specify parts of a degree. The
64      * Sexagesimal DMS notation uses integer based
65      * Degrees-(Arc)Minutes-(Arc)Seconds to describe parts of a degree.
66      */
67     enum Notation{
68         Decimal, ///< "Decimal" notation (base-10)
69         DMS,     ///< "Sexagesimal DMS" notation (base-60)
70         DM,      ///< "Sexagesimal DM" notation (base-60)
71         UTM,
72         Astro    /// < "RA and DEC" notation (used for astronomical sky coordinates)
73     };
74 
75     /**
76      * @brief The BearingType enum specifies where to measure the bearing
77      * along great circle arcs
78      *
79      * When traveling along a great circle arc defined by the two points
80      * A and B, the bearing varies along the arc. The "InitialBearing" bearing
81      * corresponds to the bearing value at A, the "FinalBearing" bearing to that
82      * at B.
83      */
84     enum BearingType {
85         InitialBearing,
86         FinalBearing
87     };
88 
89     // Type definitions
90     using Vector = QVector<GeoDataCoordinates>;
91     using PtrVector = QVector<GeoDataCoordinates *>;
92 
93     GeoDataCoordinates( const GeoDataCoordinates& other );
94 
95     /**
96      * @brief constructs an invalid instance
97      *
98      * Constructs an invalid instance such that calling isValid()
99      * on it will return @code false @endcode.
100      */
101     GeoDataCoordinates();
102 
103     /**
104      * @brief create a geocoordinate from longitude and latitude
105      * @param lon longitude
106      * @param lat latitude
107      * @param alt altitude in meters (default: 0)
108      * @param unit units that lon and lat get measured in
109      * (default for Radian: north pole at pi/2, southpole at -pi/2)
110      * @param detail detail (default: 0)
111      */
112     GeoDataCoordinates( qreal lon, qreal lat, qreal alt = 0,
113                         GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian,
114                         int detail = 0 );
115 
116     virtual ~GeoDataCoordinates();
117 
118     /**
119      * @brief Returns @code true @endcode if the coordinate is valid, @code false @endcode otherwise.
120      * @return whether the coordinate is valid
121      *
122      * A coordinate is valid, if at least one component has been set and the last
123      * assignment was not an invalid GeoDataCoordinates object.
124      */
125     bool isValid() const;
126 
127     /**
128     * @brief (re)set the coordinates in a GeoDataCoordinates object
129     * @param lon longitude
130     * @param lat latitude
131     * @param alt altitude in meters (default: 0)
132     * @param unit units that lon and lat get measured in
133     * (default for Radian: north pole at pi/2, southpole at -pi/2)
134     */
135     void set( qreal lon, qreal lat, qreal alt = 0,
136               GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian );
137 
138     /**
139     * @brief use this function to get the longitude and latitude with one
140     * call - use the unit parameter to switch between Radian and DMS
141     * @param lon longitude
142     * @param lat latitude
143     * @param unit units that lon and lat get measured in
144     * (default for Radian: north pole at pi/2, southpole at -pi/2)
145     */
146     void geoCoordinates(qreal& lon, qreal& lat, GeoDataCoordinates::Unit unit) const;
147     void geoCoordinates(qreal& lon, qreal& lat) const;
148 
149     /**
150     * @brief use this function to get the longitude, latitude and altitude
151     * with one call - use the unit parameter to switch between Radian and DMS
152     * @param lon longitude
153     * @param lat latitude
154     * @param alt altitude in meters
155     * @param unit units that lon and lat get measured in
156     * (default for Radian: north pole at pi/2, southpole at -pi/2)
157     */
158     void geoCoordinates(qreal& lon, qreal& lat, qreal& alt, GeoDataCoordinates::Unit unit) const;
159     void geoCoordinates(qreal& lon, qreal& lat, qreal& alt) const;
160 
161     /**
162     * @brief set the longitude in a GeoDataCoordinates object
163     * @param lon longitude
164     * @param unit units that lon and lat get measured in
165     * (default for Radian: north pole at pi/2, southpole at -pi/2)
166     */
167     void setLongitude( qreal lon,
168               GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian );
169 
170     /**
171     * @brief retrieves the longitude of the GeoDataCoordinates object
172     * use the unit parameter to switch between Radian and DMS
173     * @param unit units that lon and lat get measured in
174     * (default for Radian: north pole at pi/2, southpole at -pi/2)
175     * @return longitude
176     */
177     qreal longitude(GeoDataCoordinates::Unit unit) const;
178     qreal longitude() const;
179 
180     /**
181     * @brief retrieves the latitude of the GeoDataCoordinates object
182     * use the unit parameter to switch between Radian and DMS
183     * @param unit units that lon and lat get measured in
184     * (default for Radian: north pole at pi/2, southpole at -pi/2)
185     * @return latitude
186     */
187     qreal latitude( GeoDataCoordinates::Unit unit ) const;
188     qreal latitude() const;
189 
190     /**
191     * @brief set the longitude in a GeoDataCoordinates object
192     * @param lat longitude
193     * @param unit units that lon and lat get measured in
194     * (default for Radian: north pole at pi/2, southpole at -pi/2)
195     */
196     void setLatitude( qreal lat,
197               GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian );
198 
199     /**
200         * @brief return the altitude of the Point in meters
201         */
202     qreal altitude() const;
203     /**
204     * @brief set the altitude of the Point in meters
205     * @param altitude altitude
206     */
207     void setAltitude( const qreal altitude );
208 
209     /**
210     * @brief retrieves the UTM zone of the GeoDataCoordinates object.
211     * If the point is located on one of the poles (latitude < 80S or
212     * latitude > 84N) there is no UTM zone associated; in this case,
213     * 0 is returned.
214     * @return UTM zone.
215     */
216     int utmZone() const;
217 
218     /**
219     * @brief retrieves the UTM easting of the GeoDataCoordinates object,
220     * in meters.
221     * @return UTM easting
222     */
223     qreal utmEasting() const;
224 
225     /**
226     * @brief retrieves the UTM latitude band of the GeoDataCoordinates object
227     * @return UTM latitude band
228     */
229     QString utmLatitudeBand() const;
230 
231     /**
232     * @brief retrieves the UTM northing of the GeoDataCoordinates object,
233     * in meters
234     * @return UTM northing
235     */
236     qreal utmNorthing() const;
237 
238     /**
239     * @brief return the detail flag
240     * detail range: 0 for most important points, 5 for least important
241     */
242     quint8 detail() const;
243 
244     /**
245     * @brief set the detail flag
246     * @param detail detail
247     */
248     void setDetail(quint8 detail);
249 
250     /**
251      * @brief Rotates one coordinate around another.
252      * @param axis The coordinate that serves as a rotation axis
253      * @param angle Rotation angle
254      * @param unit Unit of the result
255      * @return The coordinate rotated in anticlockwise direction
256      */
257     GeoDataCoordinates rotateAround( const GeoDataCoordinates &axis, qreal angle, Unit unit = Radian ) const;
258 
259     GeoDataCoordinates rotateAround(const Quaternion &rotAxis) const;
260 
261     /**
262      * @brief Returns the bearing (true bearing, the angle between the line defined
263      * by this point and the other and the prime meridian)
264      * @param other The second point that, together with this point, defines a line
265      * @param unit Unit of the result
266      * @param type Type of the bearing
267      * @return The true bearing in the requested unit, not range normalized,
268      * in clockwise direction, with the value 0 corresponding to north
269      */
270     qreal bearing( const GeoDataCoordinates &other, Unit unit = Radian, BearingType type = InitialBearing ) const;
271 
272     /**
273      * @brief Returns the coordinates of the resulting point after moving this point
274      * according to the distance and bearing parameters
275      * @param bearing the same as above
276      * @param distance the distance on a unit sphere
277      */
278     GeoDataCoordinates moveByBearing( qreal bearing, qreal distance ) const;
279 
280     /**
281     * @brief return a Quaternion with the used coordinates
282     */
283     const Quaternion &quaternion() const;
284 
285     /**
286      * @brief slerp (spherical linear) interpolation between this coordinate and the given target coordinate
287      * @param target Destination coordinate
288      * @param t Fraction 0..1 to weight between this and target
289      * @return Interpolated coordinate between this (t<=0.0) and target (t>=1.0)
290      */
291     GeoDataCoordinates interpolate( const GeoDataCoordinates &target, double t ) const;
292 
293     /**
294      * @brief nlerp (normalized linear interpolation) between this coordinates and the given target coordinates
295      * @param target Destination coordinates
296      * @param t Fraction 0..1 to weight between this and target
297      * @return Interpolated coordinate between this (t<=0.0) and target (t>=1.0)
298      */
299     GeoDataCoordinates nlerp(const GeoDataCoordinates &target, double t) const;
300 
301     /**
302      * @brief squad (spherical and quadrangle) interpolation between b and c
303      * @param before First base point
304      * @param target Third base point (second interpolation point)
305      * @param after Fourth base point
306      * @param t Offset between b (t<=0) and c (t>=1)
307      */
308     GeoDataCoordinates interpolate( const GeoDataCoordinates &before, const GeoDataCoordinates &target, const GeoDataCoordinates &after, double t ) const;
309 
310     /**
311     * @brief return whether our coordinates represent a pole
312     * This method can be used to check whether the coordinate equals one of
313     * the poles.
314     */
315     bool isPole( Pole = AnyPole ) const;
316 
317     /**
318      * @brief This method calculates the shortest distance between two points on a sphere.
319      * @brief See: https://en.wikipedia.org/wiki/Great-circle_distance
320      */
321     qreal sphericalDistanceTo(const GeoDataCoordinates &other) const;
322 
323     /**
324     * @brief return Notation of string representation
325     */
326     static GeoDataCoordinates::Notation defaultNotation();
327 
328     /**
329     * @brief set the Notation of the string representation
330     * @param notation Notation
331     */
332     static void setDefaultNotation( GeoDataCoordinates::Notation notation );
333 
334     /**
335      * @brief normalize the longitude to always be -M_PI <= lon <= +M_PI (Radian).
336      * @param lon longitude
337      * @param unit unit of the result
338      */
339     static qreal normalizeLon( qreal lon,
340                                GeoDataCoordinates::Unit = GeoDataCoordinates::Radian );
341 
342     /**
343      * @brief normalize latitude to always be in -M_PI / 2. <= lat <= +M_PI / 2 (Radian).
344      * @param lat latitude
345      * @param unit unit of the result
346      */
347     static qreal normalizeLat( qreal lat,
348                                GeoDataCoordinates::Unit = GeoDataCoordinates::Radian );
349 
350     /**
351      * @brief normalize both longitude and latitude at the same time
352      * This method normalizes both latitude and longitude, so that the
353      * latitude and the longitude stay within the "usual" range.
354      * NOTE: If the latitude exceeds M_PI/2 (+90.0 deg) or -M_PI/2 (-90.0 deg)
355      * then this will be interpreted as a pole traversion where the point will
356      * end up on the opposite side of the globe. Therefore the longitude will
357      * change by M_PI (180 deg).
358      * If you don't want this behaviour use both normalizeLat() and
359      * normalizeLon() instead.
360      * @param lon the longitude value
361      * @param lat the latitude value
362      * @param unit unit of the result
363      */
364     static void normalizeLonLat( qreal &lon, qreal &lat,
365                                  GeoDataCoordinates::Unit = GeoDataCoordinates::Radian );
366 
367     /**
368      * @brief try to parse the string into a coordinate pair
369      * @param string the string
370      * @param successful becomes true if the conversion succeeds
371      * @return the geodatacoordinates
372      */
373     static GeoDataCoordinates fromString( const QString &string, bool& successful );
374 
375     /**
376     * @brief return a string representation of the coordinate
377     * this is a convenience function which uses the default notation
378     */
379     QString toString() const;
380 
381     /**
382     * @brief return a string with the notation given by notation
383     *
384     * @param notation set a notation different from the default one
385     * @param precision set the number of digits below degrees.
386     * The precision depends on the current notation:
387     * For Decimal representation the precision is the number of
388     * digits after the decimal point.
389     * In DMS a precision of 1 or 2 shows the arc minutes; a precision
390     * of 3 or 4 will show arc seconds. A precision beyond that will
391     * increase the number of digits after the arc second decimal point.
392     */
393     QString toString( GeoDataCoordinates::Notation notation, int precision = -1 ) const;
394 
395     static QString lonToString( qreal lon, GeoDataCoordinates::Notation notation,
396                                            GeoDataCoordinates::Unit unit = Radian,
397                                            int precision = -1,
398                                            char format = 'f' );
399     /**
400      * @brief return a string representation of longitude of the coordinate
401      * convenience function that uses the default notation
402      */
403     QString lonToString() const;
404 
405     static QString latToString( qreal lat, GeoDataCoordinates::Notation notation,
406                                            GeoDataCoordinates::Unit unit = Radian,
407                                            int precision = -1,
408                                            char format = 'f' );
409     /**
410      * @brief return a string representation of latitude of the coordinate
411      * convenience function that uses the default notation
412      */
413     QString latToString() const;
414 
415     bool operator==(const GeoDataCoordinates &other) const;
416     bool operator!=(const GeoDataCoordinates &other) const;
417 
418     GeoDataCoordinates& operator=( const GeoDataCoordinates &other );
419 
420     /** Serialize the contents of the feature to @p stream. */
421     void pack(QDataStream &stream) const;
422     /** Unserialize the contents of the feature from @p stream. */
423     void unpack(QDataStream &stream);
424 
425  private:
426     void detach();
427 
428     GeoDataCoordinatesPrivate *d;
429 
430     static GeoDataCoordinates::Notation s_notation;
431     static const GeoDataCoordinates null;
432 };
433 
434 GEODATA_EXPORT uint qHash(const GeoDataCoordinates& coordinates );
435 
436 
437 }
438 
439 Q_DECLARE_METATYPE( Marble::GeoDataCoordinates )
440 
441 #endif
442