1
/* Evolution calendar - weather backend source class for parsing
2
* CCF (coded cities forecast) formatted NWS reports
4
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
6
* Authors: David Trowbridge <trowbrds@cs.colorado.edu>
8
* This program is free software; you can redistribute it and/or
9
* modify it under the terms of version 2 of the GNU Lesser General Public
10
* License as published by the Free Software Foundation.
12
* This program is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU Lesser General Public License for more details.
17
* You should have received a copy of the GNU Lesser General Public License
18
* along with this program; if not, write to the Free Software
19
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
29
#include <glib/gi18n-lib.h>
31
#include "e-weather-source-ccf.h"
33
#define GWEATHER_I_KNOW_THIS_IS_UNSTABLE
34
#include <libgweather/weather.h>
35
#include <libgweather/gweather-xml.h>
36
#undef GWEATHER_I_KNOW_THIS_IS_UNSTABLE
44
/* The localtime() in Microsoft's C library is MT-safe */
45
#define localtime_r(tp,tmp) (localtime(tp)?(*(tmp)=*localtime(tp),(tmp)):0)
47
/* strtok() is also MT-safe (but not stateless, still uses only one
48
* buffer pointer per thread, but for the use of strtok_r() here
51
#define strtok_r(s,sep,lasts) (*(lasts)=strtok((s),(sep)))
54
G_DEFINE_TYPE (EWeatherSourceCCF, e_weather_source_ccf, E_TYPE_WEATHER_SOURCE)
61
WeatherLocation *location;
65
find_location_func (GtkTreeModel *model,
70
WeatherLocation *wl = NULL;
71
struct search_struct *search = (struct search_struct *) data;
73
gtk_tree_model_get (model, node, GWEATHER_XML_COL_POINTER, &wl, -1);
74
if (!wl || !wl->name || !wl->code || !search || search->location)
77
if (((!strcmp (wl->code, search->code)) || (search->is_old && !strcmp (wl->code + 1, search->code))) &&
78
(!strcmp (wl->name, search->name))) {
79
search->location = weather_location_clone (wl);
86
static WeatherLocation *
87
find_location (const gchar *code_name,
92
struct search_struct search;
94
search.location = NULL;
96
ids = g_strsplit (code_name, "/", 2);
98
if (!ids || !ids[0] || !ids[1])
101
model = gweather_xml_load_locations ();
105
search.code = ids[0];
106
search.name = ids[1];
107
search.is_old = is_old;
108
search.location = NULL;
110
gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc) find_location_func, &search);
112
gweather_xml_free_locations (model);
116
return search.location;
121
tokenize (gchar *buffer)
127
token = strtok_r (buffer, " \n", &tokbuf);
128
ret = g_slist_append (NULL, g_strdup (token));
129
while ((token = strtok_r (NULL, " \n/", &tokbuf)))
130
ret = g_slist_append (ret, g_strdup (token));
135
date2tm (gchar *date,
139
time_t curtime = time (NULL);
142
localtime_r (&curtime, times);
144
tmp[0] = date[0]; tmp[1] = date[1];
145
times->tm_mday = atoi (tmp);
146
tmp[0] = date[2]; tmp[1] = date[3];
147
times->tm_hour = atoi (tmp);
148
tmp[0] = date[4]; tmp[1] = date[5];
149
times->tm_min = atoi (tmp);
152
static WeatherConditions
153
decodeConditions (gchar code)
156
case 'A': return WEATHER_FAIR;
157
case 'B': return WEATHER_PARTLY_CLOUDY;
158
case 'C': return WEATHER_CLOUDY;
159
case 'D': return WEATHER_DUST;
160
case 'E': return WEATHER_MOSTLY_CLOUDY;
161
case 'F': return WEATHER_FOGGY;
162
case 'G': return WEATHER_VERY_HOT_OR_HOT_HUMID;
163
case 'H': return WEATHER_HAZE;
164
case 'I': return WEATHER_VERY_COLD_WIND_CHILL;
165
case 'J': return WEATHER_SNOW_SHOWERS;
166
case 'K': return WEATHER_SMOKE;
167
case 'L': return WEATHER_DRIZZLE;
168
case 'M': return WEATHER_SNOW_SHOWERS;
169
case 'N': return WEATHER_WINDY;
170
case 'O': return WEATHER_RAIN_OR_SNOW_MIXED;
171
case 'P': return WEATHER_BLIZZARD;
172
case 'Q': return WEATHER_BLOWING_SNOW;
173
case 'R': return WEATHER_RAIN;
174
case 'S': return WEATHER_SNOW;
175
case 'T': return WEATHER_THUNDERSTORMS;
176
case 'U': return WEATHER_SUNNY;
177
case 'V': return WEATHER_CLEAR;
178
case 'W': return WEATHER_RAIN_SHOWERS;
179
case 'X': return WEATHER_SLEET;
180
case 'Y': return WEATHER_FREEZING_RAIN;
181
case 'Z': return WEATHER_FREEZING_DRIZZLE;
182
/* hmm, this should never happen. */
183
default: return WEATHER_SUNNY;
188
decodePOP (gchar data)
200
ret = -1; /* missing data */
203
ret = (data - '0') * 10;
209
decodeSnowfall (gchar *data,
216
num[0] = data[0]; num[1] = data[1];
217
*low = atof (num) * 2.54f;
218
num[0] = data[2]; num[1] = data[3];
219
*high = atof (num) * 2.54f;
225
gint fahrenheit = atoi (data);
226
if (fahrenheit >= 900)
227
fahrenheit = (fahrenheit - 900) * -1;
228
return ((gfloat)(fahrenheit - 32)) * 5.0f / 9.0f;
232
e_weather_source_ccf_do_parse (EWeatherSourceCCF *source,
235
/* CCF gives us either 2 or 7 days of forecast data. IFPS WFO's
236
* will produce 7 day forecasts, whereas pre-IFPS WFO's are only
237
* mandated 2 (but may do 7). The morning forecast will give us either 2
238
* or 7 days worth of data. The evening forecast will give us the evening's
239
* low temperature plus 2 or 7 days forecast.
241
* The CCF format is described in NWS directive 10-503, but it's usually
242
* easier to look at a summary put up by one of the stations:
243
* http://www.crh.noaa.gov/lmk/product_guide/products/forecast/ccf.htm
245
WeatherForecast *forecasts = g_new0 (WeatherForecast, 7);
246
GSList *tokens = tokenize (buffer);
248
GSList *current = tokens;
255
date = g_slist_nth (tokens, 3);
256
date2tm (date->data, &tms);
258
/* fast-forward to the particular station we're interested in */
259
current = g_slist_nth (tokens, 5);
260
while (strcmp (current->data, source->substation))
261
current = g_slist_next (current);
262
current = g_slist_next (current);
263
/* pick up the first two conditions reports */
264
forecasts[0].conditions = decodeConditions (((gchar *)(current->data))[0]);
265
forecasts[1].conditions = decodeConditions (((gchar *)(current->data))[1]);
267
current = g_slist_next (current);
268
if (tms.tm_hour < 12) {
269
for (i = 0; i < 2; i++) {
270
forecasts[i].high = ftoc (current->data);
271
current = g_slist_next (current);
272
forecasts[i].low = ftoc (current->data);
273
current = g_slist_next (current);
275
forecasts[2].high = ftoc (current->data);
276
current = g_slist_next (current);
277
forecasts[0].pop = decodePOP (((gchar *)(current->data))[2]);
278
forecasts[1].pop = decodePOP (((gchar *)(current->data))[4]);
280
for (i = 0; i < 2; i++) {
281
current = g_slist_next (current);
282
forecasts[i].high = ftoc (current->data);
283
current = g_slist_next (current);
284
forecasts[i].low = ftoc (current->data);
286
current = g_slist_next (current);
287
forecasts[0].pop = decodePOP (((gchar *)(current->data))[1]);
288
forecasts[1].pop = decodePOP (((gchar *)(current->data))[3]);
291
current = g_slist_next (current);
292
if (strlen (current->data) == 4) {
293
/* we've got the optional snowfall field */
294
if (tms.tm_hour < 12) {
295
decodeSnowfall (current->data, &forecasts[0].low, &forecasts[0].high);
296
current = g_slist_next (g_slist_next (current));
297
decodeSnowfall (current->data, &forecasts[1].low, &forecasts[1].high);
299
current = g_slist_next (current);
300
decodeSnowfall (current->data, &forecasts[0].low, &forecasts[0].high);
302
current = g_slist_next (current);
306
base = mktime (&tms);
307
if (tms.tm_hour >= 12)
309
for (i = 0; i < 7; i++)
310
forecasts[i].date = base + 86400 * i;
312
if (current == NULL || strlen (current->data) == 3) {
313
/* We've got a pre-IFPS station. Realloc and return */
314
WeatherForecast *f = g_new0 (WeatherForecast, 2);
315
memcpy (f, forecasts, sizeof (WeatherForecast) * 2);
316
fc = g_list_append (fc, &f[0]);
317
fc = g_list_append (fc, &f[1]);
318
source->done (fc, source->finished_data);
321
/* Grab the conditions for the next 5 days */
322
forecasts[2].conditions = decodeConditions (((gchar *)(current->data))[0]);
323
forecasts[3].conditions = decodeConditions (((gchar *)(current->data))[1]);
324
forecasts[4].conditions = decodeConditions (((gchar *)(current->data))[2]);
325
forecasts[5].conditions = decodeConditions (((gchar *)(current->data))[3]);
326
forecasts[6].conditions = decodeConditions (((gchar *)(current->data))[4]);
328
/* Temperature forecasts */
329
current = g_slist_next (current);
330
if (tms.tm_hour < 12) {
331
forecasts[2].low = ftoc (current->data);
332
for (i = 3; i < 6; i++) {
333
current = g_slist_next (current);
334
forecasts[i].high = ftoc (current->data);
335
current = g_slist_next (current);
336
forecasts[i].low = ftoc (current->data);
338
current = g_slist_next (current);
339
forecasts[6].high = ftoc (current->data);
340
forecasts[6].low = forecasts[6].high;
341
current = g_slist_next (current);
342
forecasts[2].pop = decodePOP (((gchar *)(current->data))[1]);
343
forecasts[3].pop = decodePOP (((gchar *)(current->data))[3]);
344
forecasts[4].pop = decodePOP (((gchar *)(current->data))[5]);
345
forecasts[5].pop = decodePOP (((gchar *)(current->data))[7]);
346
forecasts[6].pop = decodePOP (((gchar *)(current->data))[9]);
349
for (i = 2; i < 6; i++) {
350
forecasts[i].high = ftoc (current->data);
351
current = g_slist_next (current);
352
forecasts[i].low = ftoc (current->data);
353
current = g_slist_next (current);
356
/* hack for people who put out bad data, like Pueblo, CO. Yes, PUB, that means you */
357
if (strlen (current->data) == 3)
358
current = g_slist_next (current);
359
forecasts[1].pop = decodePOP (((gchar *)(current->data))[0]);
360
forecasts[2].pop = decodePOP (((gchar *)(current->data))[2]);
361
forecasts[3].pop = decodePOP (((gchar *)(current->data))[4]);
362
forecasts[4].pop = decodePOP (((gchar *)(current->data))[6]);
363
forecasts[5].pop = decodePOP (((gchar *)(current->data))[8]);
366
for (i = 0; i < n; i++) {
367
fc = g_list_append (fc, &forecasts[i]);
369
source->done (fc, source->finished_data);
377
parse_done (WeatherInfo *info,
380
EWeatherSourceCCF *ccfsource = (EWeatherSourceCCF *) data;
385
if (!info || !weather_info_is_valid (info)) {
386
ccfsource->done (NULL, ccfsource->finished_data);
390
ccfsource->done (info, ccfsource->finished_data);
394
e_weather_source_ccf_parse (EWeatherSource *source,
395
EWeatherSourceFinished done,
398
EWeatherSourceCCF *ccfsource = (EWeatherSourceCCF *) source;
401
ccfsource->finished_data = data;
402
ccfsource->done = done;
404
prefs.type = FORECAST_LIST;
406
prefs.radar_custom_url = NULL;
407
prefs.temperature_unit = TEMP_UNIT_CENTIGRADE;
408
prefs.speed_unit = SPEED_UNIT_MS;
409
prefs.pressure_unit = PRESSURE_UNIT_HPA;
410
prefs.distance_unit = DISTANCE_UNIT_METERS;
412
if (ccfsource->location && !ccfsource->info) {
413
ccfsource->info = weather_info_new (ccfsource->location, &prefs, parse_done, source);
414
weather_location_free (ccfsource->location);
415
ccfsource->location = NULL;
417
ccfsource->info = weather_info_update (ccfsource->info, &prefs, parse_done, source);
422
e_weather_source_ccf_class_init (EWeatherSourceCCFClass *class)
424
EWeatherSourceClass *source_class;
426
source_class = E_WEATHER_SOURCE_CLASS (class);
427
source_class->parse = e_weather_source_ccf_parse;
431
e_weather_source_ccf_init (EWeatherSourceCCF *source)
433
source->location = NULL;
438
e_weather_source_ccf_new (const gchar *location)
440
/* Old location is formatted as ccf/AAA[/BBB] - AAA is the 3-letter
441
* station code for identifying the providing station (subdirectory
442
* within the crh data repository). BBB is an optional additional
443
* station ID for the station within the CCF file. If not present,
444
* BBB is assumed to be the same station as AAA. But the new
445
* location is code/name, where code is 4-letter code. So if we
446
* got the old format, then migrate to the new one, if possible.
450
EWeatherSourceCCF *source;
452
if (location == NULL)
455
if (strncmp (location, "ccf/", 4) == 0)
456
wl = find_location (location + 4, TRUE);
458
wl = find_location (location, FALSE);
463
source = g_object_new (E_TYPE_WEATHER_SOURCE_CCF, NULL);
464
source->location = wl;
467
return E_WEATHER_SOURCE (source);