File indexing completed on 2025-01-05 03:56:26

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2008-05-05
0007  * Description : Geodetic tools
0008  *
0009  * SPDX-FileCopyrightText: 2008-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  * SPDX-FileCopyrightText: 2015-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #ifndef DIGIKAM_GEODETIC_TOOLS_H
0017 #define DIGIKAM_GEODETIC_TOOLS_H
0018 
0019 // C++ includes
0020 
0021 #include <cmath>
0022 
0023 // Qt includes
0024 
0025 #include <QString>
0026 #include <QPointF>
0027 
0028 // Local includes
0029 
0030 #include "digikam_export.h"
0031 
0032 namespace Digikam
0033 {
0034 
0035 namespace Coordinates
0036 {
0037 
0038 /// Converting between radians and degrees
0039 
0040 inline double toRadians(double deg)
0041 {
0042     return (deg * M_PI / 180.0);
0043 }
0044 
0045 inline double toRadiansFactor()
0046 {
0047     return (M_PI / 180.0);
0048 }
0049 
0050 inline double toDegrees(double rad)
0051 {
0052     return (rad * 180.0 / M_PI);
0053 }
0054 
0055 inline double toDegreesFactor()
0056 {
0057     return (180.0 / M_PI);
0058 }
0059 
0060 } // namespace Coordinates
0061 
0062 // ------------------------------------------------------------------------------------------------
0063 
0064 /**
0065  * Geometric figure that can be used to describe the approximate shape of the earth.
0066  * In mathematical terms, it is a surface formed by the rotation of an ellipse about
0067  * its minor axis. An ellipsoid requires two defining parameters:
0068  *  - semi-major axis and inverse flattening, or
0069  *  - semi-major axis and semi-minor axis.
0070  */
0071 class DIGIKAM_EXPORT Ellipsoid
0072 {
0073 
0074 public:
0075 
0076     /**
0077      * WGS 1984 ellipsoid with axis in metres. This ellipsoid is used
0078      * in GPS systems and is the default for most <code>org.geotools</code> packages.
0079      */
0080     static Ellipsoid WGS84();
0081 
0082     /**
0083      * GRS 80 ellipsoid with axis in metres.
0084      */
0085     static Ellipsoid GRS80();
0086 
0087     /**
0088      * International 1924 ellipsoid with axis in metres.
0089      */
0090     static Ellipsoid INTERNATIONAL_1924();
0091 
0092     /**
0093      * Clarke 1866 ellipsoid with axis in metres.
0094      */
0095     static Ellipsoid CLARKE_1866();
0096 
0097     /**
0098      * A sphere with a radius of 6371000 metres. Spheres use a simpler
0099      * algorithm for orthodromic distance computation, which
0100      * may be faster and more robust.
0101      */
0102     static Ellipsoid SPHERE();
0103 
0104     /**
0105      * Constructs a new ellipsoid using the specified axis length.
0106      *
0107      * @param name              The ellipsoid name.
0108      * @param semiMajorAxis The equatorial radius.
0109      * @param semiMinorAxis The polar radius.
0110      */
0111     static Ellipsoid createEllipsoid(const QString& name,
0112                                      double semiMajorAxis,
0113                                      double semiMinorAxis);
0114 
0115     /**
0116      * Constructs a new ellipsoid using the specified axis length and inverse flattening value.
0117      *
0118      * @param name              The ellipsoid name.
0119      * @param semiMajorAxis     The equatorial radius.
0120      * @param inverseFlattening The inverse flattening value.
0121      *                          values.
0122      */
0123     static Ellipsoid createFlattenedSphere(const QString& name,
0124                                            double semiMajorAxis,
0125                                            double inverseFlattening);
0126 
0127     /**
0128      * Length of the semi-major axis of the ellipsoid. This is the
0129      * equatorial radius in axis linear unit.
0130      *
0131      * @return Length of semi-major axis.
0132      */
0133     double semiMajorAxis() const;
0134 
0135     /**
0136      * Length of the semi-minor axis of the ellipsoid. This is the
0137      * polar radius in axis linear unit.
0138      *
0139      * @return Length of semi-minor axis.
0140      */
0141     double semiMinorAxis() const;
0142 
0143     /**
0144      * The ratio of the distance between the center and a focus of the ellipse
0145      * to the length of its semimajor axis. The eccentricity can alternately be
0146      * computed from the equation: e=sqrt(2f-f^2).
0147      */
0148     double eccentricity() const;
0149 
0150     /**
0151      * Returns the value of the inverse of the flattening constant. Flattening is a value
0152      * used to indicate how closely an ellipsoid approaches a spherical shape. The inverse
0153      * flattening is related to the equatorial/polar radius by the formula
0154      *
0155      * ivf=r_e/(r_e-r_p).
0156      *
0157      * For perfect spheres (i.e. if isSphere returns @c true),
0158      * the DoublePOSITIVE_INFINITY value is used.
0159      *
0160      * @return The inverse flattening value.
0161      */
0162     double inverseFlattening() const;
0163 
0164     /**
0165      * Indicates if the inverse flattening is definitive for
0166      * this ellipsoid. Some ellipsoids use the IVF as the defining value, and calculate the polar
0167      * radius whenever asked. Other ellipsoids use the polar radius to calculate the IVF whenever
0168      * asked. This distinction can be important to avoid floating-point rounding errors.
0169      *
0170      * @return @c true if the inverse flattening is
0171      *         definitive, or @c false if the polar radius
0172      *         is definitive.
0173      */
0174     bool isIvfDefinitive() const;
0175 
0176     /**
0177      * @c true if the ellipsoid is degenerate and is actually a sphere. The sphere is
0178      * completely defined by the semi-major axis, which is the
0179      * radius of the sphere.
0180      *
0181      * @return @c true if the ellipsoid is degenerate and is actually a sphere.
0182      */
0183     bool isSphere() const;
0184 
0185     /**
0186      * Returns the orthodromic distance between two geographic coordinates.
0187      * The orthodromic distance is the shortest distance between two points
0188      * on a sphere's surface. The orthodromic path is always on a great circle.
0189      * This is different from the loxodromic distance, which is a
0190      * longer distance on a path with a constant direction on the compass.
0191      *
0192      * @param  x1 Longitude of first  point (in decimal degrees).
0193      * @param  y1 Latitude  of first  point (in decimal degrees).
0194      * @param  x2 Longitude of second point (in decimal degrees).
0195      * @param  y2 Latitude  of second point (in decimal degrees).
0196      * @return The orthodromic distance (in the units of this ellipsoid's axis).
0197      */
0198     double orthodromicDistance(double x1, double y1, double x2, double y2);
0199 
0200     /**
0201      * Returns the Radius Of Curvature for the given latitude,
0202      * using the geometric mean of two radii of
0203      * curvature for all azimuths.
0204      * @param latitude in degrees
0205      */
0206     double radiusOfCurvature(double latitude);
0207 
0208 protected:
0209 
0210     /**
0211      * Constructs a new ellipsoid using the specified axis length. The properties map is
0212      * given unchanged to the AbstractIdentifiedObjectAbstractIdentifiedObject(Map)
0213      * super-class constructor.
0214      *
0215      * @param semiMajorAxis     The equatorial radius.
0216      * @param semiMinorAxis     The polar radius.
0217      * @param inverseFlattening The inverse of the flattening value.
0218      * @param ivfDefinitive     @c true if the inverse flattening is definitive.
0219      *
0220      * @see createEllipsoid
0221      * @see createFlattenedSphere
0222      */
0223     Ellipsoid(const QString& name,
0224               double semiMajorAxis,
0225               double semiMinorAxis,
0226               double inverseFlattening,
0227               bool ivfDefinitive);
0228     Ellipsoid(const QString& name,
0229               double radius,
0230               bool ivfDefinitive);
0231 
0232 protected:
0233 
0234     QString name;
0235 
0236     /**
0237      * The equatorial radius.
0238      * @see getSemiMajorAxis
0239      */
0240     double m_semiMajorAxis;
0241 
0242     /**
0243      * The polar radius.
0244      * @see getSemiMinorAxis
0245      */
0246     double m_semiMinorAxis;
0247 
0248     /**
0249      * The inverse of the flattening value, or DBL_MAX
0250      * if the ellipsoid is a sphere.
0251      *
0252      * @see getInverseFlattening
0253      */
0254     double m_inverseFlattening;
0255 
0256     /**
0257      * Tells if the Inverse Flattening definitive for this ellipsoid.
0258      *
0259      * @see isIvfDefinitive
0260      */
0261     bool   m_ivfDefinitive;
0262 
0263     bool   m_isSphere;
0264 };
0265 
0266 // ------------------------------------------------------------------------------------------------
0267 
0268 class DIGIKAM_EXPORT GeodeticCalculator
0269 {
0270     /**
0271      * Performs geodetic calculations on an ellipsoid. This class encapsulates
0272      * a generic ellipsoid and calculates the following properties:
0273      *
0274      *
0275      *   Distance and azimuth between two points.
0276      *   Point located at a given distance and azimuth from an other point.
0277      *
0278      *
0279      * The calculation use the following information:
0280      *
0281      *
0282      *   The starting position (setStartingPosition), which is always considered valid.
0283      *       It is initially set at (0,0) and can only be changed to another legitimate value.
0284      *   Only one of the following:
0285      *
0286      *         The destination position (setDestinationPosition), or
0287      *         An azimuth and distance (setDirection).
0288      *
0289      *       The latest one set overrides the other and determines what will be calculated.
0290      *
0291      *
0292      */
0293 
0294 public:
0295 
0296     explicit GeodeticCalculator(const Ellipsoid& e = Ellipsoid::WGS84());
0297 
0298     /**
0299      * Returns the referenced ellipsoid.
0300      */
0301     Ellipsoid ellipsoid() const;
0302 
0303     /**
0304      * Set the starting point in geographic coordinates.
0305      * The azimuth, the orthodromic distance and the destination point
0306      * are discarded. They will need to be specified again.
0307      * Coordinates positive North and East.
0308      *
0309      * @param  longitude The longitude in decimal degrees between -180 and +180°
0310      * @param  latitude  The latitude  in decimal degrees between  -90 and  +90°
0311      */
0312     void setStartingGeographicPoint(double longitude, double latitude);
0313 
0314     /**
0315      * Set the destination point in geographic coordinates. The azimuth and distance values
0316      * will be updated as a side effect of this call. They will be recomputed the next time
0317      * getAzimuth() or getOrthodromicDistance() are invoked.
0318      * Coordinates positive North and East.
0319      *
0320      * @param  longitude The longitude in decimal degrees between -180 and +180°
0321      * @param  latitude  The latitude in decimal degrees between  -90 and  +90°
0322      *
0323      */
0324     void setDestinationGeographicPoint(double longitude, double latitude);
0325 
0326     /**
0327      * Returns the destination point. This method returns the point set by the last
0328      * call to a setDestinationGeographicPoint(...)
0329      * method, except if setDirection(...) has been
0330      * invoked after. In this later case, the destination point will be computed from the
0331      * starting point to the azimuth and distance specified.
0332      * Coordinates positive North and East.
0333      *
0334      * @return The destination point. The x and y coordinates
0335      *         are the longitude and latitude in decimal degrees, respectively.
0336      */
0337     bool destinationGeographicPoint(double* longitude, double* latitude);
0338     QPointF destinationGeographicPoint();
0339 
0340     /**
0341      * Set the azimuth and the distance from the startingGeographicPoint
0342      * starting point. The destination point will be updated as a side effect of this call.
0343      * It will be recomputed the next time destinationGeographicPoint() is invoked.
0344      * Azimuth 0° North.
0345      *
0346      * @param  azimuth The azimuth in decimal degrees from -180° to 180°.
0347      * @param  distance The orthodromic distance in the same units as the ellipsoid axis.
0348      */
0349     void setDirection(double azimuth, double distance);
0350 
0351     /**
0352      * Returns the azimuth. This method returns the value set by the last call to
0353      * <code>setDirection(double,double) setDirection(azimuth,distance)</code>,
0354      * except if <code>setDestinationGeographicPoint(double,double)
0355      * setDestinationGeographicPoint(...)</code> has been invoked after. In this later case, the
0356      * azimuth will be computed from the startingGeographicPoint starting point
0357      * to the destination point.
0358      *
0359      * @return The azimuth, in decimal degrees from -180° to +180°.
0360      */
0361     double azimuth();
0362 
0363     /**
0364      * Returns the orthodromic distance. This method returns the value set by the last call to
0365      * <code>setDirection(double,double) setDirection(azimuth,distance)</code>,
0366      * except if <code>setDestinationGeographicPoint(double,double)
0367      * setDestinationGeographicPoint(...)</code> has been invoked after. In this later case, the
0368      * distance will be computed from the startingGeographicPoint starting point
0369      * to the destination point.
0370      *
0371      * @return The orthodromic distance, in the same units as the
0372      *         getEllipsoid ellipsoid axis.
0373      */
0374     double orthodromicDistance();
0375 
0376     /**
0377      * Computes the orthodromic distance using the algorithm implemented in the Geotools's
0378      * ellipsoid class (if available), and check if the error is smaller than some tolerance
0379      * error.
0380      */
0381     bool checkOrthodromicDistance();
0382 
0383     /**
0384      * Computes the destination point from the starting
0385      * point, the azimuth and the orthodromic distance.
0386      */
0387     bool computeDestinationPoint();
0388 
0389     /**
0390      * Calculates the meridian arc length between two points in the same meridian
0391      * in the referenced ellipsoid.
0392      *
0393      * @param  latitude1 The latitude of the first  point (in decimal degrees).
0394      * @param  latitude2 The latitude of the second point (in decimal degrees).
0395      * @return Returned the meridian arc length between latitude1 and latitude2
0396      */
0397     double meridianArcLength(double latitude1, double latitude2);
0398 
0399     /**
0400      * Calculates the meridian arc length between two points in the same meridian
0401      * in the referenced ellipsoid.
0402      *
0403      * @param  P1 The latitude of the first  point (in radians).
0404      * @param  P2 The latitude of the second point (in radians).
0405      * @return Returned the meridian arc length between P1 and P2
0406      */
0407     double meridianArcLengthRadians(double P1, double P2);
0408 
0409     /**
0410      * Computes the azimuth and orthodromic distance from the
0411      * startingGeographicPoint starting point and the
0412      * destinationGeographicPoint destination point.
0413      */
0414     bool computeDirection();
0415 
0416 protected:
0417 
0418     double castToAngleRange(const double alpha);
0419 
0420     /**
0421      * Checks the latitude validity. The argument @c latitude should be
0422      * greater or equal than -90 degrees and lower or equals than +90 degrees. As
0423      * a convenience, this method converts the latitude to radians.
0424      *
0425      * @param  latitude The latitude value in decimal degrees.
0426      */
0427     bool checkLatitude(double* latitude);
0428 
0429     /**
0430      * Checks the longitude validity. The argument @c longitude should be
0431      * greater or equal than -180 degrees and lower or equals than +180 degrees. As
0432      * a convenience, this method converts the longitude to radians.
0433      *
0434      * @param  longitude The longitude value in decimal degrees.
0435      */
0436     bool checkLongitude(double* longitude);
0437 
0438     /**
0439      * Checks the azimuth validity. The argument @c azimuth should be
0440      * greater or equal than -180 degrees and lower or equals than +180 degrees.
0441      * As a convenience, this method converts the azimuth to radians.
0442      *
0443      * @param  azimuth The azimuth value in decimal degrees.
0444      */
0445     bool checkAzimuth(double* azimuth);
0446 
0447     /**
0448      * Checks the orthodromic distance validity. Arguments @c orthodromicDistance
0449      * should be greater or equal than 0 and lower or equals than the maximum orthodromic distance.
0450      *
0451      * @param  distance The orthodromic distance value.
0452      */
0453     bool checkOrthodromicDistance(const double distance);
0454 
0455     /**
0456      * Tolerance factors from the strictest (<code>TOLERANCE_0</CODE>)
0457      * to the most relax one (<code>TOLERANCE_3</CODE>).
0458      */
0459     double TOLERANCE_0, TOLERANCE_1, TOLERANCE_2, TOLERANCE_3;
0460 
0461     /**
0462      * Tolerance factor for assertions. It has no impact on computed values.
0463      */
0464     double TOLERANCE_CHECK;
0465 
0466     /**
0467      * The encapsulated ellipsoid.
0468      */
0469     Ellipsoid m_ellipsoid;
0470 
0471     /**
0472      * The semi major axis of the referenced ellipsoid.
0473      */
0474     double m_semiMajorAxis;
0475 
0476     /**
0477      * The semi minor axis of the referenced ellipsoid.
0478      */
0479     double m_semiMinorAxis;
0480 
0481     /**
0482      * The eccentricity squared of the referenced ellipsoid.
0483      */
0484     double m_eccentricitySquared;
0485 
0486     /**
0487      * The maximum orthodromic distance that could be calculated onto the referenced ellipsoid.
0488      */
0489     double m_maxOrthodromicDistance;
0490 
0491     /**
0492      * GPNARC parameters computed from the ellipsoid.
0493      */
0494     double A, B, C, D, E, F;
0495 
0496     /**
0497      * GPNHRI parameters computed from the ellipsoid.
0498      *
0499      * @c f if the flattening of the referenced ellipsoid. @c f2,
0500      * @c f3 and @c f4 are <var>f<sup>2</sup></var>,
0501      * <var>f<sup>3</sup></var> and <var>f<sup>4</sup></var> respectively.
0502      */
0503     double fo, f, f2, f3, f4;
0504 
0505     /**
0506      * Parameters computed from the ellipsoid.
0507      */
0508     double T1, T2, T4, T6;
0509 
0510     /**
0511      * Parameters computed from the ellipsoid.
0512      */
0513     double a01, a02, a03, a21, a22, a23, a42, a43, a63;
0514 
0515     /**
0516      * The (<var>latitude</var>, <var>longitude</var>) coordinate of the first point
0517      * in radians. This point is set by setStartingGeographicPoint.
0518      */
0519     double m_lat1, m_long1;
0520 
0521     /**
0522      * The (<var>latitude</var>, <var>longitude</var>) coordinate of the destination point
0523      * in radians. This point is set by setDestinationGeographicPoint.
0524      */
0525     double m_lat2, m_long2;
0526 
0527     /**
0528      * The distance and azimuth (in radians) from the starting point
0529      * (long1, lat1) to the destination point
0530      * (long2, lat2).
0531      */
0532     double m_distance, m_azimuth;
0533 
0534     /**
0535      * Tell if the destination point is valid.
0536      * @c false if long2 and lat2 need to be computed.
0537      */
0538     bool m_destinationValid;
0539 
0540     /**
0541      * Tell if the azimuth and the distance are valids.
0542      * @c false if distance and azimuth need to be computed.
0543      */
0544     bool m_directionValid;
0545 };
0546 
0547 } // namespace Digikam
0548 
0549 #endif // DIGIKAM_GEODETIC_TOOLS_H