[gnome-clocks/zbrown/world-clocks: 5/5] world: colour coded according to sun in location



commit c95b64b815f6482ea5de39e3d6f772a63e67430f
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 0202a83..13b2f9b 100644
--- a/data/css/gnome-clocks.css
+++ b/data/css/gnome-clocks.css
@@ -275,5 +275,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 b0dea3a..f37809b 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]