[gnome-clocks/zbrown/world-clocks: 3/4] world: colour coded according to sun in location
- From: Zander <zbrown src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-clocks/zbrown/world-clocks: 3/4] world: colour coded according to sun in location
- Date: Sun, 23 Feb 2020 18:24:54 +0000 (UTC)
commit 052e82ae990876bf8d40ccc58bb296c62515cb64
Author: Zander Brown <zbrown gnome org>
Date: Sun Feb 16 13:23:11 2020 +0000
world: colour coded according to sun in location
Thanks to Manuel for figuring out the maths!
data/css/gnome-clocks.css | 22 ++++++
src/meson.build | 3 +-
src/twilight.c | 177 ++++++++++++++++++++++++++++++++++++++++++++++
src/utils.vala | 15 ++++
src/world.vala | 124 ++++++++++++++++++++++++++++++--
5 files changed, 334 insertions(+), 7 deletions(-)
---
diff --git a/data/css/gnome-clocks.css b/data/css/gnome-clocks.css
index f7281d9..41bea50 100644
--- a/data/css/gnome-clocks.css
+++ b/data/css/gnome-clocks.css
@@ -268,5 +268,27 @@ spinbutton.clocks-timer-label button {
background: #e5a50a;
color: #000000;
font-weight: lighter;
+ border: 1px solid rgba(0, 0, 0, 0.06);
+ transition: 0.4s background ease-in;
+}
+
+.night .clock-time {
+ background: #a0a2b7;
+}
+
+.astro .clock-time {
+ background: #c6adaa;
+}
+
+.naut .clock-time {
+ background: #ecb89c;
+}
+
+.civil .clock-time {
+ background: #FAE189;
+}
+
+.day .clock-time {
+ background: #fcf7b0;
}
diff --git a/src/meson.build b/src/meson.build
index 2c80bed..8c8015e 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -19,7 +19,8 @@ clocks_vala_sources = [
]
clocks_c_sources = [
- 'cutils.c'
+ 'cutils.c',
+ 'twilight.c',
]
clocks_sources = [
diff --git a/src/twilight.c b/src/twilight.c
new file mode 100644
index 0000000..a706a23
--- /dev/null
+++ b/src/twilight.c
@@ -0,0 +1,177 @@
+/**
+ * GNOME Clocks
+ *
+ * © 2020 Manuel Genovés <manuel genoves gmail com>
+ *
+ * Routine for calculating sunrise/sunset times, largely based on
+ * https://en.wikipedia.org/wiki/Sunrise_equation
+ * and the equations from
+ * "Practical Astronomy with your Calculator or Spreadsheet"
+ * 4th edition by Peter Duf, Jonathan Zwart
+ *
+ * Ported to C (from python3/numpy) by Zander Brown
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Author: Manuel Genovés <manuel genoves gmail com>
+ * Zander Brown <zbrown gnome org>
+ */
+
+#include <math.h>
+#include <glib.h>
+
+// Epoch 2000
+// (see https://en.wikipedia.org/wiki/Epoch_(astronomy)#Julian_Dates_and_J2000)
+#define JULIAN_YEAR_2000 2451545
+
+#define RADIANS(degrees) ((degrees) * G_PI / 180.0)
+#define DEGREES(radians) ((radians) * 180.0 / G_PI)
+
+#define RISESET_CORRECTION_NONE 0.0
+#define RISESET_CORRECTION_CIVIL 6.0
+#define RISESET_CORRECTION_NAUTICAL 12.0
+#define RISESET_CORRECTION_ASTRONOMICAL 18.0
+
+static gboolean
+is_in_north_summer (int month)
+{
+ // we use meteorogical season because we don't need solstices for calculate them,
+ // some days are lost this way, but it's way easier to calculate
+
+ return (6 >= month && month <= 8);
+}
+
+
+static gboolean
+is_in_north_winter (int month)
+{
+ // we use meteorogical season because we don't need solstices for calculate them,
+ // some days are lost this way, but it's way easier to calculate
+
+ return (1 >= (month + 1)) && ((month + 1) <= 3);
+}
+
+
+/**
+ * calculate_sunrise_sunset:
+ * @lat: place latitude
+ * @lon: place longitude
+ * @year: the gregorian year
+ * @month: the gregorian month of @year
+ * @day: the gregorian day of @month
+ * @correction: correction takes care of dawn/dusk/etc, one of
+ * %RISESET_CORRECTION_NONE, %RISESET_CORRECTION_CIVIL,
+ * %RISESET_CORRECTION_NAUTICAL, %RISESET_CORRECTION_ASTRONOMICAL
+ * @rise_hour: (out): the hour of sunrise
+ * @rise_min: (out): the min within @rise_hour of sunrise
+ * @set_hour: (out): the hour of sunset
+ * @set_min: (out): the min within @set_hour of sunset
+ *
+ * Calculate sunrise and sunset in a given location, adjusted for @correction
+ * to include/exclude twilight
+ *
+ * Arguments and results are all UTC
+ *
+ * Since: 3.36
+ */
+void
+calculate_sunrise_sunset (double lat,
+ double lon,
+ int year,
+ int month,
+ int day,
+ double correction,
+ int *rise_hour,
+ int *rise_min,
+ int *set_hour,
+ int *set_min)
+{
+ double sunrise_hour;
+ double sunrise_minute;
+ double sunset_hour;
+ double sunset_minute;
+
+ // first we calculate our current Julian date
+ int julian_day_number = ((1461 * (year + 4800 + (month - 14) / 12)) / 4 +
+ (367 * (month - 2 - 12 * ((month - 14) / 12))) / 12 -
+ (3 * ((year + 4900 + (month - 14) / 12) / 100)) / 4 +
+ day - 32075);
+
+ // convert julian date to julian date corrected by Epoch2000
+ int n = julian_day_number - JULIAN_YEAR_2000 + 0.0008;
+
+ // mean solar noon
+ double J = n - lon / 360;
+
+ // solar mean anomaly
+ double M = fmod (357.5291 + 0.98560028 * J, 360.0);
+
+ // equation of the center
+ double C = (1.9148 * sin (RADIANS (M)) +
+ 0.0200 * sin (RADIANS (2 * M)) +
+ 0.0003 * sin (RADIANS (3 * M)));
+
+ // ecliptic longitude
+ double l = fmod(M + C + 180 + 102.9372, 360.0);
+
+ // solar transit
+ double J_transit = (J + JULIAN_YEAR_2000 +
+ 0.0053 * sin (RADIANS (M)) -
+ 0.0069 * sin (RADIANS (2 * l)));
+
+ // sun declination
+ double d = DEGREES (asin (sin (RADIANS (l)) * sin (RADIANS (23.55))));
+
+ // IMPORTANT: for polar circles we can't compute anything for certain dates
+
+ if ((((is_in_north_summer (month) && (lat <= (d - 90))) || (lat >= (90 - d)))) ||
+ (((is_in_north_winter (month) && (lat <= (-d - 90))) || (lat >= (90 + d))))) {
+
+ sunrise_hour = 0;
+ sunrise_minute = 0;
+ sunset_hour = 23;
+ sunset_minute = 59;
+ } else {
+ double sunrise_days;
+ double sunrise_day;
+ double sunrise_hours;
+ double sunset_days;
+ double sunset_day;
+ double sunset_hours;
+ // hour angle
+ double w = DEGREES (acos ((sin (RADIANS (-correction)) + sin (RADIANS (-0.83)) -
+ sin (RADIANS (lat)) * sin (RADIANS (d)))
+ / (cos (RADIANS (lat))) * cos (RADIANS (d))));
+
+ // julian sunrise
+ double J_sunrise = (J_transit - w / 360 - 0.5);
+ double J_sunset = (J_transit + w / 360 - 0.5);
+
+ // convert Julian dates to UTC time (disregarding days in the process)
+ sunrise_days = modf (J_sunrise, &sunrise_day);
+ sunset_days = modf (J_sunset, &sunset_day);
+
+ sunrise_hours = modf (sunrise_days * 24, &sunrise_hour);
+ sunset_hours = modf (sunset_days * 24, &sunset_hour);
+
+ modf (sunrise_hours * 60, &sunrise_minute);
+ modf (sunset_hours * 60, &sunset_minute);
+ }
+
+ if (rise_hour) {
+ *rise_hour = sunrise_hour;
+ }
+
+ if (rise_min) {
+ *rise_min = sunrise_minute;
+ }
+
+ if (set_hour) {
+ *set_hour = sunset_hour;
+ }
+
+ if (set_min) {
+ *set_min = sunset_minute;
+ }
+}
+
diff --git a/src/utils.vala b/src/utils.vala
index db66e54..51d213d 100644
--- a/src/utils.vala
+++ b/src/utils.vala
@@ -17,6 +17,21 @@
*/
extern int clocks_cutils_get_week_start ();
+extern void calculate_sunrise_sunset (double lat,
+ double lon,
+ int year,
+ int month,
+ int day,
+ double correction,
+ out int rise_hour,
+ out int rise_min,
+ out int set_hour,
+ out int set_min);
+
+const double RISESET_CORRECTION_NONE = 0.0;
+const double RISESET_CORRECTION_CIVIL = 6.0;
+const double RISESET_CORRECTION_NAUTICAL = 12.0;
+const double RISESET_CORRECTION_ASTRONOMICAL = 18.0;
namespace Clocks {
namespace Utils {
diff --git a/src/world.vala b/src/world.vala
index e5f460b..0f488a5 100644
--- a/src/world.vala
+++ b/src/world.vala
@@ -199,12 +199,48 @@ public class Item : Object, ContentItem {
}
}
+ // CSS class for the current time of day
+ public string state_class {
+ get {
+ if (date_time.compare (sun_rise) > 0 || date_time.compare (sun_set) < 0) {
+ return "day";
+ }
+
+ if (date_time.compare (civil_rise) > 0 || date_time.compare (civil_set) < 0) {
+ return "civil";
+ }
+
+ if (date_time.compare (naut_rise) > 0 || date_time.compare (naut_set) < 0) {
+ return "naut";
+ }
+
+ if (date_time.compare (astro_rise) > 0 || date_time.compare (astro_set) < 0) {
+ return "astro";
+ }
+
+ return "night";
+ }
+ }
+
private string _name;
private GLib.TimeZone time_zone;
private GLib.DateTime local_time;
private GLib.DateTime date_time;
private GWeather.Info weather_info;
+ // When sunrise/sunset happens, at different corrections, in locations
+ // timezone for calculating the colour pill
+ private DateTime sun_rise;
+ private DateTime sun_set;
+ private DateTime civil_rise;
+ private DateTime civil_set;
+ private DateTime naut_rise;
+ private DateTime naut_set;
+ private DateTime astro_rise;
+ private DateTime astro_set;
+ // When we last calculated
+ private int last_calc_day = -1;
+
public Item (GWeather.Location location) {
Object (location: location);
@@ -214,12 +250,86 @@ public class Item : Object, ContentItem {
tick ();
}
+ private void calculate_riseset () {
+ double lat, lon;
+ int y, m, d;
+ int rise_hour, rise_min;
+ int set_hour, set_min;
+
+ if (date_time.get_day_of_year () == last_calc_day) {
+ return;
+ }
+
+ location.get_coords (out lat, out lon);
+
+ var utc = date_time.to_utc ();
+ utc.get_ymd (out y, out m, out d);
+
+ calculate_sunrise_sunset (lat,
+ lon,
+ y,
+ m,
+ d,
+ RISESET_CORRECTION_NONE,
+ out rise_hour,
+ out rise_min,
+ out set_hour,
+ out set_min);
+
+ sun_rise = new DateTime.utc (y, m, d, rise_hour, rise_min, 0).to_timezone (time_zone);
+ sun_set = new DateTime.utc (y, m, d, set_hour, set_min, 0).to_timezone (time_zone);
+
+ calculate_sunrise_sunset (lat,
+ lon,
+ y,
+ m,
+ d,
+ RISESET_CORRECTION_CIVIL,
+ out rise_hour,
+ out rise_min,
+ out set_hour,
+ out set_min);
+
+ civil_rise = new DateTime.utc (y, m, d, rise_hour, rise_min, 0).to_timezone (time_zone);
+ civil_set = new DateTime.utc (y, m, d, set_hour, set_min, 0).to_timezone (time_zone);
+
+ calculate_sunrise_sunset (lat,
+ lon,
+ y,
+ m,
+ d,
+ RISESET_CORRECTION_NAUTICAL,
+ out rise_hour,
+ out rise_min,
+ out set_hour,
+ out set_min);
+
+ naut_rise = new DateTime.utc (y, m, d, rise_hour, rise_min, 0).to_timezone (time_zone);
+ naut_set = new DateTime.utc (y, m, d, set_hour, set_min, 0).to_timezone (time_zone);
+
+ calculate_sunrise_sunset (lat,
+ lon,
+ y,
+ m,
+ d,
+ RISESET_CORRECTION_ASTRONOMICAL,
+ out rise_hour,
+ out rise_min,
+ out set_hour,
+ out set_min);
+
+ astro_rise = new DateTime.utc (y, m, d, rise_hour, rise_min, 0).to_timezone (time_zone);
+ astro_set = new DateTime.utc (y, m, d, set_hour, set_min, 0).to_timezone (time_zone);
+ }
+
[Signal (run = "first")]
public virtual signal void tick () {
var wallclock = Utils.WallClock.get_default ();
local_time = wallclock.date_time;
date_time = local_time.to_timezone (time_zone);
+ calculate_riseset ();
+
// We don't use the normal constructor since we only want static data
// and we do not want update() to be called.
if (location.has_coords ()) {
@@ -281,11 +391,13 @@ private class Tile : Gtk.ListBoxRow {
}
private void update () {
- if (location.is_daytime) {
- get_style_context ().remove_class ("night");
- } else {
- get_style_context ().add_class ("night");
- }
+ var ctx = get_style_context ();
+ ctx.remove_class ("night");
+ ctx.remove_class ("astro");
+ ctx.remove_class ("naut");
+ ctx.remove_class ("civil");
+ ctx.remove_class ("day");
+ ctx.add_class (location.state_class);
var diff = ((double) location.local_offset / (double) TimeSpan.HOUR);
var diff_string = "%.0f".printf (diff.abs ());
@@ -466,7 +578,7 @@ public class Face : Gtk.Stack, Clocks.Clock {
[GtkCallback]
private void item_activated (Gtk.ListBox list, Gtk.ListBoxRow row) {
- show_standalone ((row as Tile).location);
+ show_standalone (((Tile) row).location);
}
[GtkCallback]
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]