1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /* weather.c - Overall weather server functions
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, see
16  * <http://www.gnu.org/licenses/>.
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <assert.h>
26 #include <string.h>
27 #include <ctype.h>
28 #include <math.h>
29 #include <fenv.h>
30 
31 #ifdef HAVE_VALUES_H
32 #include <values.h>
33 #endif
34 
35 #include <time.h>
36 #include <unistd.h>
37 
38 #include <gdk-pixbuf/gdk-pixbuf.h>
39 
40 #define MATEWEATHER_I_KNOW_THIS_IS_UNSTABLE
41 #include "weather.h"
42 #include "weather-priv.h"
43 
44 #define MOON_PHASES 36
45 
46 /**
47  * SECTION:weather
48  * @Title: weather
49  */
50 
51 static void _weather_internal_check (void);
52 
53 
54 static inline void
mateweather_gettext_init(void)55 mateweather_gettext_init (void)
56 {
57     static gsize mateweather_gettext_initialized = FALSE;
58 
59     if (G_UNLIKELY (g_once_init_enter (&mateweather_gettext_initialized))) {
60         bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR);
61 #ifdef HAVE_BIND_TEXTDOMAIN_CODESET
62         bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
63 #endif
64         g_once_init_leave (&mateweather_gettext_initialized, TRUE);
65     }
66 }
67 
68 const char *
mateweather_gettext(const char * str)69 mateweather_gettext (const char *str)
70 {
71     mateweather_gettext_init ();
72     return dgettext (GETTEXT_PACKAGE, str);
73 }
74 
75 const char *
mateweather_dpgettext(const char * context,const char * str)76 mateweather_dpgettext (const char *context,
77                     const char *str)
78 {
79     mateweather_gettext_init ();
80     return g_dpgettext2 (GETTEXT_PACKAGE, context, str);
81 }
82 
83 /*
84  * Convert string of the form "DD-MM-SSH" to radians
85  * DD:degrees (to 3 digits), MM:minutes, SS:seconds H:hemisphere (NESW)
86  * Return value is positive for N,E; negative for S,W.
87  */
88 static gdouble
dmsh2rad(const gchar * latlon)89 dmsh2rad (const gchar *latlon)
90 {
91     char *p1, *p2;
92     int deg, min, sec, dir;
93     gdouble value;
94 
95     if (latlon == NULL)
96 	return DBL_MAX;
97     p1 = strchr (latlon, '-');
98     p2 = strrchr (latlon, '-');
99     if (p1 == NULL || p1 == latlon) {
100         return DBL_MAX;
101     } else if (p1 == p2) {
102 	sscanf (latlon, "%d-%d", &deg, &min);
103 	sec = 0;
104     } else if (p2 == 1 + p1) {
105 	return DBL_MAX;
106     } else {
107 	sscanf (latlon, "%d-%d-%d", &deg, &min, &sec);
108     }
109     if (deg > 180 || min >= 60 || sec >= 60)
110 	return DBL_MAX;
111     value = (gdouble)((deg * 60 + min) * 60 + sec) * M_PI / 648000.;
112 
113     dir = g_ascii_toupper (latlon[strlen (latlon) - 1]);
114     if (dir == 'W' || dir == 'S')
115 	value = -value;
116     else if (dir != 'E' && dir != 'N' && (value != 0.0 || dir != '0'))
117 	value = DBL_MAX;
118     return value;
119 }
120 
121 WeatherLocation *
weather_location_new(const gchar * name,const gchar * code,const gchar * zone,const gchar * radar,const gchar * coordinates,const gchar * country_code,const gchar * tz_hint)122 weather_location_new (const gchar *name, const gchar *code,
123 		      const gchar *zone, const gchar *radar,
124 		      const gchar *coordinates,
125 		      const gchar *country_code,
126 		      const gchar *tz_hint)
127 {
128     WeatherLocation *location;
129 
130     _weather_internal_check ();
131 
132     location = g_new (WeatherLocation, 1);
133 
134     /* name and metar code must be set */
135     location->name = g_strdup (name);
136     location->code = g_strdup (code);
137 
138     if (zone) {
139         location->zone = g_strdup (zone);
140     } else {
141         location->zone = g_strdup ("------");
142     }
143 
144     if (radar) {
145         location->radar = g_strdup (radar);
146     } else {
147         location->radar = g_strdup ("---");
148     }
149 
150     if (location->zone[0] == '-') {
151         location->zone_valid = FALSE;
152     } else {
153         location->zone_valid = TRUE;
154     }
155 
156     location->coordinates = NULL;
157     if (coordinates)
158     {
159 	char **pieces;
160 
161 	pieces = g_strsplit (coordinates, " ", -1);
162 
163 	if (g_strv_length (pieces) == 2)
164 	{
165             location->coordinates = g_strdup (coordinates);
166             location->latitude = dmsh2rad (pieces[0]);
167 	    location->longitude = dmsh2rad (pieces[1]);
168 	}
169 
170 	g_strfreev (pieces);
171     }
172 
173     if (!location->coordinates)
174     {
175         location->coordinates = g_strdup ("---");
176         location->latitude = DBL_MAX;
177         location->longitude = DBL_MAX;
178     }
179 
180     location->latlon_valid = (location->latitude < DBL_MAX && location->longitude < DBL_MAX);
181 
182     location->country_code = g_strdup (country_code);
183     location->tz_hint = g_strdup (tz_hint);
184 
185     return location;
186 }
187 
188 WeatherLocation *
weather_location_clone(const WeatherLocation * location)189 weather_location_clone (const WeatherLocation *location)
190 {
191     WeatherLocation *clone;
192 
193     g_return_val_if_fail (location != NULL, NULL);
194 
195     clone = weather_location_new (location->name,
196 				  location->code, location->zone,
197 				  location->radar, location->coordinates,
198 				  location->country_code, location->tz_hint);
199     clone->latitude = location->latitude;
200     clone->longitude = location->longitude;
201     clone->latlon_valid = location->latlon_valid;
202     return clone;
203 }
204 
205 void
weather_location_free(WeatherLocation * location)206 weather_location_free (WeatherLocation *location)
207 {
208     if (location) {
209         g_free (location->name);
210         g_free (location->code);
211         g_free (location->zone);
212         g_free (location->radar);
213         g_free (location->coordinates);
214         g_free (location->country_code);
215         g_free (location->tz_hint);
216 
217         g_free (location);
218     }
219 }
220 
221 gboolean
weather_location_equal(const WeatherLocation * location1,const WeatherLocation * location2)222 weather_location_equal (const WeatherLocation *location1, const WeatherLocation *location2)
223 {
224     /* if something is NULL, then it's TRUE if and only if both are NULL) */
225     if (location1 == NULL || location2 == NULL)
226         return (location1 == location2);
227     if (!location1->code || !location2->code)
228         return (location1->code == location2->code);
229     if (!location1->name || !location2->name)
230         return (location1->name == location2->name);
231 
232     return ((strcmp (location1->code, location2->code) == 0) &&
233 	    (strcmp (location1->name, location2->name) == 0));
234 }
235 
236 static const gchar *wind_direction_str[] = {
237     N_("Variable"),
238     N_("North"), N_("North - NorthEast"), N_("Northeast"), N_("East - NorthEast"),
239     N_("East"), N_("East - Southeast"), N_("Southeast"), N_("South - Southeast"),
240     N_("South"), N_("South - Southwest"), N_("Southwest"), N_("West - Southwest"),
241     N_("West"), N_("West - Northwest"), N_("Northwest"), N_("North - Northwest")
242 };
243 
244 const gchar *
weather_wind_direction_string(WeatherWindDirection wind)245 weather_wind_direction_string (WeatherWindDirection wind)
246 {
247     if (wind <= WIND_INVALID || wind >= WIND_LAST)
248 	return _("Invalid");
249 
250     return _(wind_direction_str[(int)wind]);
251 }
252 
253 static const gchar *sky_str[] = {
254     N_("Clear Sky"),
255     N_("Broken clouds"),
256     N_("Scattered clouds"),
257     N_("Few clouds"),
258     N_("Overcast")
259 };
260 
261 const gchar *
weather_sky_string(WeatherSky sky)262 weather_sky_string (WeatherSky sky)
263 {
264     if (sky <= SKY_INVALID || sky >= SKY_LAST)
265 	return _("Invalid");
266 
267     return _(sky_str[(int)sky]);
268 }
269 
270 
271 /*
272  * Even though tedious, I switched to a 2D array for weather condition
273  * strings, in order to facilitate internationalization, esp. for languages
274  * with genders.
275  */
276 
277 /*
278  * Almost all reportable combinations listed in
279  * http://www.crh.noaa.gov/arx/wx.tbl.php are entered below, except those
280  * having 2 qualifiers mixed together [such as "Blowing snow in vicinity"
281  * (VCBLSN), "Thunderstorm in vicinity" (VCTS), etc].
282  * Combinations that are not possible are filled in with "??".
283  * Some other exceptions not handled yet, such as "SN BLSN" which has
284  * special meaning.
285  */
286 
287 /*
288  * Note, magic numbers, when you change the size here, make sure to change
289  * the below function so that new values are recognized
290  */
291 /*                   NONE                         VICINITY                             LIGHT                      MODERATE                      HEAVY                      SHALLOW                      PATCHES                         PARTIAL                      THUNDERSTORM                    BLOWING                      SHOWERS                         DRIFTING                      FREEZING                      */
292 /*               *******************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/
293 static const gchar *conditions_str[24][13] = {
294 /* Translators: If you want to know what "blowing" "shallow" "partial"
295  * etc means, you can go to http://www.weather.com/glossary/ and
296  * http://www.crh.noaa.gov/arx/wx.tbl.php */
297     /* NONE          */ {"??",                        "??",                                "??",                      "??",                         "??",                      "??",                        "??",                           "??",                        N_("Thunderstorm"),             "??",                        "??",                           "??",                         "??"                         },
298     /* DRIZZLE       */ {N_("Drizzle"),               "??",                                N_("Light drizzle"),       N_("Moderate drizzle"),       N_("Heavy drizzle"),       "??",                        "??",                           "??",                        "??",                           "??",                        "??",                           "??",                         N_("Freezing drizzle")       },
299     /* RAIN          */ {N_("Rain"),                  "??",                                N_("Light rain"),          N_("Moderate rain"),          N_("Heavy rain"),          "??",                        "??",                           "??",                        N_("Thunderstorm"),             "??",                        N_("Rain showers"),             "??",                         N_("Freezing rain")          },
300     /* SNOW          */ {N_("Snow"),                  "??",                                N_("Light snow"),          N_("Moderate snow"),          N_("Heavy snow"),          "??",                        "??",                           "??",                        N_("Snowstorm"),                N_("Blowing snowfall"),      N_("Snow showers"),             N_("Drifting snow"),          "??"                         },
301     /* SNOW_GRAINS   */ {N_("Snow grains"),           "??",                                N_("Light snow grains"),   N_("Moderate snow grains"),   N_("Heavy snow grains"),   "??",                        "??",                           "??",                        "??",                           "??",                        "??",                           "??",                         "??"                         },
302     /* ICE_CRYSTALS  */ {N_("Ice crystals"),          "??",                                "??",                      N_("Ice crystals"),           "??",                      "??",                        "??",                           "??",                        "??",                           "??",                        "??",                           "??",                         "??"                         },
303     /* ICE_PELLETS   */ {N_("Ice pellets"),           "??",                                N_("Few ice pellets"),     N_("Moderate ice pellets"),   N_("Heavy ice pellets"),   "??",                        "??",                           "??",                        N_("Ice pellet storm"),         "??",                        N_("Showers of ice pellets"),   "??",                         "??"                         },
304     /* HAIL          */ {N_("Hail"),                  "??",                                "??",                      N_("Hail"),                   "??",                      "??",                        "??",                           "??",                        N_("Hailstorm"),                "??",                        N_("Hail showers"),             "??",                         "??",                        },
305     /* SMALL_HAIL    */ {N_("Small hail"),            "??",                                "??",                      N_("Small hail"),             "??",                      "??",                        "??",                           "??",                        N_("Small hailstorm"),          "??",                        N_("Showers of small hail"),    "??",                         "??"                         },
306     /* PRECIPITATION */ {N_("Unknown precipitation"), "??",                                "??",                      "??",                         "??",                      "??",                        "??",                           "??",                        "??",                           "??",                        "??",                           "??",                         "??"                         },
307     /* MIST          */ {N_("Mist"),                  "??",                                "??",                      N_("Mist"),                   "??",                      "??",                        "??",                           "??",                        "??",                           "??",                        "??",                           "??",                         "??"                         },
308     /* FOG           */ {N_("Fog"),                   N_("Fog in the vicinity") ,          "??",                      N_("Fog"),                    "??",                      N_("Shallow fog"),           N_("Patches of fog"),           N_("Partial fog"),           "??",                           "??",                        "??",                           "??",                         N_("Freezing fog")           },
309     /* SMOKE         */ {N_("Smoke"),                 "??",                                "??",                      N_("Smoke"),                  "??",                      "??",                        "??",                           "??",                        "??",                           "??",                        "??",                           "??",                         "??"                         },
310     /* VOLCANIC_ASH  */ {N_("Volcanic ash"),          "??",                                "??",                      N_("Volcanic ash"),           "??",                      "??",                        "??",                           "??",                        "??",                           "??",                        "??",                           "??",                         "??"                         },
311     /* SAND          */ {N_("Sand"),                  "??",                                "??",                      N_("Sand"),                   "??",                      "??",                        "??",                           "??",                        "??",                           N_("Blowing sand"),          "",                             N_("Drifting sand"),          "??"                         },
312     /* HAZE          */ {N_("Haze"),                  "??",                                "??",                      N_("Haze"),                   "??",                      "??",                        "??",                           "??",                        "??",                           "??",                        "??",                           "??",                         "??"                         },
313     /* SPRAY         */ {"??",                        "??",                                "??",                      "??",                         "??",                      "??",                        "??",                           "??",                        "??",                           N_("Blowing sprays"),        "??",                           "??",                         "??"                         },
314     /* DUST          */ {N_("Dust"),                  "??",                                "??",                      N_("Dust"),                   "??",                      "??",                        "??",                           "??",                        "??",                           N_("Blowing dust"),          "??",                           N_("Drifting dust"),          "??"                         },
315     /* SQUALL        */ {N_("Squall"),                "??",                                "??",                      N_("Squall"),                 "??",                      "??",                        "??",                           "??",                        "??",                           "??",                        "??",                           "??",                         "??"                         },
316     /* SANDSTORM     */ {N_("Sandstorm"),             N_("Sandstorm in the vicinity") ,    "??",                      N_("Sandstorm"),              N_("Heavy sandstorm"),     "??",                        "??",                           "??",                        "??",                           "??",                        "??",                           "??",                         "??"                         },
317     /* DUSTSTORM     */ {N_("Duststorm"),             N_("Duststorm in the vicinity") ,    "??",                      N_("Duststorm"),              N_("Heavy duststorm"),     "??",                        "??",                           "??",                        "??",                           "??",                        "??",                           "??",                         "??"                         },
318     /* FUNNEL_CLOUD  */ {N_("Funnel cloud"),          "??",                                "??",                      "??",                         "??",                      "??",                        "??",                           "??",                        "??",                           "??",                        "??",                           "??",                         "??"                         },
319     /* TORNADO       */ {N_("Tornado"),               "??",                                "??",                      "??",                         "??",                      "??",                        "??",                           "??",                        "??",                           "??",                        "??",                           "??",                         "??"                         },
320     /* DUST_WHIRLS   */ {N_("Dust whirls"),           N_("Dust whirls in the vicinity") ,  "??",                      N_("Dust whirls"),            "??",                      "??",                        "??",                           "??",                        "??",                           "??",                        "??",                           "??",                         "??"                         }
321 };
322 
323 const gchar *
weather_conditions_string(WeatherConditions cond)324 weather_conditions_string (WeatherConditions cond)
325 {
326     const gchar *str;
327 
328     if (!cond.significant) {
329 	return "-";
330     } else {
331 	if (cond.phenomenon > PHENOMENON_INVALID &&
332 	    cond.phenomenon < PHENOMENON_LAST &&
333 	    cond.qualifier > QUALIFIER_INVALID &&
334 	    cond.qualifier < QUALIFIER_LAST)
335 	    str = _(conditions_str[(int)cond.phenomenon][(int)cond.qualifier]);
336 	else
337 	    str = _("Invalid");
338 	return (strlen (str) > 0) ? str : "-";
339     }
340 }
341 
342 /* Locals turned global to facilitate asynchronous HTTP requests */
343 
344 
345 gboolean
requests_init(WeatherInfo * info)346 requests_init (WeatherInfo *info)
347 {
348     if (info->requests_pending)
349         return FALSE;
350 
351     return TRUE;
352 }
353 
request_done(WeatherInfo * info,gboolean ok)354 void request_done (WeatherInfo *info, gboolean ok)
355 {
356     if (ok) {
357 	(void) calc_sun (info);
358 	info->moonValid = info->valid && calc_moon (info);
359     }
360     if (!--info->requests_pending)
361         info->finish_cb (info, info->cb_data);
362 }
363 
364 /* it's OK to pass in NULL */
365 void
free_forecast_list(WeatherInfo * info)366 free_forecast_list (WeatherInfo *info)
367 {
368     GSList *p;
369 
370     if (!info)
371 	return;
372 
373     for (p = info->forecast_list; p; p = p->next)
374 	weather_info_free (p->data);
375 
376     if (info->forecast_list) {
377 	g_slist_free (info->forecast_list);
378 	info->forecast_list = NULL;
379     }
380 }
381 
382 /* Relative humidity computation - thanks to <Olof.Oberg@modopaper.modogroup.com> */
383 
384 static inline gdouble
calc_humidity(gdouble temp,gdouble dewp)385 calc_humidity (gdouble temp, gdouble dewp)
386 {
387     gdouble esat, esurf;
388 
389     if (temp > -500.0 && dewp > -500.0) {
390 	temp = TEMP_F_TO_C (temp);
391 	dewp = TEMP_F_TO_C (dewp);
392 
393 	esat = 6.11 * pow (10.0, (7.5 * temp) / (237.7 + temp));
394 	esurf = 6.11 * pow (10.0, (7.5 * dewp) / (237.7 + dewp));
395     } else {
396 	esurf = -1.0;
397 	esat = 1.0;
398     }
399     return ((esurf/esat) * 100.0);
400 }
401 
402 static inline gdouble
calc_apparent(WeatherInfo * info)403 calc_apparent (WeatherInfo *info)
404 {
405     gdouble temp = info->temp;
406     gdouble wind = WINDSPEED_KNOTS_TO_MPH (info->windspeed);
407     gdouble apparent = -1000.;
408 
409     /*
410      * Wind chill calculations as of 01-Nov-2001
411      * http://www.nws.noaa.gov/om/windchill/index.shtml
412      * Some pages suggest that the formula will soon be adjusted
413      * to account for solar radiation (bright sun vs cloudy sky)
414      */
415     if (temp <= 50.0) {
416         if (wind > 3.0) {
417 	    gdouble v = pow (wind, 0.16);
418 	    apparent = 35.74 + 0.6215 * temp - 35.75 * v + 0.4275 * temp * v;
419 	} else if (wind >= 0.) {
420 	    apparent = temp;
421 	}
422     }
423     /*
424      * Heat index calculations:
425      * http://www.srh.noaa.gov/fwd/heatindex/heat5.html
426      */
427     else if (temp >= 80.0) {
428         if (info->temp >= -500. && info->dew >= -500.) {
429 	    gdouble humidity = calc_humidity (info->temp, info->dew);
430 	    gdouble t2 = temp * temp;
431 	    gdouble h2 = humidity * humidity;
432 
433 #if 1
434 	    /*
435 	     * A really precise formula.  Note that overall precision is
436 	     * constrained by the accuracy of the instruments and that the
437 	     * we receive the temperature and dewpoints as integers.
438 	     */
439 	    gdouble t3 = t2 * temp;
440 	    gdouble h3 = h2 * temp;
441 
442 	    apparent = 16.923
443 		+ 0.185212 * temp
444 		+ 5.37941 * humidity
445 		- 0.100254 * temp * humidity
446 		+ 9.41695e-3 * t2
447 		+ 7.28898e-3 * h2
448 		+ 3.45372e-4 * t2 * humidity
449 		- 8.14971e-4 * temp * h2
450 		+ 1.02102e-5 * t2 * h2
451 		- 3.8646e-5 * t3
452 		+ 2.91583e-5 * h3
453 		+ 1.42721e-6 * t3 * humidity
454 		+ 1.97483e-7 * temp * h3
455 		- 2.18429e-8 * t3 * h2
456 		+ 8.43296e-10 * t2 * h3
457 		- 4.81975e-11 * t3 * h3;
458 #else
459 	    /*
460 	     * An often cited alternative: values are within 5 degrees for
461 	     * most ranges between 10% and 70% humidity and to 110 degrees.
462 	     */
463 	    apparent = - 42.379
464 		+  2.04901523 * temp
465 		+ 10.14333127 * humidity
466 		-  0.22475541 * temp * humidity
467 		-  6.83783e-3 * t2
468 		-  5.481717e-2 * h2
469 		+  1.22874e-3 * t2 * humidity
470 		+  8.5282e-4 * temp * h2
471 		-  1.99e-6 * t2 * h2;
472 #endif
473 	}
474     } else {
475         apparent = temp;
476     }
477 
478     return apparent;
479 }
480 
481 WeatherInfo *
_weather_info_fill(WeatherInfo * info,WeatherLocation * location,const WeatherPrefs * prefs,WeatherInfoFunc cb,gpointer data)482 _weather_info_fill (WeatherInfo *info,
483 		    WeatherLocation *location,
484 		    const WeatherPrefs *prefs,
485 		    WeatherInfoFunc cb,
486 		    gpointer data)
487 {
488     g_return_val_if_fail (((info == NULL) && (location != NULL)) || \
489 			  ((info != NULL) && (location == NULL)), NULL);
490     g_return_val_if_fail (prefs != NULL, NULL);
491 
492     /* FIXME: i'm not sure this works as intended anymore */
493     if (!info) {
494     	info = g_new0 (WeatherInfo, 1);
495     	info->requests_pending = 0;
496     	info->location = weather_location_clone (location);
497     } else {
498         location = info->location;
499 	if (info->forecast)
500 	    g_free (info->forecast);
501 	info->forecast = NULL;
502 
503 	free_forecast_list (info);
504 
505 	if (info->radar != NULL) {
506 	    g_object_unref (info->radar);
507 	    info->radar = NULL;
508 	}
509     }
510 
511     /* Update in progress */
512     if (!requests_init (info)) {
513         return NULL;
514     }
515 
516     /* Defaults (just in case...) */
517     /* Well, no just in case anymore.  We may actually fail to fetch some
518      * fields. */
519     info->forecast_type = prefs->type;
520 
521     info->temperature_unit = prefs->temperature_unit;
522     info->speed_unit = prefs->speed_unit;
523     info->pressure_unit = prefs->pressure_unit;
524     info->distance_unit = prefs->distance_unit;
525 
526     info->update = 0;
527     info->sky = -1;
528     info->cond.significant = FALSE;
529     info->cond.phenomenon = PHENOMENON_NONE;
530     info->cond.qualifier = QUALIFIER_NONE;
531     info->temp = -1000.0;
532     info->tempMinMaxValid = FALSE;
533     info->temp_min = -1000.0;
534     info->temp_max = -1000.0;
535     info->dew = -1000.0;
536     info->wind = -1;
537     info->windspeed = -1;
538     info->pressure = -1.0;
539     info->visibility = -1.0;
540     info->sunriseValid = FALSE;
541     info->sunsetValid = FALSE;
542     info->moonValid = FALSE;
543     info->sunrise = 0;
544     info->sunset = 0;
545     info->moonphase = 0;
546     info->moonlatitude = 0;
547     info->forecast = NULL;
548     info->forecast_list = NULL;
549     info->radar = NULL;
550     info->radar_url = prefs->radar && prefs->radar_custom_url ?
551     		      g_strdup (prefs->radar_custom_url) : NULL;
552     info->finish_cb = cb;
553     info->cb_data = data;
554 
555     if (!info->session) {
556         info->session = soup_session_new ();
557     }
558 
559     metar_start_open (info);
560     iwin_start_open (info);
561 
562     if (prefs->radar) {
563         wx_start_open (info);
564     }
565 
566     return info;
567 }
568 
569 void
weather_info_abort(WeatherInfo * info)570 weather_info_abort (WeatherInfo *info)
571 {
572     g_return_if_fail (info != NULL);
573 
574     if (info->session) {
575 	soup_session_abort (info->session);
576 	info->requests_pending = 0;
577     }
578 }
579 
580 WeatherInfo *
weather_info_clone(const WeatherInfo * info)581 weather_info_clone (const WeatherInfo *info)
582 {
583     WeatherInfo *clone;
584 
585     g_return_val_if_fail (info != NULL, NULL);
586 
587     clone = g_new (WeatherInfo, 1);
588 
589 
590     /* move everything */
591     memmove (clone, info, sizeof (WeatherInfo));
592 
593 
594     /* special moves */
595     clone->location = weather_location_clone (info->location);
596     /* This handles null correctly */
597     clone->forecast = g_strdup (info->forecast);
598     clone->radar_url = g_strdup (info->radar_url);
599 
600     if (info->forecast_list) {
601 	GSList *p;
602 
603 	clone->forecast_list = NULL;
604 	for (p = info->forecast_list; p; p = p->next) {
605 	    clone->forecast_list = g_slist_prepend (clone->forecast_list, weather_info_clone (p->data));
606 	}
607 
608 	clone->forecast_list = g_slist_reverse (clone->forecast_list);
609     }
610 
611     clone->radar = info->radar;
612     if (clone->radar != NULL)
613 	g_object_ref (clone->radar);
614 
615     return clone;
616 }
617 
618 void
weather_info_free(WeatherInfo * info)619 weather_info_free (WeatherInfo *info)
620 {
621     if (!info)
622         return;
623 
624     weather_info_abort (info);
625     if (info->session)
626 	g_object_unref (info->session);
627 
628     weather_location_free (info->location);
629     info->location = NULL;
630 
631     g_free (info->forecast);
632     info->forecast = NULL;
633 
634     free_forecast_list (info);
635 
636     if (info->radar != NULL) {
637         g_object_unref (info->radar);
638         info->radar = NULL;
639     }
640 
641     g_free (info);
642 }
643 
644 gboolean
weather_info_is_valid(WeatherInfo * info)645 weather_info_is_valid (WeatherInfo *info)
646 {
647     g_return_val_if_fail (info != NULL, FALSE);
648     return info->valid;
649 }
650 
651 gboolean
weather_info_network_error(WeatherInfo * info)652 weather_info_network_error (WeatherInfo *info)
653 {
654     g_return_val_if_fail (info != NULL, FALSE);
655     return info->network_error;
656 }
657 
658 void
weather_info_to_metric(WeatherInfo * info)659 weather_info_to_metric (WeatherInfo *info)
660 {
661     g_return_if_fail (info != NULL);
662 
663     info->temperature_unit = TEMP_UNIT_CENTIGRADE;
664     info->speed_unit = SPEED_UNIT_MS;
665     info->pressure_unit = PRESSURE_UNIT_HPA;
666     info->distance_unit = DISTANCE_UNIT_METERS;
667 }
668 
669 void
weather_info_to_imperial(WeatherInfo * info)670 weather_info_to_imperial (WeatherInfo *info)
671 {
672     g_return_if_fail (info != NULL);
673 
674     info->temperature_unit = TEMP_UNIT_FAHRENHEIT;
675     info->speed_unit = SPEED_UNIT_MPH;
676     info->pressure_unit = PRESSURE_UNIT_INCH_HG;
677     info->distance_unit = DISTANCE_UNIT_MILES;
678 }
679 
680 const WeatherLocation *
weather_info_get_location(WeatherInfo * info)681 weather_info_get_location (WeatherInfo *info)
682 {
683     g_return_val_if_fail (info != NULL, NULL);
684     return info->location;
685 }
686 
687 const gchar *
weather_info_get_location_name(WeatherInfo * info)688 weather_info_get_location_name (WeatherInfo *info)
689 {
690     g_return_val_if_fail (info != NULL, NULL);
691     g_return_val_if_fail (info->location != NULL, NULL);
692     return info->location->name;
693 }
694 
695 const gchar *
weather_info_get_update(WeatherInfo * info)696 weather_info_get_update (WeatherInfo *info)
697 {
698     static gchar buf[200];
699     char *utf8, *timeformat;
700 
701     g_return_val_if_fail (info != NULL, NULL);
702 
703     if (!info->valid)
704         return "-";
705 
706     if (info->update != 0) {
707         struct tm tm;
708         localtime_r (&info->update, &tm);
709 	/* Translators: this is a format string for strftime
710 	 *             see `man 3 strftime` for more details
711 	 */
712 	timeformat = g_locale_from_utf8 (_("%a, %b %d / %H:%M"), -1,
713 					 NULL, NULL, NULL);
714 	if (!timeformat) {
715 	    strcpy (buf, "???");
716 	}
717 	else if (strftime (buf, sizeof (buf), timeformat, &tm) <= 0) {
718 	    strcpy (buf, "???");
719 	}
720 	g_free (timeformat);
721 
722 	/* Convert to UTF-8 */
723 	utf8 = g_locale_to_utf8 (buf, -1, NULL, NULL, NULL);
724 	strcpy (buf, utf8);
725 	g_free (utf8);
726     } else {
727         strncpy (buf, _("Unknown observation time"), sizeof (buf));
728 	buf[sizeof (buf)-1] = '\0';
729     }
730 
731     return buf;
732 }
733 
734 const gchar *
weather_info_get_sky(WeatherInfo * info)735 weather_info_get_sky (WeatherInfo *info)
736 {
737     g_return_val_if_fail (info != NULL, NULL);
738     if (!info->valid)
739         return "-";
740     if (info->sky < 0)
741 	return _("Unknown");
742     return weather_sky_string (info->sky);
743 }
744 
745 const gchar *
weather_info_get_conditions(WeatherInfo * info)746 weather_info_get_conditions (WeatherInfo *info)
747 {
748     g_return_val_if_fail (info != NULL, NULL);
749     if (!info->valid)
750         return "-";
751     return weather_conditions_string (info->cond);
752 }
753 
754 static const gchar *
temperature_string(gdouble temp,TempUnit to_unit,gboolean want_round)755 temperature_string (gdouble temp, TempUnit to_unit, gboolean want_round)
756 {
757     static gchar buf[100];
758 
759     switch (to_unit) {
760     case TEMP_UNIT_FAHRENHEIT:
761 	if (!want_round) {
762 	    /* Translators: This is the temperature in degrees Fahrenheit (\302\260 is U+00B0 DEGREE SIGN) */
763 	    g_snprintf (buf, sizeof (buf), _("%.1f \302\260F"), temp);
764 	} else {
765 	    const int range_problem = FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW;
766 	    gdouble temp_r;
767 
768 	    feclearexcept(range_problem);
769 	    temp_r = round (temp);
770 	    if (fetestexcept(range_problem))
771 	        g_snprintf (buf, sizeof (buf), _("n/a"));
772 	    else
773 	        /* Translators: This is the temperature in degrees Fahrenheit (\302\260 is U+00B0 DEGREE SIGN) */
774 	        g_snprintf (buf, sizeof (buf), _("%d \302\260F"), (int)temp_r);
775 	}
776 	break;
777     case TEMP_UNIT_CENTIGRADE:
778 	if (!want_round) {
779 	    /* Translators: This is the temperature in degrees Celsius (\302\260 is U+00B0 DEGREE SIGN) */
780 	    g_snprintf (buf, sizeof (buf), _("%.1f \302\260C"), TEMP_F_TO_C (temp));
781 	} else {
782 	    const int range_problem = FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW;
783 	    gdouble temp_r;
784 
785 	    feclearexcept(range_problem);
786 	    temp_r = round (TEMP_F_TO_C (temp));
787 	    if (fetestexcept(range_problem))
788 	        g_snprintf (buf, sizeof (buf), _("n/a"));
789 	    else
790 	        /* Translators: This is the temperature in degrees Celsius (\302\260 is U+00B0 DEGREE SIGN) */
791 	        g_snprintf (buf, sizeof (buf), _("%d \302\260C"), (int)temp_r);
792 	}
793 	break;
794     case TEMP_UNIT_KELVIN:
795 	if (!want_round) {
796 	    /* Translators: This is the temperature in kelvin */
797 	    g_snprintf (buf, sizeof (buf), _("%.1f K"), TEMP_F_TO_K (temp));
798 	} else {
799 	    const int range_problem = FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW;
800 	    gdouble temp_r;
801 
802 	    feclearexcept(range_problem);
803 	    temp_r = round (TEMP_F_TO_K (temp));
804 	    if (fetestexcept(range_problem))
805 	        g_snprintf (buf, sizeof (buf), _("n/a"));
806 	    else
807 	        /* Translators: This is the temperature in kelvin */
808 	        g_snprintf (buf, sizeof (buf), _("%d K"), (int)temp_r);
809 	}
810 	break;
811 
812     case TEMP_UNIT_INVALID:
813     case TEMP_UNIT_DEFAULT:
814     default:
815 	g_warning ("Conversion to illegal temperature unit: %d", to_unit);
816 	return _("Unknown");
817     }
818 
819     return buf;
820 }
821 
822 const gchar *
weather_info_get_temp(WeatherInfo * info)823 weather_info_get_temp (WeatherInfo *info)
824 {
825     g_return_val_if_fail (info != NULL, NULL);
826 
827     if (!info->valid)
828         return "-";
829     if (info->temp < -500.0)
830         return _("Unknown");
831 
832     return temperature_string (info->temp, info->temperature_unit, FALSE);
833 }
834 
835 const gchar *
weather_info_get_temp_min(WeatherInfo * info)836 weather_info_get_temp_min (WeatherInfo *info)
837 {
838     g_return_val_if_fail (info != NULL, NULL);
839 
840     if (!info->valid || !info->tempMinMaxValid)
841         return "-";
842     if (info->temp_min < -500.0)
843         return _("Unknown");
844 
845     return temperature_string (info->temp_min, info->temperature_unit, FALSE);
846 }
847 
848 const gchar *
weather_info_get_temp_max(WeatherInfo * info)849 weather_info_get_temp_max (WeatherInfo *info)
850 {
851     g_return_val_if_fail (info != NULL, NULL);
852 
853     if (!info->valid || !info->tempMinMaxValid)
854         return "-";
855     if (info->temp_max < -500.0)
856         return _("Unknown");
857 
858     return temperature_string (info->temp_max, info->temperature_unit, FALSE);
859 }
860 
861 const gchar *
weather_info_get_dew(WeatherInfo * info)862 weather_info_get_dew (WeatherInfo *info)
863 {
864     g_return_val_if_fail (info != NULL, NULL);
865 
866     if (!info->valid)
867         return "-";
868     if (info->dew < -500.0)
869         return _("Unknown");
870 
871     return temperature_string (info->dew, info->temperature_unit, FALSE);
872 }
873 
874 const gchar *
weather_info_get_humidity(WeatherInfo * info)875 weather_info_get_humidity (WeatherInfo *info)
876 {
877     static gchar buf[20];
878     gdouble humidity;
879 
880     g_return_val_if_fail (info != NULL, NULL);
881 
882     if (!info->valid)
883         return "-";
884 
885     humidity = calc_humidity (info->temp, info->dew);
886     if (humidity < 0.0)
887         return _("Unknown");
888 
889     /* Translators: This is the humidity in percent */
890     g_snprintf (buf, sizeof (buf), _("%.f%%"), humidity);
891     return buf;
892 }
893 
894 const gchar *
weather_info_get_apparent(WeatherInfo * info)895 weather_info_get_apparent (WeatherInfo *info)
896 {
897     gdouble apparent;
898 
899     g_return_val_if_fail (info != NULL, NULL);
900     if (!info->valid)
901         return "-";
902 
903     apparent = calc_apparent (info);
904     if (apparent < -500.0)
905         return _("Unknown");
906 
907     return temperature_string (apparent, info->temperature_unit, FALSE);
908 }
909 
910 static const gchar *
windspeed_string(gfloat knots,SpeedUnit to_unit)911 windspeed_string (gfloat knots, SpeedUnit to_unit)
912 {
913     static gchar buf[100];
914 
915     switch (to_unit) {
916     case SPEED_UNIT_KNOTS:
917 	/* Translators: This is the wind speed in knots */
918 	g_snprintf (buf, sizeof (buf), _("%0.1f knots"), knots);
919 	break;
920     case SPEED_UNIT_MPH:
921 	/* Translators: This is the wind speed in miles per hour */
922 	g_snprintf (buf, sizeof (buf), _("%.1f mph"), WINDSPEED_KNOTS_TO_MPH (knots));
923 	break;
924     case SPEED_UNIT_KPH:
925 	/* Translators: This is the wind speed in kilometers per hour */
926 	g_snprintf (buf, sizeof (buf), _("%.1f km/h"), WINDSPEED_KNOTS_TO_KPH (knots));
927 	break;
928     case SPEED_UNIT_MS:
929 	/* Translators: This is the wind speed in meters per second */
930 	g_snprintf (buf, sizeof (buf), _("%.1f m/s"), WINDSPEED_KNOTS_TO_MS (knots));
931 	break;
932     case SPEED_UNIT_BFT:
933 	/* Translators: This is the wind speed as a Beaufort force factor
934 	 * (commonly used in nautical wind estimation).
935 	 */
936 	g_snprintf (buf, sizeof (buf), _("Beaufort force %.1f"),
937 		    WINDSPEED_KNOTS_TO_BFT (knots));
938 	break;
939     case SPEED_UNIT_INVALID:
940     case SPEED_UNIT_DEFAULT:
941     default:
942 	g_warning ("Conversion to illegal speed unit: %d", to_unit);
943 	return _("Unknown");
944     }
945 
946     return buf;
947 }
948 
949 const gchar *
weather_info_get_wind(WeatherInfo * info)950 weather_info_get_wind (WeatherInfo *info)
951 {
952     static gchar buf[200];
953 
954     g_return_val_if_fail (info != NULL, NULL);
955 
956     if (!info->valid)
957         return "-";
958     if (info->windspeed < 0.0 || info->wind < 0)
959         return _("Unknown");
960     if (info->windspeed == 0.00) {
961         strncpy (buf, _("Calm"), sizeof (buf));
962 	buf[sizeof (buf)-1] = '\0';
963     } else {
964         /* Translators: This is 'wind direction' / 'wind speed' */
965         g_snprintf (buf, sizeof (buf), _("%s / %s"),
966 		    weather_wind_direction_string (info->wind),
967 		    windspeed_string (info->windspeed, info->speed_unit));
968     }
969     return buf;
970 }
971 
972 const gchar *
weather_info_get_pressure(WeatherInfo * info)973 weather_info_get_pressure (WeatherInfo *info)
974 {
975     static gchar buf[100];
976 
977     g_return_val_if_fail (info != NULL, NULL);
978 
979     if (!info->valid)
980         return "-";
981     if (info->pressure < 0.0)
982         return _("Unknown");
983 
984     switch (info->pressure_unit) {
985     case PRESSURE_UNIT_INCH_HG:
986 	/* Translators: This is pressure in inches of mercury */
987 	g_snprintf (buf, sizeof (buf), _("%.2f inHg"), info->pressure);
988 	break;
989     case PRESSURE_UNIT_MM_HG:
990 	/* Translators: This is pressure in millimeters of mercury */
991 	g_snprintf (buf, sizeof (buf), _("%.1f mmHg"), PRESSURE_INCH_TO_MM (info->pressure));
992 	break;
993     case PRESSURE_UNIT_KPA:
994 	/* Translators: This is pressure in kiloPascals */
995 	g_snprintf (buf, sizeof (buf), _("%.2f kPa"), PRESSURE_INCH_TO_KPA (info->pressure));
996 	break;
997     case PRESSURE_UNIT_HPA:
998 	/* Translators: This is pressure in hectoPascals */
999 	g_snprintf (buf, sizeof (buf), _("%.2f hPa"), PRESSURE_INCH_TO_HPA (info->pressure));
1000 	break;
1001     case PRESSURE_UNIT_MB:
1002 	/* Translators: This is pressure in millibars */
1003 	g_snprintf (buf, sizeof (buf), _("%.2f mb"), PRESSURE_INCH_TO_MB (info->pressure));
1004 	break;
1005     case PRESSURE_UNIT_ATM:
1006 	/* Translators: This is pressure in atmospheres */
1007 	g_snprintf (buf, sizeof (buf), _("%.3f atm"), PRESSURE_INCH_TO_ATM (info->pressure));
1008 	break;
1009 
1010     case PRESSURE_UNIT_INVALID:
1011     case PRESSURE_UNIT_DEFAULT:
1012     default:
1013 	g_warning ("Conversion to illegal pressure unit: %d", info->pressure_unit);
1014 	return _("Unknown");
1015     }
1016 
1017     return buf;
1018 }
1019 
1020 const gchar *
weather_info_get_visibility(WeatherInfo * info)1021 weather_info_get_visibility (WeatherInfo *info)
1022 {
1023     static gchar buf[100];
1024 
1025     g_return_val_if_fail (info != NULL, NULL);
1026 
1027     if (!info->valid)
1028         return "-";
1029     if (info->visibility < 0.0)
1030         return _("Unknown");
1031 
1032     switch (info->distance_unit) {
1033     case DISTANCE_UNIT_MILES:
1034 	/* Translators: This is the visibility in miles */
1035 	g_snprintf (buf, sizeof (buf), _("%.1f miles"), info->visibility);
1036 	break;
1037     case DISTANCE_UNIT_KM:
1038 	/* Translators: This is the visibility in kilometers */
1039 	g_snprintf (buf, sizeof (buf), _("%.1f km"), VISIBILITY_SM_TO_KM (info->visibility));
1040 	break;
1041     case DISTANCE_UNIT_METERS:
1042 	/* Translators: This is the visibility in meters */
1043 	g_snprintf (buf, sizeof (buf), _("%.0fm"), VISIBILITY_SM_TO_M (info->visibility));
1044 	break;
1045 
1046     case DISTANCE_UNIT_INVALID:
1047     case DISTANCE_UNIT_DEFAULT:
1048     default:
1049 	g_warning ("Conversion to illegal visibility unit: %d", info->pressure_unit);
1050 	return _("Unknown");
1051     }
1052 
1053     return buf;
1054 }
1055 
1056 const gchar *
weather_info_get_sunrise(WeatherInfo * info)1057 weather_info_get_sunrise (WeatherInfo *info)
1058 {
1059     static gchar buf[200];
1060     struct tm tm;
1061 
1062     g_return_val_if_fail (info && info->location, NULL);
1063 
1064     if (!info->location->latlon_valid)
1065         return "-";
1066     if (!info->valid)
1067         return "-";
1068     if (!calc_sun (info))
1069         return "-";
1070 
1071     localtime_r (&info->sunrise, &tm);
1072     if (strftime (buf, sizeof (buf), _("%H:%M"), &tm) <= 0)
1073         return "-";
1074     return buf;
1075 }
1076 
1077 const gchar *
weather_info_get_sunset(WeatherInfo * info)1078 weather_info_get_sunset (WeatherInfo *info)
1079 {
1080     static gchar buf[200];
1081     struct tm tm;
1082 
1083     g_return_val_if_fail (info && info->location, NULL);
1084 
1085     if (!info->location->latlon_valid)
1086         return "-";
1087     if (!info->valid)
1088         return "-";
1089     if (!calc_sun (info))
1090         return "-";
1091 
1092     localtime_r (&info->sunset, &tm);
1093     if (strftime (buf, sizeof (buf), _("%H:%M"), &tm) <= 0)
1094         return "-";
1095     return buf;
1096 }
1097 
1098 const gchar *
weather_info_get_forecast(WeatherInfo * info)1099 weather_info_get_forecast (WeatherInfo *info)
1100 {
1101     g_return_val_if_fail (info != NULL, NULL);
1102     return info->forecast;
1103 }
1104 
1105 /**
1106  * weather_info_get_forecast_list:
1107  * Returns list of WeatherInfo* objects for the forecast.
1108  * The list is owned by the 'info' object thus is alive as long
1109  * as the 'info'. This list is filled only when requested with
1110  * type FORECAST_LIST and if available for given location.
1111  * The 'update' property is the date/time when the forecast info
1112  * is used for.
1113  **/
1114 GSList *
weather_info_get_forecast_list(WeatherInfo * info)1115 weather_info_get_forecast_list (WeatherInfo *info)
1116 {
1117     g_return_val_if_fail (info != NULL, NULL);
1118 
1119     if (!info->valid)
1120 	return NULL;
1121 
1122     return info->forecast_list;
1123 }
1124 
1125 GdkPixbufAnimation *
weather_info_get_radar(WeatherInfo * info)1126 weather_info_get_radar (WeatherInfo *info)
1127 {
1128     g_return_val_if_fail (info != NULL, NULL);
1129     return info->radar;
1130 }
1131 
1132 const gchar *
weather_info_get_temp_summary(WeatherInfo * info)1133 weather_info_get_temp_summary (WeatherInfo *info)
1134 {
1135     g_return_val_if_fail (info != NULL, NULL);
1136 
1137     if (!info->valid || info->temp < -500.0)
1138         return "--";
1139 
1140     return temperature_string (info->temp, info->temperature_unit, TRUE);
1141 
1142 }
1143 
1144 gchar *
weather_info_get_weather_summary(WeatherInfo * info)1145 weather_info_get_weather_summary (WeatherInfo *info)
1146 {
1147     const gchar *buf;
1148 
1149     g_return_val_if_fail (info != NULL, NULL);
1150 
1151     if (!info->valid)
1152 	return g_strdup (_("Retrieval failed"));
1153     buf = weather_info_get_conditions (info);
1154     if (!strcmp (buf, "-"))
1155         buf = weather_info_get_sky (info);
1156     return g_strdup_printf ("%s: %s", weather_info_get_location_name (info), buf);
1157 }
1158 
1159 const gchar *
weather_info_get_icon_name(WeatherInfo * info)1160 weather_info_get_icon_name (WeatherInfo *info)
1161 {
1162     WeatherConditions cond;
1163     WeatherSky        sky;
1164     time_t            current_time;
1165     gboolean          daytime;
1166     gchar*            icon;
1167     static gchar      icon_buffer[32];
1168     WeatherMoonPhase  moonPhase;
1169     WeatherMoonLatitude moonLat;
1170     gint              phase;
1171 
1172     g_return_val_if_fail (info != NULL, NULL);
1173 
1174     if (!info->valid)
1175         return NULL;
1176 
1177     cond = info->cond;
1178     sky = info->sky;
1179 
1180     if (cond.significant) {
1181 	if (cond.phenomenon != PHENOMENON_NONE &&
1182 	    cond.qualifier == QUALIFIER_THUNDERSTORM)
1183             return "weather-storm";
1184 
1185         switch (cond.phenomenon) {
1186 	case PHENOMENON_INVALID:
1187 	case PHENOMENON_LAST:
1188 	case PHENOMENON_NONE:
1189 	    break;
1190 
1191 	case PHENOMENON_DRIZZLE:
1192 	case PHENOMENON_RAIN:
1193 	case PHENOMENON_UNKNOWN_PRECIPITATION:
1194 	case PHENOMENON_HAIL:
1195 	case PHENOMENON_SMALL_HAIL:
1196 	    return "weather-showers";
1197 
1198 	case PHENOMENON_SNOW:
1199 	case PHENOMENON_SNOW_GRAINS:
1200 	case PHENOMENON_ICE_PELLETS:
1201 	case PHENOMENON_ICE_CRYSTALS:
1202 	    return "weather-snow";
1203 
1204 	case PHENOMENON_TORNADO:
1205 	case PHENOMENON_SQUALL:
1206 	    return "weather-storm";
1207 
1208 	case PHENOMENON_MIST:
1209 	case PHENOMENON_FOG:
1210 	case PHENOMENON_SMOKE:
1211 	case PHENOMENON_VOLCANIC_ASH:
1212 	case PHENOMENON_SAND:
1213 	case PHENOMENON_HAZE:
1214 	case PHENOMENON_SPRAY:
1215 	case PHENOMENON_DUST:
1216 	case PHENOMENON_SANDSTORM:
1217 	case PHENOMENON_DUSTSTORM:
1218 	case PHENOMENON_FUNNEL_CLOUD:
1219 	case PHENOMENON_DUST_WHIRLS:
1220 	    return "weather-fog";
1221         }
1222     }
1223 
1224     if (info->midnightSun ||
1225 	(!info->sunriseValid && !info->sunsetValid))
1226 	daytime = TRUE;
1227     else if (info->polarNight)
1228 	daytime = FALSE;
1229     else {
1230 	current_time = time (NULL);
1231 	daytime =
1232 	    ( !info->sunriseValid || (current_time >= info->sunrise) ) &&
1233 	    ( !info->sunsetValid || (current_time < info->sunset) );
1234     }
1235 
1236     switch (sky) {
1237     case SKY_INVALID:
1238     case SKY_LAST:
1239     case SKY_CLEAR:
1240 	if (daytime)
1241 	    return "weather-clear";
1242 	else {
1243 	    icon = g_stpcpy(icon_buffer, "weather-clear-night");
1244 	    break;
1245 	}
1246 
1247     case SKY_BROKEN:
1248     case SKY_SCATTERED:
1249     case SKY_FEW:
1250 	if (daytime)
1251 	    return "weather-few-clouds";
1252 	else {
1253 	    icon = g_stpcpy(icon_buffer, "weather-few-clouds-night");
1254 	    break;
1255 	}
1256 
1257     case SKY_OVERCAST:
1258 	return "weather-overcast";
1259 
1260     default: /* unrecognized */
1261 	return NULL;
1262     }
1263 
1264     /*
1265      * A phase-of-moon icon is to be returned.
1266      * Determine which one based on the moon's location
1267      */
1268     if (info->moonValid && weather_info_get_value_moonphase(info, &moonPhase, &moonLat)) {
1269 	phase = (gint)((moonPhase * MOON_PHASES / 360.) + 0.5);
1270 	if (phase == MOON_PHASES) {
1271 	    phase = 0;
1272 	} else if (phase > 0 &&
1273 		   (RADIANS_TO_DEGREES(weather_info_get_location(info)->latitude)
1274 		    < moonLat)) {
1275 	    /*
1276 	     * Locations south of the moon's latitude will see the moon in the
1277 	     * northern sky.  The moon waxes and wanes from left to right
1278 	     * so we reference an icon running in the opposite direction.
1279 	     */
1280 	    phase = MOON_PHASES - phase;
1281 	}
1282 
1283 	/*
1284 	 * If the moon is not full then append the angle to the icon string.
1285 	 * Note that an icon by this name is not required to exist:
1286 	 * the caller can use GTK_ICON_LOOKUP_GENERIC_FALLBACK to fall back to
1287 	 * the full moon image.
1288 	 */
1289 	if ((0 == (MOON_PHASES & 0x1)) && (MOON_PHASES/2 != phase)) {
1290 	    g_snprintf(icon, sizeof(icon_buffer) - strlen(icon_buffer),
1291 		       "-%03d", phase * 360 / MOON_PHASES);
1292 	}
1293     }
1294     return icon_buffer;
1295 }
1296 
1297 static gboolean
temperature_value(gdouble temp_f,TempUnit to_unit,gdouble * value,TempUnit def_unit)1298 temperature_value (gdouble temp_f,
1299 		   TempUnit to_unit,
1300 		   gdouble *value,
1301 		   TempUnit def_unit)
1302 {
1303     gboolean ok = TRUE;
1304 
1305     *value = 0.0;
1306     if (temp_f < -500.0)
1307 	return FALSE;
1308 
1309     if (to_unit == TEMP_UNIT_DEFAULT)
1310 	    to_unit = def_unit;
1311 
1312     switch (to_unit) {
1313         case TEMP_UNIT_FAHRENHEIT:
1314 	    *value = temp_f;
1315 	    break;
1316         case TEMP_UNIT_CENTIGRADE:
1317 	    *value = TEMP_F_TO_C (temp_f);
1318 	    break;
1319         case TEMP_UNIT_KELVIN:
1320 	    *value = TEMP_F_TO_K (temp_f);
1321 	    break;
1322         case TEMP_UNIT_INVALID:
1323         case TEMP_UNIT_DEFAULT:
1324 	default:
1325 	    ok = FALSE;
1326 	    break;
1327     }
1328 
1329     return ok;
1330 }
1331 
1332 static gboolean
speed_value(gdouble knots,SpeedUnit to_unit,gdouble * value,SpeedUnit def_unit)1333 speed_value (gdouble knots, SpeedUnit to_unit, gdouble *value, SpeedUnit def_unit)
1334 {
1335     gboolean ok = TRUE;
1336 
1337     *value = -1.0;
1338 
1339     if (knots < 0.0)
1340 	return FALSE;
1341 
1342     if (to_unit == SPEED_UNIT_DEFAULT)
1343 	    to_unit = def_unit;
1344 
1345     switch (to_unit) {
1346         case SPEED_UNIT_KNOTS:
1347             *value = knots;
1348 	    break;
1349         case SPEED_UNIT_MPH:
1350             *value = WINDSPEED_KNOTS_TO_MPH (knots);
1351 	    break;
1352         case SPEED_UNIT_KPH:
1353             *value = WINDSPEED_KNOTS_TO_KPH (knots);
1354 	    break;
1355         case SPEED_UNIT_MS:
1356             *value = WINDSPEED_KNOTS_TO_MS (knots);
1357 	    break;
1358 	case SPEED_UNIT_BFT:
1359 	    *value = WINDSPEED_KNOTS_TO_BFT (knots);
1360 	    break;
1361         case SPEED_UNIT_INVALID:
1362         case SPEED_UNIT_DEFAULT:
1363         default:
1364             ok = FALSE;
1365             break;
1366     }
1367 
1368     return ok;
1369 }
1370 
1371 static gboolean
pressure_value(gdouble inHg,PressureUnit to_unit,gdouble * value,PressureUnit def_unit)1372 pressure_value (gdouble inHg, PressureUnit to_unit, gdouble *value, PressureUnit def_unit)
1373 {
1374     gboolean ok = TRUE;
1375 
1376     *value = -1.0;
1377 
1378     if (inHg < 0.0)
1379 	return FALSE;
1380 
1381     if (to_unit == PRESSURE_UNIT_DEFAULT)
1382 	    to_unit = def_unit;
1383 
1384     switch (to_unit) {
1385         case PRESSURE_UNIT_INCH_HG:
1386             *value = inHg;
1387 	    break;
1388         case PRESSURE_UNIT_MM_HG:
1389             *value = PRESSURE_INCH_TO_MM (inHg);
1390 	    break;
1391         case PRESSURE_UNIT_KPA:
1392             *value = PRESSURE_INCH_TO_KPA (inHg);
1393 	    break;
1394         case PRESSURE_UNIT_HPA:
1395             *value = PRESSURE_INCH_TO_HPA (inHg);
1396 	    break;
1397         case PRESSURE_UNIT_MB:
1398             *value = PRESSURE_INCH_TO_MB (inHg);
1399 	    break;
1400         case PRESSURE_UNIT_ATM:
1401             *value = PRESSURE_INCH_TO_ATM (inHg);
1402 	    break;
1403         case PRESSURE_UNIT_INVALID:
1404         case PRESSURE_UNIT_DEFAULT:
1405         default:
1406 	    ok = FALSE;
1407 	    break;
1408     }
1409 
1410     return ok;
1411 }
1412 
1413 static gboolean
distance_value(gdouble miles,DistanceUnit to_unit,gdouble * value,DistanceUnit def_unit)1414 distance_value (gdouble miles, DistanceUnit to_unit, gdouble *value, DistanceUnit def_unit)
1415 {
1416     gboolean ok = TRUE;
1417 
1418     *value = -1.0;
1419 
1420     if (miles < 0.0)
1421 	return FALSE;
1422 
1423     if (to_unit == DISTANCE_UNIT_DEFAULT)
1424 	    to_unit = def_unit;
1425 
1426     switch (to_unit) {
1427         case DISTANCE_UNIT_MILES:
1428             *value = miles;
1429             break;
1430         case DISTANCE_UNIT_KM:
1431             *value = VISIBILITY_SM_TO_KM (miles);
1432             break;
1433         case DISTANCE_UNIT_METERS:
1434             *value = VISIBILITY_SM_TO_M (miles);
1435             break;
1436         case DISTANCE_UNIT_INVALID:
1437         case DISTANCE_UNIT_DEFAULT:
1438         default:
1439 	    ok = FALSE;
1440 	    break;
1441     }
1442 
1443     return ok;
1444 }
1445 
1446 gboolean
weather_info_get_value_sky(WeatherInfo * info,WeatherSky * sky)1447 weather_info_get_value_sky (WeatherInfo *info, WeatherSky *sky)
1448 {
1449     g_return_val_if_fail (info != NULL, FALSE);
1450     g_return_val_if_fail (sky != NULL, FALSE);
1451 
1452     if (!info->valid)
1453 	return FALSE;
1454 
1455     if (info->sky <= SKY_INVALID || info->sky >= SKY_LAST)
1456 	return FALSE;
1457 
1458     *sky = info->sky;
1459 
1460     return TRUE;
1461 }
1462 
1463 gboolean
weather_info_get_value_conditions(WeatherInfo * info,WeatherConditionPhenomenon * phenomenon,WeatherConditionQualifier * qualifier)1464 weather_info_get_value_conditions (WeatherInfo *info, WeatherConditionPhenomenon *phenomenon, WeatherConditionQualifier *qualifier)
1465 {
1466     g_return_val_if_fail (info != NULL, FALSE);
1467     g_return_val_if_fail (phenomenon != NULL, FALSE);
1468     g_return_val_if_fail (qualifier != NULL, FALSE);
1469 
1470     if (!info->valid)
1471 	return FALSE;
1472 
1473     if (!info->cond.significant)
1474 	return FALSE;
1475 
1476     if (!(info->cond.phenomenon > PHENOMENON_INVALID &&
1477 	  info->cond.phenomenon < PHENOMENON_LAST &&
1478 	  info->cond.qualifier > QUALIFIER_INVALID &&
1479 	  info->cond.qualifier < QUALIFIER_LAST))
1480         return FALSE;
1481 
1482     *phenomenon = info->cond.phenomenon;
1483     *qualifier = info->cond.qualifier;
1484 
1485     return TRUE;
1486 }
1487 
1488 gboolean
weather_info_get_value_temp(WeatherInfo * info,TempUnit unit,gdouble * value)1489 weather_info_get_value_temp (WeatherInfo *info, TempUnit unit, gdouble *value)
1490 {
1491     g_return_val_if_fail (info != NULL, FALSE);
1492     g_return_val_if_fail (value != NULL, FALSE);
1493 
1494     if (!info->valid)
1495 	return FALSE;
1496 
1497     return temperature_value (info->temp, unit, value, info->temperature_unit);
1498 }
1499 
1500 gboolean
weather_info_get_value_temp_min(WeatherInfo * info,TempUnit unit,gdouble * value)1501 weather_info_get_value_temp_min (WeatherInfo *info, TempUnit unit, gdouble *value)
1502 {
1503     g_return_val_if_fail (info != NULL, FALSE);
1504     g_return_val_if_fail (value != NULL, FALSE);
1505 
1506     if (!info->valid || !info->tempMinMaxValid)
1507 	return FALSE;
1508 
1509     return temperature_value (info->temp_min, unit, value, info->temperature_unit);
1510 }
1511 
1512 gboolean
weather_info_get_value_temp_max(WeatherInfo * info,TempUnit unit,gdouble * value)1513 weather_info_get_value_temp_max (WeatherInfo *info, TempUnit unit, gdouble *value)
1514 {
1515     g_return_val_if_fail (info != NULL, FALSE);
1516     g_return_val_if_fail (value != NULL, FALSE);
1517 
1518     if (!info->valid || !info->tempMinMaxValid)
1519 	return FALSE;
1520 
1521     return temperature_value (info->temp_max, unit, value, info->temperature_unit);
1522 }
1523 
1524 gboolean
weather_info_get_value_dew(WeatherInfo * info,TempUnit unit,gdouble * value)1525 weather_info_get_value_dew (WeatherInfo *info, TempUnit unit, gdouble *value)
1526 {
1527     g_return_val_if_fail (info != NULL, FALSE);
1528     g_return_val_if_fail (value != NULL, FALSE);
1529 
1530     if (!info->valid)
1531 	return FALSE;
1532 
1533     return temperature_value (info->dew, unit, value, info->temperature_unit);
1534 }
1535 
1536 gboolean
weather_info_get_value_apparent(WeatherInfo * info,TempUnit unit,gdouble * value)1537 weather_info_get_value_apparent (WeatherInfo *info, TempUnit unit, gdouble *value)
1538 {
1539     g_return_val_if_fail (info != NULL, FALSE);
1540     g_return_val_if_fail (value != NULL, FALSE);
1541 
1542     if (!info->valid)
1543 	return FALSE;
1544 
1545     return temperature_value (calc_apparent (info), unit, value, info->temperature_unit);
1546 }
1547 
1548 gboolean
weather_info_get_value_update(WeatherInfo * info,time_t * value)1549 weather_info_get_value_update (WeatherInfo *info, time_t *value)
1550 {
1551     g_return_val_if_fail (info != NULL, FALSE);
1552     g_return_val_if_fail (value != NULL, FALSE);
1553 
1554     if (!info->valid)
1555 	return FALSE;
1556 
1557     *value = info->update;
1558 
1559     return TRUE;
1560 }
1561 
1562 gboolean
weather_info_get_value_sunrise(WeatherInfo * info,time_t * value)1563 weather_info_get_value_sunrise (WeatherInfo *info, time_t *value)
1564 {
1565     g_return_val_if_fail (info != NULL, FALSE);
1566     g_return_val_if_fail (value != NULL, FALSE);
1567 
1568     if (!info->valid || !info->sunriseValid)
1569 	return FALSE;
1570 
1571     *value = info->sunrise;
1572 
1573     return TRUE;
1574 }
1575 
1576 gboolean
weather_info_get_value_sunset(WeatherInfo * info,time_t * value)1577 weather_info_get_value_sunset (WeatherInfo *info, time_t *value)
1578 {
1579     g_return_val_if_fail (info != NULL, FALSE);
1580     g_return_val_if_fail (value != NULL, FALSE);
1581 
1582     if (!info->valid || !info->sunsetValid)
1583 	return FALSE;
1584 
1585     *value = info->sunset;
1586 
1587     return TRUE;
1588 }
1589 
1590 gboolean
weather_info_get_value_moonphase(WeatherInfo * info,WeatherMoonPhase * value,WeatherMoonLatitude * lat)1591 weather_info_get_value_moonphase (WeatherInfo      *info,
1592 				  WeatherMoonPhase *value,
1593 				  WeatherMoonLatitude *lat)
1594 {
1595     g_return_val_if_fail (info != NULL, FALSE);
1596     g_return_val_if_fail (value != NULL, FALSE);
1597 
1598     if (!info->valid || !info->moonValid)
1599 	return FALSE;
1600 
1601     *value = info->moonphase;
1602     *lat   = info->moonlatitude;
1603 
1604     return TRUE;
1605 }
1606 
1607 gboolean
weather_info_get_value_wind(WeatherInfo * info,SpeedUnit unit,gdouble * speed,WeatherWindDirection * direction)1608 weather_info_get_value_wind (WeatherInfo *info, SpeedUnit unit, gdouble *speed, WeatherWindDirection *direction)
1609 {
1610     gboolean res = FALSE;
1611 
1612     g_return_val_if_fail (info != NULL, FALSE);
1613     g_return_val_if_fail (speed != NULL, FALSE);
1614     g_return_val_if_fail (direction != NULL, FALSE);
1615 
1616     if (!info->valid)
1617 	return FALSE;
1618 
1619     if (info->windspeed < 0.0 || info->wind <= WIND_INVALID || info->wind >= WIND_LAST)
1620         return FALSE;
1621 
1622     res = speed_value (info->windspeed, unit, speed, info->speed_unit);
1623     *direction = info->wind;
1624 
1625     return res;
1626 }
1627 
1628 gboolean
weather_info_get_value_pressure(WeatherInfo * info,PressureUnit unit,gdouble * value)1629 weather_info_get_value_pressure (WeatherInfo *info, PressureUnit unit, gdouble *value)
1630 {
1631     g_return_val_if_fail (info != NULL, FALSE);
1632     g_return_val_if_fail (value != NULL, FALSE);
1633 
1634     if (!info->valid)
1635 	return FALSE;
1636 
1637     return pressure_value (info->pressure, unit, value, info->pressure_unit);
1638 }
1639 
1640 gboolean
weather_info_get_value_visibility(WeatherInfo * info,DistanceUnit unit,gdouble * value)1641 weather_info_get_value_visibility (WeatherInfo *info, DistanceUnit unit, gdouble *value)
1642 {
1643     g_return_val_if_fail (info != NULL, FALSE);
1644     g_return_val_if_fail (value != NULL, FALSE);
1645 
1646     if (!info->valid)
1647 	return FALSE;
1648 
1649     return distance_value (info->visibility, unit, value, info->distance_unit);
1650 }
1651 
1652 /**
1653  * weather_info_get_upcoming_moonphases:
1654  * @info:   WeatherInfo containing the time_t of interest
1655  * @phases: An array of four time_t values that will hold the returned values.
1656  *    The values are estimates of the time of the next new, quarter, full and
1657  *    three-quarter moons.
1658  *
1659  * Returns: gboolean indicating success or failure
1660  */
1661 gboolean
weather_info_get_upcoming_moonphases(WeatherInfo * info,time_t * phases)1662 weather_info_get_upcoming_moonphases (WeatherInfo *info, time_t *phases)
1663 {
1664     g_return_val_if_fail (info != NULL, FALSE);
1665     g_return_val_if_fail (phases != NULL, FALSE);
1666 
1667     return calc_moon_phases(info, phases);
1668 }
1669 
1670 static void
_weather_internal_check(void)1671 _weather_internal_check (void)
1672 {
1673     g_assert (G_N_ELEMENTS (wind_direction_str) == WIND_LAST);
1674     g_assert (G_N_ELEMENTS (sky_str) == SKY_LAST);
1675     g_assert (G_N_ELEMENTS (conditions_str) == PHENOMENON_LAST);
1676     g_assert (G_N_ELEMENTS (conditions_str[0]) == QUALIFIER_LAST);
1677 }
1678