File indexing completed on 2024-11-10 04:57:04

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