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 }