File indexing completed on 2024-05-05 05:38:23

0001 /*
0002     SPDX-FileCopyrightText: 2017 Roman Gilg <subdiff@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "suncalc.h"
0008 #include "colorcorrectconstants.h"
0009 
0010 #include <cmath>
0011 #include <numbers>
0012 
0013 #include <QDateTime>
0014 
0015 namespace ColorCorrect
0016 {
0017 inline constexpr double TWILIGHT_NAUT = -12.0;
0018 inline constexpr double TWILIGHT_CIVIL = -6.0;
0019 inline constexpr double SUN_RISE_SET = -0.833;
0020 inline constexpr double SUN_HIGH = 2.0;
0021 
0022 QVariantMap calculateSunTimings(double latitude, double longitude, bool morning)
0023 {
0024     // calculations based on https://aa.quae.nl/en/reken/zonpositie.html
0025     // accuracy: +/- 5min
0026 
0027     // positioning
0028     constexpr double rad = std::numbers::pi / 180.;
0029     constexpr double earthObliquity = 23.4397; // epsilon
0030 
0031     const double lat = latitude; // phi
0032     const double lng = -longitude; // lw
0033 
0034     // times
0035     QDate prompt = QDate::currentDate();
0036     const double juPrompt = prompt.toJulianDay(); // J
0037     constexpr double ju2000 = 2451545.; // J2000
0038 
0039     // geometry
0040     auto mod360 = [](double number) -> double {
0041         return std::fmod(number, 360.);
0042     };
0043 
0044     auto sin = [](double angle) -> double {
0045         return std::sin(angle * rad);
0046     };
0047     auto cos = [](double angle) -> double {
0048         return std::cos(angle * rad);
0049     };
0050     auto asin = [](double val) -> double {
0051         return std::asin(val) / rad;
0052     };
0053     auto acos = [](double val) -> double {
0054         return std::acos(val) / rad;
0055     };
0056 
0057     auto anomaly = [&mod360](const double date) -> double { // M
0058         return mod360(357.5291 + 0.98560028 * (date - ju2000));
0059     };
0060 
0061     auto center = [&sin](double anomaly) -> double { // C
0062         return 1.9148 * sin(anomaly) + 0.02 * sin(2 * anomaly) + 0.0003 * sin(3 * anomaly);
0063     };
0064 
0065     auto ecliptLngMean = [](double anom) -> double { // Mean ecliptical longitude L_sun = Mean Anomaly + Perihelion + 180°
0066         return anom + 282.9372; // anom + 102.9372 + 180°
0067     };
0068 
0069     auto ecliptLng = [&](double anom) -> double { // lambda = L_sun + C
0070         return ecliptLngMean(anom) + center(anom);
0071     };
0072 
0073     auto declination = [&](const double date) -> double { // delta
0074         const double anom = anomaly(date);
0075         const double eclLng = ecliptLng(anom);
0076 
0077         return mod360(asin(sin(earthObliquity) * sin(eclLng)));
0078     };
0079 
0080     // sun hour angle at specific angle
0081     auto hourAngle = [&](const double date, double angle) -> double { // H_t
0082         const double decl = declination(date);
0083         const double ret0 = (sin(angle) - sin(lat) * sin(decl)) / (cos(lat) * cos(decl));
0084 
0085         double ret = mod360(acos(ret0));
0086         if (180. < ret) {
0087             ret = ret - 360.;
0088         }
0089         return ret;
0090     };
0091 
0092     /*
0093      * Sun positions
0094      */
0095 
0096     // transit is at noon
0097     auto getTransit = [&](const double date) -> double { // Jtransit
0098         const double juMeanSolTime = juPrompt - ju2000 - 0.0009 - lng / 360.; // n_x = J - J_2000 - J_0 - l_w / 360°
0099         const double juTrEstimate = date + qRound64(juMeanSolTime) - juMeanSolTime; // J_x = J + n - n_x
0100         const double anom = anomaly(juTrEstimate); // M
0101         const double eclLngM = ecliptLngMean(anom); // L_sun
0102 
0103         return juTrEstimate + 0.0053 * sin(anom) - 0.0068 * sin(2 * eclLngM);
0104     };
0105 
0106     auto getSunMorning = [&hourAngle](const double angle, const double transit) -> double {
0107         return transit - hourAngle(transit, angle) / 360.;
0108     };
0109 
0110     auto getSunEvening = [&hourAngle](const double angle, const double transit) -> double {
0111         return transit + hourAngle(transit, angle) / 360.;
0112     };
0113 
0114     /*
0115      * Begin calculations
0116      */
0117 
0118     // noon - sun at the highest point
0119     const double juNoon = getTransit(juPrompt);
0120 
0121     double begin, end;
0122     if (morning) {
0123         begin = getSunMorning(TWILIGHT_CIVIL, juNoon);
0124         end = getSunMorning(SUN_HIGH, juNoon);
0125     } else {
0126         begin = getSunEvening(SUN_HIGH, juNoon);
0127         end = getSunEvening(TWILIGHT_CIVIL, juNoon);
0128     }
0129     // transform to QDateTime
0130     begin += 0.5;
0131     end += 0.5;
0132 
0133     QTime timeBegin, timeEnd;
0134 
0135     if (std::isnan(begin)) {
0136         timeBegin = QTime();
0137     } else {
0138         double timePart = begin - (int)begin;
0139         timeBegin = QTime::fromMSecsSinceStartOfDay((int)(timePart * MSC_DAY));
0140     }
0141     if (std::isnan(end)) {
0142         timeEnd = QTime();
0143     } else {
0144         double timePart = end - (int)end;
0145         timeEnd = QTime::fromMSecsSinceStartOfDay((int)(timePart * MSC_DAY));
0146     }
0147 
0148     QDateTime dateTimeBegin(prompt, timeBegin, Qt::UTC);
0149     QDateTime dateTimeEnd(prompt, timeEnd, Qt::UTC);
0150 
0151     QVariantMap map;
0152     map.insert("begin", dateTimeBegin.toLocalTime());
0153     map.insert("end", dateTimeEnd.toLocalTime());
0154     return map;
0155 }
0156 
0157 QVariantMap SunCalc::getMorningTimings(double latitude, double longitude)
0158 {
0159     return calculateSunTimings(latitude, longitude, true);
0160 }
0161 
0162 QVariantMap SunCalc::getEveningTimings(double latitude, double longitude)
0163 {
0164     return calculateSunTimings(latitude, longitude, false);
0165 }
0166 
0167 }