KWin
Loading...
Searching...
No Matches
suncalc.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2017 Roman Gilg <subdiff@gmail.com>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9#include "suncalc.h"
10#include "constants.h"
11
12#include <QDateTime>
13#include <QTimeZone>
14#include <QtMath>
15
16namespace KWin
17{
18
19#define TWILIGHT_NAUT -12.0
20#define TWILIGHT_CIVIL -6.0
21#define SUN_RISE_SET -0.833
22#define SUN_HIGH 2.0
23
24static QTime convertToLocalTime(const QDateTime &when, const QTime &utcTime)
25{
26 const QTimeZone timeZone = QTimeZone::systemTimeZone();
27 const int utcOffset = timeZone.offsetFromUtc(when);
28 return utcTime.addSecs(utcOffset);
29}
30
31QPair<QDateTime, QDateTime> calculateSunTimings(const QDateTime &dateTime, double latitude, double longitude, bool morning)
32{
33 // calculations based on https://aa.quae.nl/en/reken/zonpositie.html
34 // accuracy: +/- 5min
35
36 // positioning
37 const double rad = M_PI / 180.;
38 const double earthObliquity = 23.4397; // epsilon
39
40 const double lat = latitude; // phi
41 const double lng = -longitude; // lw
42
43 // times
44 const QDateTime utcDateTime = dateTime.toUTC();
45 const double juPrompt = utcDateTime.date().toJulianDay(); // J
46 const double ju2000 = 2451545.; // J2000
47
48 // geometry
49 auto mod360 = [](double number) -> double {
50 return std::fmod(number, 360.);
51 };
52
53 auto sin = [&rad](double angle) -> double {
54 return std::sin(angle * rad);
55 };
56 auto cos = [&rad](double angle) -> double {
57 return std::cos(angle * rad);
58 };
59 auto asin = [&rad](double val) -> double {
60 return std::asin(val) / rad;
61 };
62 auto acos = [&rad](double val) -> double {
63 return std::acos(val) / rad;
64 };
65
66 auto anomaly = [&](const double date) -> double { // M
67 return mod360(357.5291 + 0.98560028 * (date - ju2000));
68 };
69
70 auto center = [&sin](double anomaly) -> double { // C
71 return 1.9148 * sin(anomaly) + 0.02 * sin(2 * anomaly) + 0.0003 * sin(3 * anomaly);
72 };
73
74 auto ecliptLngMean = [](double anom) -> double { // Mean ecliptical longitude L_sun = Mean Anomaly + Perihelion + 180°
75 return anom + 282.9372; // anom + 102.9372 + 180°
76 };
77
78 auto ecliptLng = [&](double anom) -> double { // lambda = L_sun + C
79 return ecliptLngMean(anom) + center(anom);
80 };
81
82 auto declination = [&](const double date) -> double { // delta
83 const double anom = anomaly(date);
84 const double eclLng = ecliptLng(anom);
85
86 return mod360(asin(sin(earthObliquity) * sin(eclLng)));
87 };
88
89 // sun hour angle at specific angle
90 auto hourAngle = [&](const double date, double angle) -> double { // H_t
91 const double decl = declination(date);
92 const double ret0 = (sin(angle) - sin(lat) * sin(decl)) / (cos(lat) * cos(decl));
93
94 double ret = mod360(acos(ret0));
95 if (180. < ret) {
96 ret = ret - 360.;
97 }
98 return ret;
99 };
100
101 /*
102 * Sun positions
103 */
104
105 // transit is at noon
106 auto getTransit = [&](const double date) -> double { // Jtransit
107 const double juMeanSolTime = juPrompt - ju2000 - 0.0009 - lng / 360.; // n_x = J - J_2000 - J_0 - l_w / 360°
108 const double juTrEstimate = date + qRound64(juMeanSolTime) - juMeanSolTime; // J_x = J + n - n_x
109 const double anom = anomaly(juTrEstimate); // M
110 const double eclLngM = ecliptLngMean(anom); // L_sun
111
112 return juTrEstimate + 0.0053 * sin(anom) - 0.0068 * sin(2 * eclLngM);
113 };
114
115 auto getSunMorning = [&hourAngle](const double angle, const double transit) -> double {
116 return transit - hourAngle(transit, angle) / 360.;
117 };
118
119 auto getSunEvening = [&hourAngle](const double angle, const double transit) -> double {
120 return transit + hourAngle(transit, angle) / 360.;
121 };
122
123 /*
124 * Begin calculations
125 */
126
127 // noon - sun at the highest point
128 const double juNoon = getTransit(juPrompt);
129
130 double begin, end;
131 if (morning) {
132 begin = getSunMorning(TWILIGHT_CIVIL, juNoon);
133 end = getSunMorning(SUN_HIGH, juNoon);
134 } else {
135 begin = getSunEvening(SUN_HIGH, juNoon);
136 end = getSunEvening(TWILIGHT_CIVIL, juNoon);
137 }
138 // transform to QDateTime
139 begin += 0.5;
140 end += 0.5;
141
142 QDateTime dateTimeBegin;
143 QDateTime dateTimeEnd;
144
145 if (!std::isnan(begin)) {
146 const double dayFraction = begin - int(begin);
147 const QTime utcTime = QTime::fromMSecsSinceStartOfDay(dayFraction * MSC_DAY);
148 const QTime localTime = convertToLocalTime(dateTime, utcTime);
149 dateTimeBegin = QDateTime(dateTime.date(), localTime);
150 }
151
152 if (!std::isnan(end)) {
153 const double dayFraction = end - int(end);
154 const QTime utcTime = QTime::fromMSecsSinceStartOfDay(dayFraction * MSC_DAY);
155 const QTime localTime = convertToLocalTime(dateTime, utcTime);
156 dateTimeEnd = QDateTime(dateTime.date(), localTime);
157 }
158
159 return {dateTimeBegin, dateTimeEnd};
160}
161
162}
QPair< QDateTime, QDateTime > calculateSunTimings(const QDateTime &dateTime, double latitude, double longitude, bool morning)
Definition suncalc.cpp:31
#define SUN_HIGH
Definition suncalc.cpp:22
#define TWILIGHT_CIVIL
Definition suncalc.cpp:20