1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* weather-metar.c - Weather server functions (METAR)
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 * <https://www.gnu.org/licenses/>.
17 */
18
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/types.h>
26
27 #include "gweather-private.h"
28
29 enum {
30 TIME_RE,
31 WIND_RE,
32 VIS_RE,
33 COND_RE,
34 CLOUD_RE,
35 TEMP_RE,
36 PRES_RE,
37
38 RE_NUM
39 };
40
41 /* Return time of weather report as secs since epoch UTC */
42 static time_t
make_time(gint utcDate,gint utcHour,gint utcMin)43 make_time (gint utcDate, gint utcHour, gint utcMin)
44 {
45 const time_t now = time (NULL);
46 struct tm tm;
47
48 localtime_r (&now, &tm);
49
50 /* If last reading took place just before midnight UTC on the
51 * first, adjust the date downward to allow for the month
52 * change-over. This ASSUMES that the reading won't be more than
53 * 24 hrs old! */
54 if ((utcDate > tm.tm_mday) && (tm.tm_mday == 1)) {
55 tm.tm_mday = 0; /* mktime knows this is the last day of the previous
56 * month. */
57 } else {
58 tm.tm_mday = utcDate;
59 }
60 tm.tm_hour = utcHour;
61 tm.tm_min = utcMin;
62 tm.tm_sec = 0;
63
64 /* mktime() assumes value is local, not UTC. Use tm_gmtoff to compensate */
65 #ifdef HAVE_TM_TM_GMOFF
66 return tm.tm_gmtoff + mktime (&tm);
67 #elif defined HAVE_TIMEZONE
68 return timezone + mktime (&tm);
69 #endif
70 }
71
72 static void
metar_tok_time(gchar * tokp,GWeatherInfo * info)73 metar_tok_time (gchar *tokp, GWeatherInfo *info)
74 {
75 gint day, hr, min;
76
77 g_debug ("metar_tok_time: %s", tokp);
78
79 sscanf (tokp, "%2u%2u%2u", &day, &hr, &min);
80 info->update = make_time (day, hr, min);
81 }
82
83 static void
metar_tok_wind(gchar * tokp,GWeatherInfo * info)84 metar_tok_wind (gchar *tokp, GWeatherInfo *info)
85 {
86 gchar sdir[4], sspd[4], sgust[4];
87 gint dir, spd = -1;
88 gchar *gustp;
89 size_t glen;
90
91 g_debug ("metar_tok_wind: %s", tokp);
92
93 strncpy (sdir, tokp, 3);
94 sdir[3] = 0;
95 dir = (!strcmp (sdir, "VRB")) ? -1 : atoi (sdir);
96
97 memset (sspd, 0, sizeof (sspd));
98 glen = strspn (tokp + 3, CONST_DIGITS);
99 strncpy (sspd, tokp + 3, glen);
100 spd = atoi (sspd);
101 tokp += glen + 3;
102
103 gustp = strchr (tokp, 'G');
104 if (gustp) {
105 memset (sgust, 0, sizeof (sgust));
106 glen = strspn (gustp + 1, CONST_DIGITS);
107 strncpy (sgust, gustp + 1, glen);
108 tokp = gustp + 1 + glen;
109 }
110
111 if (!strcmp (tokp, "MPS"))
112 info->windspeed = WINDSPEED_MS_TO_KNOTS ((GWeatherWindSpeed)spd);
113 else
114 info->windspeed = (GWeatherWindSpeed)spd;
115
116 if ((349 <= dir) || (dir <= 11))
117 info->wind = GWEATHER_WIND_N;
118 else if ((12 <= dir) && (dir <= 33))
119 info->wind = GWEATHER_WIND_NNE;
120 else if ((34 <= dir) && (dir <= 56))
121 info->wind = GWEATHER_WIND_NE;
122 else if ((57 <= dir) && (dir <= 78))
123 info->wind = GWEATHER_WIND_ENE;
124 else if ((79 <= dir) && (dir <= 101))
125 info->wind = GWEATHER_WIND_E;
126 else if ((102 <= dir) && (dir <= 123))
127 info->wind = GWEATHER_WIND_ESE;
128 else if ((124 <= dir) && (dir <= 146))
129 info->wind = GWEATHER_WIND_SE;
130 else if ((147 <= dir) && (dir <= 168))
131 info->wind = GWEATHER_WIND_SSE;
132 else if ((169 <= dir) && (dir <= 191))
133 info->wind = GWEATHER_WIND_S;
134 else if ((192 <= dir) && (dir <= 213))
135 info->wind = GWEATHER_WIND_SSW;
136 else if ((214 <= dir) && (dir <= 236))
137 info->wind = GWEATHER_WIND_SW;
138 else if ((237 <= dir) && (dir <= 258))
139 info->wind = GWEATHER_WIND_WSW;
140 else if ((259 <= dir) && (dir <= 281))
141 info->wind = GWEATHER_WIND_W;
142 else if ((282 <= dir) && (dir <= 303))
143 info->wind = GWEATHER_WIND_WNW;
144 else if ((304 <= dir) && (dir <= 326))
145 info->wind = GWEATHER_WIND_NW;
146 else if ((327 <= dir) && (dir <= 348))
147 info->wind = GWEATHER_WIND_NNW;
148 }
149
150 static void
metar_tok_vis(gchar * tokp,GWeatherInfo * info)151 metar_tok_vis (gchar *tokp, GWeatherInfo *info)
152 {
153 gchar *pfrac, *pend, *psp;
154 gchar sval[6];
155 gint num, den, val;
156
157 g_debug ("metar_tok_vis: %s", tokp);
158
159 memset (sval, 0, sizeof (sval));
160
161 if (!strcmp (tokp,"CAVOK")) {
162 // "Ceiling And Visibility OK": visibility >= 10 KM
163 info->visibility=10000. / VISIBILITY_SM_TO_M (1.);
164 info->sky = GWEATHER_SKY_CLEAR;
165 } else if (0 != (pend = strstr (tokp, "SM"))) {
166 // US observation: field ends with "SM"
167 pfrac = strchr (tokp, '/');
168 if (pfrac) {
169 if (*tokp == 'M') {
170 info->visibility = 0.001;
171 } else {
172 num = (*(pfrac - 1) - '0');
173 strncpy (sval, pfrac + 1, pend - pfrac - 1);
174 den = atoi (sval);
175 info->visibility =
176 ((GWeatherVisibility)num / ((GWeatherVisibility)den));
177
178 psp = strchr (tokp, ' ');
179 if (psp) {
180 *psp = '\0';
181 val = atoi (tokp);
182 info->visibility += (GWeatherVisibility)val;
183 }
184 }
185 } else {
186 strncpy (sval, tokp, pend - tokp);
187 val = atoi (sval);
188 info->visibility = (GWeatherVisibility)val;
189 }
190 } else {
191 // International observation: NNNN(DD NNNNDD)?
192 // For now: use only the minimum visibility and ignore its direction
193 strncpy (sval, tokp, strspn (tokp, CONST_DIGITS));
194 val = atoi (sval);
195 info->visibility = (GWeatherVisibility)val / VISIBILITY_SM_TO_M (1.);
196 }
197 }
198
199 static void
metar_tok_cloud(gchar * tokp,GWeatherInfo * info)200 metar_tok_cloud (gchar *tokp, GWeatherInfo *info)
201 {
202 gchar stype[4], salt[4];
203
204 g_debug ("metar_tok_cloud: %s", tokp);
205
206 strncpy (stype, tokp, 3);
207 stype[3] = 0;
208 if (strlen (tokp) == 6) {
209 strncpy (salt, tokp + 3, 3);
210 salt[3] = 0;
211 }
212
213 if (!strcmp (stype, "CLR")) {
214 info->sky = GWEATHER_SKY_CLEAR;
215 } else if (!strcmp (stype, "SKC")) {
216 info->sky = GWEATHER_SKY_CLEAR;
217 } else if (!strcmp (stype, "NSC")) {
218 info->sky = GWEATHER_SKY_CLEAR;
219 } else if (!strcmp (stype, "BKN")) {
220 info->sky = GWEATHER_SKY_BROKEN;
221 } else if (!strcmp (stype, "SCT")) {
222 info->sky = GWEATHER_SKY_SCATTERED;
223 } else if (!strcmp (stype, "FEW")) {
224 info->sky = GWEATHER_SKY_FEW;
225 } else if (!strcmp (stype, "OVC")) {
226 info->sky = GWEATHER_SKY_OVERCAST;
227 }
228 }
229
230 static void
metar_tok_pres(gchar * tokp,GWeatherInfo * info)231 metar_tok_pres (gchar *tokp, GWeatherInfo *info)
232 {
233 g_debug ("metar_tok_pres: %s", tokp);
234
235 if (*tokp == 'A') {
236 gchar sintg[3], sfract[3];
237 gint intg, fract;
238
239 strncpy (sintg, tokp + 1, 2);
240 sintg[2] = 0;
241 intg = atoi (sintg);
242
243 strncpy (sfract, tokp + 3, 2);
244 sfract[2] = 0;
245 fract = atoi (sfract);
246
247 info->pressure = (GWeatherPressure)intg + (((GWeatherPressure)fract)/100.0);
248 } else { /* *tokp == 'Q' */
249 gchar spres[5];
250 gint pres;
251
252 strncpy (spres, tokp + 1, 4);
253 spres[4] = 0;
254 pres = atoi (spres);
255
256 info->pressure = PRESSURE_MBAR_TO_INCH ((GWeatherPressure)pres);
257 }
258 }
259
260 static void
metar_tok_temp(gchar * tokp,GWeatherInfo * info)261 metar_tok_temp (gchar *tokp, GWeatherInfo *info)
262 {
263 gchar *ptemp, *pdew, *psep;
264
265 g_debug ("metar_tok_temp: %s", tokp);
266
267 psep = strchr (tokp, '/');
268 *psep = 0;
269 ptemp = tokp;
270 pdew = psep + 1;
271
272 info->temp = (*ptemp == 'M') ? TEMP_C_TO_F (-atoi (ptemp + 1))
273 : TEMP_C_TO_F (atoi (ptemp));
274 if (*pdew) {
275 info->dew = (*pdew == 'M') ? TEMP_C_TO_F (-atoi (pdew + 1))
276 : TEMP_C_TO_F (atoi (pdew));
277 } else {
278 info->dew = -1000.0;
279 }
280 }
281
282 /* How "important" are the conditions to be reported to the user.
283 Indexed by GWeatherConditionPhenomenon */
284 static const int importance_scale[] = {
285 0, /* invalid */
286 0, /* none */
287 20, /* drizzle */
288 30, /* rain */
289 35, /* snow */
290 35, /* snow grains */
291 35, /* ice crystals */
292 35, /* ice pellets */
293 35, /* hail */
294 35, /* small hail */
295 20, /* unknown precipitation */
296 10, /* mist */
297 15, /* fog */
298 15, /* smoke */
299 18, /* volcanic ash */
300 18, /* sand */
301 15, /* haze */
302 15, /* spray */
303 15, /* dust */
304 40, /* squall */
305 50, /* sandstorm */
306 50, /* duststorm */
307 70, /* funnel cloud */
308 70, /* tornado */
309 50, /* dust whirls */
310 };
311
312 static gboolean
condition_more_important(GWeatherConditions * which,GWeatherConditions * than)313 condition_more_important (GWeatherConditions *which,
314 GWeatherConditions *than)
315 {
316 if (!than->significant)
317 return TRUE;
318 if (!which->significant)
319 return FALSE;
320
321 if (importance_scale[than->phenomenon] <
322 importance_scale[which->phenomenon])
323 return TRUE;
324
325 return FALSE;
326 }
327
328 static void
metar_tok_cond(gchar * tokp,GWeatherInfo * info)329 metar_tok_cond (gchar *tokp, GWeatherInfo *info)
330 {
331 GWeatherConditions new_cond;
332 gchar squal[3], sphen[4];
333 gchar *pphen;
334
335 g_debug ("metar_tok_cond: %s", tokp);
336
337 if ((strlen (tokp) > 3) && ((*tokp == '+') || (*tokp == '-')))
338 ++tokp; /* FIX */
339
340 if ((*tokp == '+') || (*tokp == '-'))
341 pphen = tokp + 1;
342 else if (strlen (tokp) < 4)
343 pphen = tokp;
344 else
345 pphen = tokp + 2;
346
347 memset (squal, 0, sizeof (squal));
348 strncpy (squal, tokp, pphen - tokp);
349 squal[pphen - tokp] = 0;
350
351 memset (sphen, 0, sizeof (sphen));
352 strncpy (sphen, pphen, sizeof (sphen));
353 sphen[sizeof (sphen)-1] = '\0';
354
355 /* Defaults */
356 new_cond.qualifier = GWEATHER_QUALIFIER_NONE;
357 new_cond.phenomenon = GWEATHER_PHENOMENON_NONE;
358 new_cond.significant = FALSE;
359
360 if (!strcmp (squal, "")) {
361 new_cond.qualifier = GWEATHER_QUALIFIER_MODERATE;
362 } else if (!strcmp (squal, "-")) {
363 new_cond.qualifier = GWEATHER_QUALIFIER_LIGHT;
364 } else if (!strcmp (squal, "+")) {
365 new_cond.qualifier = GWEATHER_QUALIFIER_HEAVY;
366 } else if (!strcmp (squal, "VC")) {
367 new_cond.qualifier = GWEATHER_QUALIFIER_VICINITY;
368 } else if (!strcmp (squal, "MI")) {
369 new_cond.qualifier = GWEATHER_QUALIFIER_SHALLOW;
370 } else if (!strcmp (squal, "BC")) {
371 new_cond.qualifier = GWEATHER_QUALIFIER_PATCHES;
372 } else if (!strcmp (squal, "PR")) {
373 new_cond.qualifier = GWEATHER_QUALIFIER_PARTIAL;
374 } else if (!strcmp (squal, "TS")) {
375 new_cond.qualifier = GWEATHER_QUALIFIER_THUNDERSTORM;
376 } else if (!strcmp (squal, "BL")) {
377 new_cond.qualifier = GWEATHER_QUALIFIER_BLOWING;
378 } else if (!strcmp (squal, "SH")) {
379 new_cond.qualifier = GWEATHER_QUALIFIER_SHOWERS;
380 } else if (!strcmp (squal, "DR")) {
381 new_cond.qualifier = GWEATHER_QUALIFIER_DRIFTING;
382 } else if (!strcmp (squal, "FZ")) {
383 new_cond.qualifier = GWEATHER_QUALIFIER_FREEZING;
384 } else {
385 return;
386 }
387
388 if (!strcmp (sphen, "DZ")) {
389 new_cond.phenomenon = GWEATHER_PHENOMENON_DRIZZLE;
390 } else if (!strcmp (sphen, "RA")) {
391 new_cond.phenomenon = GWEATHER_PHENOMENON_RAIN;
392 } else if (!strcmp (sphen, "SN")) {
393 new_cond.phenomenon = GWEATHER_PHENOMENON_SNOW;
394 } else if (!strcmp (sphen, "SG")) {
395 new_cond.phenomenon = GWEATHER_PHENOMENON_SNOW_GRAINS;
396 } else if (!strcmp (sphen, "IC")) {
397 new_cond.phenomenon = GWEATHER_PHENOMENON_ICE_CRYSTALS;
398 } else if (!strcmp (sphen, "PL")) {
399 new_cond.phenomenon = GWEATHER_PHENOMENON_ICE_PELLETS;
400 } else if (!strcmp (sphen, "GR")) {
401 new_cond.phenomenon = GWEATHER_PHENOMENON_HAIL;
402 } else if (!strcmp (sphen, "GS")) {
403 new_cond.phenomenon = GWEATHER_PHENOMENON_SMALL_HAIL;
404 } else if (!strcmp (sphen, "UP")) {
405 new_cond.phenomenon = GWEATHER_PHENOMENON_UNKNOWN_PRECIPITATION;
406 } else if (!strcmp (sphen, "BR")) {
407 new_cond.phenomenon = GWEATHER_PHENOMENON_MIST;
408 } else if (!strcmp (sphen, "FG")) {
409 new_cond.phenomenon = GWEATHER_PHENOMENON_FOG;
410 } else if (!strcmp (sphen, "FU")) {
411 new_cond.phenomenon = GWEATHER_PHENOMENON_SMOKE;
412 } else if (!strcmp (sphen, "VA")) {
413 new_cond.phenomenon = GWEATHER_PHENOMENON_VOLCANIC_ASH;
414 } else if (!strcmp (sphen, "SA")) {
415 new_cond.phenomenon = GWEATHER_PHENOMENON_SAND;
416 } else if (!strcmp (sphen, "HZ")) {
417 new_cond.phenomenon = GWEATHER_PHENOMENON_HAZE;
418 } else if (!strcmp (sphen, "PY")) {
419 new_cond.phenomenon = GWEATHER_PHENOMENON_SPRAY;
420 } else if (!strcmp (sphen, "DU")) {
421 new_cond.phenomenon = GWEATHER_PHENOMENON_DUST;
422 } else if (!strcmp (sphen, "SQ")) {
423 new_cond.phenomenon = GWEATHER_PHENOMENON_SQUALL;
424 } else if (!strcmp (sphen, "SS")) {
425 new_cond.phenomenon = GWEATHER_PHENOMENON_SANDSTORM;
426 } else if (!strcmp (sphen, "DS")) {
427 new_cond.phenomenon = GWEATHER_PHENOMENON_DUSTSTORM;
428 } else if (!strcmp (sphen, "PO")) {
429 new_cond.phenomenon = GWEATHER_PHENOMENON_DUST_WHIRLS;
430 } else if (!strcmp (sphen, "+FC")) {
431 new_cond.phenomenon = GWEATHER_PHENOMENON_TORNADO;
432 } else if (!strcmp (sphen, "FC")) {
433 new_cond.phenomenon = GWEATHER_PHENOMENON_FUNNEL_CLOUD;
434 } else {
435 return;
436 }
437
438 if ((new_cond.qualifier != GWEATHER_QUALIFIER_NONE) || (new_cond.phenomenon != GWEATHER_PHENOMENON_NONE))
439 new_cond.significant = TRUE;
440
441 if (condition_more_important (&new_cond, &info->cond))
442 info->cond = new_cond;
443 }
444
445 #define TIME_RE_STR "([0-9]{6})Z"
446 #define WIND_RE_STR "(([0-9]{3})|VRB)([0-9]?[0-9]{2})(G[0-9]?[0-9]{2})?(KT|MPS)"
447 #define VIS_RE_STR "((([0-9]?[0-9])|(M?([12] )?([1357]/1?[0-9])))SM)|" \
448 "([0-9]{4}(N|NE|E|SE|S|SW|W|NW( [0-9]{4}(N|NE|E|SE|S|SW|W|NW))?)?)|" \
449 "CAVOK"
450 #define COND_RE_STR "(-|\\+)?(VC|MI|BC|PR|TS|BL|SH|DR|FZ)?(DZ|RA|SN|SG|IC|PE|GR|GS|UP|BR|FG|FU|VA|SA|HZ|PY|DU|SQ|SS|DS|PO|\\+?FC)"
451 #define CLOUD_RE_STR "((CLR|BKN|SCT|FEW|OVC|SKC|NSC)([0-9]{3}|///)?(CB|TCU|///)?)"
452 #define TEMP_RE_STR "(M?[0-9][0-9])/(M?(//|[0-9][0-9])?)"
453 #define PRES_RE_STR "(A|Q)([0-9]{4})"
454
455 /* POSIX regular expressions do not allow us to express "match whole words
456 * only" in a simple way, so we have to wrap them all into
457 * (^| )(...regex...)( |$)
458 */
459 #define RE_PREFIX "(^| )("
460 #define RE_SUFFIX ")( |$)"
461
462 static GRegex *metar_re[RE_NUM];
463 static void (*metar_f[RE_NUM]) (gchar *tokp, GWeatherInfo *info);
464
465 static void
metar_init_re(void)466 metar_init_re (void)
467 {
468 static gboolean initialized = FALSE;
469 if (initialized)
470 return;
471 initialized = TRUE;
472
473 metar_re[TIME_RE] = g_regex_new (RE_PREFIX TIME_RE_STR RE_SUFFIX, G_REGEX_OPTIMIZE, 0, NULL);
474 metar_re[WIND_RE] = g_regex_new (RE_PREFIX WIND_RE_STR RE_SUFFIX, G_REGEX_OPTIMIZE, 0, NULL);
475 metar_re[VIS_RE] = g_regex_new (RE_PREFIX VIS_RE_STR RE_SUFFIX, G_REGEX_OPTIMIZE, 0, NULL);
476 metar_re[COND_RE] = g_regex_new (RE_PREFIX COND_RE_STR RE_SUFFIX, G_REGEX_OPTIMIZE, 0, NULL);
477 metar_re[CLOUD_RE] = g_regex_new (RE_PREFIX CLOUD_RE_STR RE_SUFFIX, G_REGEX_OPTIMIZE, 0, NULL);
478 metar_re[TEMP_RE] = g_regex_new (RE_PREFIX TEMP_RE_STR RE_SUFFIX, G_REGEX_OPTIMIZE, 0, NULL);
479 metar_re[PRES_RE] = g_regex_new (RE_PREFIX PRES_RE_STR RE_SUFFIX, G_REGEX_OPTIMIZE, 0, NULL);
480
481 metar_f[TIME_RE] = metar_tok_time;
482 metar_f[WIND_RE] = metar_tok_wind;
483 metar_f[VIS_RE] = metar_tok_vis;
484 metar_f[COND_RE] = metar_tok_cond;
485 metar_f[CLOUD_RE] = metar_tok_cloud;
486 metar_f[TEMP_RE] = metar_tok_temp;
487 metar_f[PRES_RE] = metar_tok_pres;
488 }
489
490 gboolean
metar_parse(gchar * metar,GWeatherInfo * info)491 metar_parse (gchar *metar, GWeatherInfo *info)
492 {
493 gchar *p;
494 //gchar *rmk;
495 gint i, i2;
496 gchar *tokp;
497
498 g_return_val_if_fail (info != NULL, FALSE);
499 g_return_val_if_fail (metar != NULL, FALSE);
500
501 g_debug ("About to metar_parse: %s", metar);
502
503 metar_init_re ();
504
505 /*
506 * Force parsing to end at "RMK" field. This prevents a subtle
507 * problem when info within the remark happens to match an earlier state
508 * and, as a result, throws off all the remaining expression
509 */
510 if (0 != (p = strstr (metar, " RMK "))) {
511 *p = '\0';
512 //rmk = p + 5; // uncomment this if RMK data becomes useful
513 }
514
515 p = metar;
516 i = TIME_RE;
517 while (*p) {
518 int token_start, token_end;
519
520 i2 = RE_NUM;
521 token_start = strlen(p);
522 token_end = token_start;
523
524 for (i = 0; i < RE_NUM; i++) {
525 GMatchInfo *match_info;
526
527 if (g_regex_match_full (metar_re[i], p, -1, 0, 0, &match_info, NULL))
528 {
529 int tmp_token_start, tmp_token_end;
530 /* Skip leading and trailing space characters, if present.
531 (the regular expressions include those characters to
532 only get matches limited to whole words). */
533 g_match_info_fetch_pos (match_info, 0, &tmp_token_start, &tmp_token_end);
534 if (p[tmp_token_start] == ' ') tmp_token_start++;
535 if (p[tmp_token_end - 1] == ' ') tmp_token_end--;
536
537 /* choose the regular expression with the earliest match */
538 if (tmp_token_start < token_start) {
539 i2 = i;
540 token_start = tmp_token_start;
541 token_end = tmp_token_end;
542 }
543 }
544
545 g_match_info_unref (match_info);
546 }
547
548 if (i2 != RE_NUM) {
549 tokp = g_strndup (p + token_start, token_end - token_start);
550 metar_f[i2] (tokp, info);
551 g_free (tokp);
552 }
553
554 p += token_end;
555 p += strspn (p, " ");
556 }
557 return TRUE;
558 }
559
560 static void
metar_finish(SoupSession * session,SoupMessage * msg,gpointer data)561 metar_finish (SoupSession *session, SoupMessage *msg, gpointer data)
562 {
563 GWeatherInfo *info;
564 WeatherLocation *loc;
565 const gchar *p, *eoln;
566 gchar *searchkey, *metar;
567 gboolean success = FALSE;
568
569 if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
570 if (msg->status_code == SOUP_STATUS_CANCELLED) {
571 g_debug ("Failed to get METAR data: %d %s.\n",
572 msg->status_code, msg->reason_phrase);
573 return;
574 }
575
576 info = data;
577 if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) {
578 info->network_error = TRUE;
579 } else {
580 /* Translators: %d is an error code, and %s the error string */
581 g_warning (_("Failed to get METAR data: %d %s.\n"),
582 msg->status_code, msg->reason_phrase);
583 }
584
585 _gweather_info_request_done (info, msg);
586 return;
587 }
588
589 info = data;
590 loc = &info->location;
591
592 g_debug ("METAR data for %s", loc->code);
593 g_debug ("%s", msg->response_body->data);
594
595 searchkey = g_strdup_printf ("<raw_text>%s ", loc->code);
596 p = strstr (msg->response_body->data, searchkey);
597
598 if (p) {
599 p += strlen (searchkey);
600 eoln = strstr (p, "</raw_text>");
601 if (eoln)
602 metar = g_strndup (p, eoln - p);
603 else
604 metar = g_strdup (p);
605 success = metar_parse (metar, info);
606 g_free (metar);
607 if (success)
608 g_debug ("Successfully parsed METAR for %s", loc->code);
609 else
610 g_debug ("Failed to parse raw_text METAR for %s", loc->code);
611 } else if (!strstr (msg->response_body->data, "aviationweather.gov")) {
612 /* The response doesn't even seem to have come from NOAA...
613 * most likely it is a wifi hotspot login page. Call that a
614 * network error.
615 */
616 info->network_error = TRUE;
617 g_debug ("Response to query for %s did not come from correct server", loc->code);
618 } else {
619 g_debug ("Failed to parse METAR for %s", loc->code);
620 }
621
622 g_free (searchkey);
623
624 if (!info->valid)
625 info->valid = success;
626 _gweather_info_request_done (info, msg);
627 }
628
629 /* Read current conditions and fill in info structure */
630 void
metar_start_open(GWeatherInfo * info)631 metar_start_open (GWeatherInfo *info)
632 {
633 WeatherLocation *loc;
634 SoupMessage *msg;
635
636 g_return_if_fail (info != NULL);
637
638 info->valid = info->network_error = FALSE;
639 loc = &info->location;
640
641 if (!loc->latlon_valid)
642 return;
643
644 g_debug ("metar_start_open, requesting: https://www.aviationweather.gov/adds/dataserver_current/httpparam?dataSource=metars&requestType=retrieve&format=xml&hoursBeforeNow=3&mostRecent=true&fields=raw_text&stationString=%s", loc->code);
645 msg = soup_form_request_new (
646 "GET", "https://www.aviationweather.gov/adds/dataserver_current/httpparam",
647 "dataSource", "metars",
648 "requestType", "retrieve",
649 "format", "xml",
650 "hoursBeforeNow", "3",
651 "mostRecent", "true",
652 "fields", "raw_text",
653 "stationString", loc->code,
654 NULL);
655 _gweather_info_begin_request (info, msg);
656 soup_session_queue_message (info->session, msg, metar_finish, info);
657 }
658