weather: Properly extract and translate weather information.
Completes commit 98cc78d6de66fc3a79abd9148ba03e9b21f89fb1.
This commit is contained in:
committed by
Georges Basile Stavracas Neto
parent
d7c9e70c31
commit
13342ac82e
+277
-26
@@ -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);
|
||||
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!
|
||||
*/
|
||||
|
||||
gweather_info_set_enabled_providers (self->weather_info, GWEATHER_PROVIDER_ALL);
|
||||
/* 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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user