[gnome-calendar/wip/flb/weather-forecast: 4/50] weather: Properly extract and translate weather information.
- From: Georges Basile Stavracas Neto <gbsneto src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-calendar/wip/flb/weather-forecast: 4/50] weather: Properly extract and translate weather information.
- Date: Tue, 31 Oct 2017 08:18:09 +0000 (UTC)
commit ba24fe713c19102d1692232b7c5d26d87a6d725d
Author: Florian Brosch <flo brosch gmail com>
Date: Thu Oct 12 23:51:57 2017 +0200
weather: Properly extract and translate weather information.
Completes commit 98cc78d6de66fc3a79abd9148ba03e9b21f89fb1.
src/gcal-weather-service.c | 305 ++++++++++++++++++++++++++++++++++++++++----
src/gcal-weather-service.h | 11 ++-
2 files changed, 287 insertions(+), 29 deletions(-)
---
diff --git a/src/gcal-weather-service.c b/src/gcal-weather-service.c
index 8ce69d1..3a40b85 100644
--- a/src/gcal-weather-service.c
+++ b/src/gcal-weather-service.c
@@ -20,6 +20,7 @@
#include <geocode-glib/geocode-glib.h>
#include <geoclue.h>
+#include <math.h>
#include "gcal-weather-service.h"
@@ -28,11 +29,16 @@ G_BEGIN_DECLS
/* GcalWeatherService:
*
- * @location_service: Used to monitor location changes.
- * Initialized by gcal_weather_service_run(),
- * freed by gcal_weather_service_stop().
- * @location_cancellable: Used to deal with async location service construction.
- * @weather_info: The weather info to query.
+ * @check_interval: Amount of seconds to wait before re-fetching weather infos.
+ * @timeout_id: Time-out event ID or %0. Timer is used to periodically update weather infos.
+ * @location_service: Used to monitor location changes.
+ * Initialized by gcal_weather_service_run(),
+ * freed by gcal_weather_service_stop().
+ * @location_cancellable: Used to deal with async location service construction.
+ * @locaton_running: Whether location service is active.
+ * @weather_info: The weather info to query.
+ * @max_days: Number of days we want weather information for.
+ * @weather_service_running: True if weather service is active.
*
* This service listens to location and weather changes and reports them.
*
@@ -50,19 +56,20 @@ struct _GcalWeatherService
/* <public> */
/* <private> */
+
/* timer: */
- guint check_interval;
- guint timeout_id;
+ guint check_interval;
+ guint timeout_id;
/* locations: */
- GClueSimple *location_service; /* owned, nullable */
- GCancellable *location_cancellable; /* owned, non-null */
- gboolean location_service_running;
+ GClueSimple *location_service; /* owned, nullable */
+ GCancellable *location_cancellable; /* owned, non-null */
+ gboolean location_service_running;
/* weather: */
- GWeatherInfo *weather_info; /* owned, nullable */
- guint max_days;
- gboolean weather_service_running;
+ GWeatherInfo *weather_info; /* owned, nullable */
+ guint max_days;
+ gboolean weather_service_running;
};
@@ -127,6 +134,17 @@ static void gcal_weather_service_timer_start (GcalWeatherService *
static gboolean on_timer_timeout (GcalWeatherService *self);
+static gboolean get_time_day_start (GcalWeatherService *self,
+ GDate *ret_date,
+ gint64 *ret_unix);
+
+static gboolean compute_weather_info_data (GSList *samples,
+ gchar **icon_name,
+ gchar **temperature);
+
+static GSList* preprocess_gweather_reports (GcalWeatherService *self,
+ GSList *samples);
+
G_END_DECLS
@@ -283,6 +301,227 @@ gcal_weather_service_init (GcalWeatherService *self)
* < private >
**************/
+/* get_time_day_start:
+ * @self: The #GcalWeatherService instance.
+ * @date: (out) (not nullable): A #GDate that should be set to today.
+ * @unix: (out) (not nullable): A UNIX time stamp that should be set to today
+ *
+ * Provides current date in two different forms.
+ *
+ * Returns: %TRUE on success.
+ */
+static gboolean
+get_time_day_start (GcalWeatherService *self,
+ GDate *ret_date,
+ gint64 *ret_unix)
+{
+ g_autoptr (GDateTime) now = NULL;
+ g_autoptr (GDateTime) day = NULL;
+
+
+ g_return_val_if_fail (self != NULL, FALSE);
+ g_return_val_if_fail (ret_date != NULL, FALSE);
+ g_return_val_if_fail (ret_unix != NULL, FALSE);
+
+ now = g_date_time_new_now_local ();
+ day = g_date_time_add_full (now,
+ 0, /* years */
+ 0, /* months */
+ 0, /* days */
+ - g_date_time_get_hour (now),
+ - g_date_time_get_minute (now),
+ - g_date_time_get_seconds (now));
+
+ g_date_set_dmy (ret_date,
+ g_date_time_get_day_of_month (day),
+ g_date_time_get_month (day),
+ g_date_time_get_year (day));
+
+ *ret_unix = g_date_time_to_unix (day);
+ return TRUE;
+}
+
+
+
+/* compute_weather_info_data:
+ * @samples: List of received #GWeatherInfos.
+ * @icon_name: (out): (transfer full): weather icon name or %NULL.
+ * @temperature: (out): (transfer full): temperature and unit or %NULL.
+ *
+ * Computes a icon name and temperature representing @samples.
+ *
+ * Returns: %TRUE if there is a valid icon name and temperature.
+ */
+static gboolean
+compute_weather_info_data (GSList *samples,
+ gchar **icon_name,
+ gchar **temperature)
+{
+ GWeatherConditionPhenomenon phenomenon_val = GWEATHER_PHENOMENON_INVALID;
+ GWeatherInfo *phenomenon_gwi = NULL; /* unowned */
+ gdouble temp_val = NAN;
+ GWeatherInfo *temp_gwi = NULL; /* unowned */
+ GSList *iter; /* unowned */
+
+ /* Note: I checked three different gweather consumers
+ * and they all pick different values. So here is my
+ * take: I pick up the worst weather for icons and
+ * the highest temperature. I basically want to know
+ * whether I need my umbrella for my appointment.
+ * Not sure about the right temperature. It is probably
+ * better to pick-up the median of all predictions
+ * during daytime.
+ */
+
+ g_return_val_if_fail (icon_name != NULL, FALSE);
+ g_return_val_if_fail (temperature != NULL, FALSE);
+
+ for (iter = samples; iter != NULL; iter = iter->next)
+ {
+ GWeatherInfo *gwi; /* unowned */
+ GWeatherConditionPhenomenon phenomenon;
+ GWeatherConditionQualifier _qualifier;
+ gboolean valid_val;
+ gdouble temp;
+
+ gwi = GWEATHER_INFO (iter->data);
+
+ valid_val = gweather_info_get_value_temp (gwi,
+ GWEATHER_TEMP_UNIT_DEFAULT,
+ &temp);
+ if (!valid_val || isnan (temp))
+ continue;
+
+ valid_val = gweather_info_get_value_conditions (gwi, &phenomenon, &_qualifier);
+ if (!valid_val || valid_val == GWEATHER_PHENOMENON_INVALID || valid_val == GWEATHER_PHENOMENON_LAST)
+ continue;
+
+ if (phenomenon_gwi == NULL || phenomenon > phenomenon_val)
+ {
+ phenomenon_val = phenomenon;
+ phenomenon_gwi = gwi;
+ }
+ if (temp_gwi == NULL || temp > temp_val)
+ {
+ temp_val = temp;
+ temp_gwi = gwi;
+ }
+ }
+
+ if (phenomenon_gwi != NULL && temp_gwi != NULL)
+ {
+ *icon_name = g_strdup (gweather_info_get_symbolic_icon_name (phenomenon_gwi));
+ *temperature = gweather_info_get_temp (temp_gwi);
+ return TRUE;
+ }
+ else
+ {
+ /* empty list */
+ *icon_name = NULL;
+ *temperature = NULL;
+ return FALSE;
+ }
+}
+
+
+
+/* preprocess_gweather_reports:
+ * @self: The #GcalWeatherService instance.
+ * @samples: Received list of #GWeatherInfos
+ *
+ * Computes weather info objects representing specific days
+ * by combining given #GWeatherInfos.
+ *
+ * Returns: (transfer full): List of up to $self->max_days #GcalWeatherInfos.
+ */
+static GSList*
+preprocess_gweather_reports (GcalWeatherService *self,
+ GSList *samples)
+{
+ const glong DAY_SECONDS = 24*60*60;
+ GSList *result = NULL; /* owned[owned] */
+ GSList **days; /* owned[owned[unowned]] */
+ GSList *iter; /* unowned */
+ GDate cur_gdate;
+ glong today_unix;
+
+ g_return_val_if_fail (GCAL_IS_WEATHER_SERVICE (self), NULL);
+
+ /* This function basically separates samples by
+ * date and calls compute_weather_info_data for
+ * every bucket to build weather infos.
+ * Each bucket represents a single day.
+ */
+
+ /* All gweather consumers I reviewed presume
+ * sorted samples. However, there is no documented
+ * order. Lets assume the worst.
+ */
+
+ if (self->max_days <= 0)
+ return NULL;
+
+ if (!get_time_day_start (self, &cur_gdate, &today_unix))
+ return NULL;
+
+ days = g_malloc0 (sizeof (GSList*) * self->max_days);
+
+ /* Split samples to max_days buckets: */
+ for (iter = samples; iter != NULL; iter = iter->next)
+ {
+ GWeatherInfo *gwi; /* unowned */
+ gboolean valid_date;
+ glong gwi_dtime;
+ gsize bucket;
+
+ gwi = GWEATHER_INFO (iter->data);
+ valid_date = gweather_info_get_value_update (gwi, &gwi_dtime);
+ if (!valid_date)
+ continue;
+
+ if (gwi_dtime >= 0 && gwi_dtime >= today_unix)
+ {
+ bucket = (gwi_dtime - today_unix) / DAY_SECONDS;
+ if (bucket >= 0 && bucket < self->max_days)
+ days[bucket] = g_slist_prepend (days[bucket], gwi);
+ }
+ else
+ {
+ g_info ("Received historic weather information.");
+ }
+ }
+
+ /* Produce GcalWeatherInfo for each bucket: */
+ for (int i = 0; i < self->max_days; i++)
+ {
+ g_autofree gchar *icon_name;
+ g_autofree gchar *temperature;
+
+ if (compute_weather_info_data (days[i], &icon_name, &temperature))
+ {
+ GcalWeatherInfo* gcwi; /* owned */
+
+ gcwi = gcal_weather_info_new (&cur_gdate,
+ icon_name,
+ temperature);
+
+ result = g_slist_prepend (result,
+ g_steal_pointer (&gcwi));
+ }
+
+ g_date_add_days (&cur_gdate, 1);
+ }
+
+ /* Cleanup: */
+ for (int i = 0; i < self->max_days; i++)
+ g_slist_free (days[i]);
+ g_free (days);
+
+ return result;
+}
+
+
+
/**
* gcal_weather_service_update_location:
* @self: The #GcalWeatherService instance.
@@ -309,9 +548,17 @@ gcal_weather_service_update_location (GcalWeatherService *self,
}
else
{
- self->weather_info = gweather_info_new (location, GWEATHER_FORECAST_ZONE);
-
- gweather_info_set_enabled_providers (self->weather_info, GWEATHER_PROVIDER_ALL);
+ self->weather_info = gweather_info_new (location, GWEATHER_FORECAST_ZONE | GWEATHER_FORECAST_LIST);
+ /* TODO: display weather attributions somewhere:
+ * gweather_info_get_attribution (self->weather_info);
+ * Do no roll-out a release without resolving this one before!
+ */
+
+ /* NOTE: We do not get detailed infos for GWEATHER_PROVIDER_ALL.
+ * This combination works fine, though. We should open a bug / investigate
+ * what is going on.
+ */
+ gweather_info_set_enabled_providers (self->weather_info, GWEATHER_PROVIDER_METAR |
GWEATHER_PROVIDER_OWM | GWEATHER_PROVIDER_YR_NO);
g_signal_connect (self->weather_info, "updated", (GCallback) gcal_weather_service_update_weather,
self);
gweather_info_update (self->weather_info);
@@ -559,17 +806,22 @@ static void
gcal_weather_service_update_weather (GWeatherInfo *info,
GcalWeatherService *self)
{
- GSList* infos = NULL;
+ GSList *gcinfos = NULL; /* owned[owned] */
+ g_return_if_fail (info == NULL || GWEATHER_IS_INFO (info));
g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
+ /* Compute a list of weather infos. */
if (info == NULL)
{
g_info ("Could not retrieve valid weather");
}
else if (gweather_info_is_valid (info))
{
- // TODO
+ GSList *gwforecast; /* unowned */
+
+ gwforecast = gweather_info_get_forecast_list (info);
+ gcinfos = preprocess_gweather_reports (self, gwforecast);
}
else
{
@@ -577,7 +829,8 @@ gcal_weather_service_update_weather (GWeatherInfo *info,
g_info ("Could not retrieve valid weather for location '%s'", location_name);
}
- g_signal_emit (self, gcal_weather_service_signals[SIG_WEATHER_CHANGED], 0, infos);
+ g_signal_emit (self, gcal_weather_service_signals[SIG_WEATHER_CHANGED], 0, gcinfos);
+ g_slist_free_full (gcinfos, g_object_unref);
}
@@ -688,14 +941,12 @@ on_timer_timeout (GcalWeatherService *self)
* Returns: (transfer full): A newly created #GcalWeatherService.
*/
GcalWeatherService *
-gcal_weather_service_new (guint max_days,
- guint check_interval)
+gcal_weather_service_new (guint max_days,
+ guint check_interval)
{
return g_object_new (GCAL_TYPE_WEATHER_SERVICE,
- "max-days",
- max_days,
- "check-interval",
- check_interval,
+ "max-days", max_days,
+ "check-interval", check_interval,
NULL);
}
@@ -722,7 +973,7 @@ gcal_weather_service_run (GcalWeatherService *self,
{
/* Start location and weather service: */
self->location_service_running = TRUE;
- self->location_service_running = TRUE;
+ self->weather_service_running = TRUE;
g_cancellable_cancel (self->location_cancellable);
g_cancellable_reset (self->location_cancellable);
gclue_simple_new (DESKTOP_FILE_NAME,
@@ -735,7 +986,7 @@ gcal_weather_service_run (GcalWeatherService *self,
{
/* Use the given location to retrieve weather information: */
self->location_service_running = FALSE;
- self->location_service_running = TRUE;
+ self->weather_service_running = TRUE;
gcal_weather_service_update_location (self, location);
gcal_weather_service_timer_start (self);
diff --git a/src/gcal-weather-service.h b/src/gcal-weather-service.h
index ea2fbd9..8c83ac5 100644
--- a/src/gcal-weather-service.h
+++ b/src/gcal-weather-service.h
@@ -23,6 +23,8 @@
#include <glib-object.h>
#include <glib.h>
+#include "gcal-weather-info.h"
+
G_BEGIN_DECLS
#define GCAL_TYPE_WEATHER_SERVICE (gcal_weather_service_get_type())
@@ -30,8 +32,13 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (GcalWeatherService, gcal_weather_service, GCAL, WEATHER_SERVICE, GObject)
-GcalWeatherService* gcal_weather_service_new (guint max_days,
- guint check_interval);
+GcalWeatherService* gcal_weather_service_new (guint max_days,
+ guint check_interval);
+
+GTimeZone* gcal_weather_service_get_time_zone (GcalWeatherService *self);
+
+void gcal_weather_service_set_time_zone (GcalWeatherService *self,
+ GTimeZone *value);
void gcal_weather_service_run (GcalWeatherService *self,
GWeatherLocation *location);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]