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 }