]> git.mxchange.org Git - simgear.git/blob - simgear/environment/metar.cxx
- allow for (rather unusual) ////// cloud groups
[simgear.git] / simgear / environment / metar.cxx
1 // metar interface class
2 //
3 // Written by Melchior FRANZ, started December 2003.
4 //
5 // Copyright (C) 2003  Melchior FRANZ - mfranz@aon.at
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU 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.
20 //
21 // $Id$
22
23 /**
24  * @file metar.cxx
25  * Interface for encoded Meteorological Aerodrome Reports (METAR).
26  */
27 #ifdef HAVE_CONFIG_H
28 #  include <simgear_config.h>
29 #endif
30
31 #include <string>
32 #include <time.h>
33
34 #include <simgear/io/sg_socket.hxx>
35 #include <simgear/debug/logstream.hxx>
36 #include <simgear/structure/exception.hxx>
37
38 #include "metar.hxx"
39
40 #define NaN SGMetarNaN
41
42 /**
43  * The constructor takes a Metar string, or a four-letter ICAO code. In the
44  * latter case the metar string is downloaded from
45  * http://weather.noaa.gov/pub/data/observations/metar/stations/.
46  * The constructor throws sg_io_exceptions on failure. The "METAR"
47  * keyword has no effect (apart from incrementing the group counter
48  * @a grpcount) and can be left away. A keyword "SPECI" is
49  * likewise accepted.
50  *
51  * @param m     ICAO station id or metar string
52  * @param proxy proxy host (optional; default: "")
53  * @param port  proxy port (optional; default: "80")
54  * @param auth  proxy authorization information (optional; default: "")
55  *
56  * @par Examples:
57  * @code
58  * SGMetar *m = new SGMetar("METAR KSFO 061656Z 19004KT 9SM SCT100 OVC200 08/03 A3013");
59  * double t = m->getTemperature_F();
60  * delete m;
61  *
62  * SGMetar n("KSFO", "proxy.provider.foo", "3128", "proxy-password");
63  * double d = n.getDewpoint_C();
64  * @endcode
65  */
66 SGMetar::SGMetar(const string& m, const string& proxy, const string& port,
67                 const string& auth, const time_t time) :
68         _grpcount(0),
69         _x_proxy(false),
70         _year(-1),
71         _month(-1),
72         _day(-1),
73         _hour(-1),
74         _minute(-1),
75         _report_type(-1),
76         _wind_dir(-1),
77         _wind_speed(NaN),
78         _gust_speed(NaN),
79         _wind_range_from(-1),
80         _wind_range_to(-1),
81         _temp(NaN),
82         _dewp(NaN),
83         _pressure(NaN),
84         _rain(false),
85         _hail(false),
86         _snow(false),
87         _cavok(false)
88 {
89         if (m.length() == 4 && isalnum(m[0]) && isalnum(m[1]) && isalnum(m[2]) && isalnum(m[3])) {
90                 for (int i = 0; i < 4; i++)
91                         _icao[i] = toupper(m[i]);
92                 _icao[4] = '\0';
93                 _data = loadData(_icao, proxy, port, auth, time);
94         } else {
95                 _data = new char[m.length() + 2];       // make room for " \0"
96                 strcpy(_data, m.c_str());
97                 _url = _data;
98         }
99         normalizeData();
100
101         _m = _data;
102         _icao[0] = '\0';
103
104         // NOAA preample
105         if (!scanPreambleDate())
106                 useCurrentDate();
107         scanPreambleTime();
108
109         // METAR header
110         scanType();
111         if (!scanId() || !scanDate()) {
112                 delete[] _data;
113                 throw sg_io_exception("metar data bogus ", sg_location(_url));
114         }
115         scanModifier();
116
117         // base set
118         scanWind();
119         scanVariability();
120         while (scanVisibility()) ;
121         while (scanRwyVisRange()) ;
122         while (scanWeather()) ;
123         while (scanSkyCondition()) ;
124         scanTemperature();
125         scanPressure();
126         while (scanSkyCondition()) ;
127         while (scanRunwayReport()) ;
128         scanWindShear();
129
130         // appendix
131         while (scanColorState()) ;
132         scanTrendForecast();
133         while (scanRunwayReport()) ;
134         scanRemainder();
135         scanRemark();
136
137         if (_grpcount < 4) {
138                 delete[] _data;
139                 throw sg_io_exception("metar data incomplete ", sg_location(_url));
140         }
141
142         _url = "";
143 }
144
145
146 /**
147   * Clears lists and maps to discourage access after destruction.
148   */
149 SGMetar::~SGMetar()
150 {
151         _clouds.clear();
152         _runways.clear();
153         _weather.clear();
154         delete[] _data;
155 }
156
157
158 void SGMetar::useCurrentDate()
159 {
160         struct tm now;
161         time_t now_sec = time(0);
162 #if defined( _MSC_VER ) || defined ( __MINGW32__ )
163         now = *gmtime(&now_sec);
164 #else
165         gmtime_r(&now_sec, &now);
166 #endif
167         _year = now.tm_year + 1900;
168         _month = now.tm_mon + 1;
169 }
170
171
172 /**
173   * If called with "KSFO" loads data from
174   * @code
175   * http://weather.noaa.gov/pub/data/observations/metar/stations/KSFO.TXT.
176   * @endcode
177   * Throws sg_io_exception on failure. Gives up after waiting longer than 10 seconds.
178   *
179   * @param id four-letter ICAO Metar station code, e.g. "KSFO".
180   * @param proxy proxy host (optional; default: "")
181   * @param port  proxy port (optional; default: "80")
182   * @param auth  proxy authorization information (optional; default: "")
183   * @return pointer to Metar data string, allocated by new char[].
184   * @see rfc2068.txt for proxy spec ("Proxy-Authorization")
185   */
186 char *SGMetar::loadData(const char *id, const string& proxy, const string& port,
187                 const string& auth, time_t time)
188 {
189         const int buflen = 512;
190         char buf[2 * buflen];
191
192         string host = proxy.empty() ? "weather.noaa.gov" : proxy;
193         string path = "/pub/data/observations/metar/stations/";
194
195         path += string(id) + ".TXT";
196         _url = "http://weather.noaa.gov" + path;
197
198         SGSocket *sock = new SGSocket(host, port.empty() ? "80" : port, "tcp");
199         sock->set_timeout(10000);
200         if (!sock->open(SG_IO_OUT)) {
201                 delete sock;
202                 throw sg_io_exception("cannot connect to ", sg_location(host));
203         }
204
205         string get = "GET ";
206         if (!proxy.empty())
207                 get += "http://weather.noaa.gov";
208
209         sprintf(buf, "%ld", time);
210         get += path + " HTTP/1.0\015\012X-Time: " + buf + "\015\012";
211
212         if (!auth.empty())
213                 get += "Proxy-Authorization: " + auth + "\015\012";
214
215         get += "\015\012";
216         sock->writestring(get.c_str());
217
218         int i;
219
220         // skip HTTP header
221         while ((i = sock->readline(buf, buflen))) {
222                 if (i <= 2 && isspace(buf[0]) && (!buf[1] || isspace(buf[1])))
223                         break;
224                 if (!strncmp(buf, "X-MetarProxy: ", 13))
225                         _x_proxy = true;
226         }
227         if (i) {
228                 i = sock->readline(buf, buflen);
229                 if (i)
230                         sock->readline(&buf[i], buflen);
231         }
232
233         sock->close();
234         delete sock;
235
236         char *b = buf;
237         scanBoundary(&b);
238         if (*b == '<')
239                 throw sg_io_exception("no metar data available from ", 
240                                 sg_location(_url));
241
242         char *metar = new char[strlen(b) + 2];  // make room for " \0"
243         strcpy(metar, b);
244         return metar;
245 }
246
247
248 /**
249   * Replace any number of subsequent spaces by just one space, and add
250   * a trailing space. This makes scanning for things like "ALL RWY" easier.
251   */
252 void SGMetar::normalizeData()
253 {
254         char *src, *dest;
255         for (src = dest = _data; (*dest++ = *src++); )
256                 while (*src == ' ' && src[1] == ' ')
257                         src++;
258         for (dest--; isspace(*--dest); ) ;
259         *++dest = ' ';
260         *++dest = '\0';
261 }
262
263
264 // \d\d\d\d/\d\d/\d\d
265 bool SGMetar::scanPreambleDate()
266 {
267         char *m = _m;
268         int year, month, day;
269         if (!scanNumber(&m, &year, 4))
270                 return false;
271         if (*m++ != '/')
272                 return false;
273         if (!scanNumber(&m, &month, 2))
274                 return false;
275         if (*m++ != '/')
276                 return false;
277         if (!scanNumber(&m, &day, 2))
278                 return false;
279         if (!scanBoundary(&m))
280                 return false;
281         _year = year;
282         _month = month;
283         _day = day;
284         _m = m;
285         return true;
286 }
287
288
289 // \d\d:\d\d
290 bool SGMetar::scanPreambleTime()
291 {
292         char *m = _m;
293         int hour, minute;
294         if (!scanNumber(&m, &hour, 2))
295                 return false;
296         if (*m++ != ':')
297                 return false;
298         if (!scanNumber(&m, &minute, 2))
299                 return false;
300         if (!scanBoundary(&m))
301                 return false;
302         _hour = hour;
303         _minute = minute;
304         _m = m;
305         return true;
306 }
307
308
309 // (METAR|SPECI)
310 bool SGMetar::scanType()
311 {
312         if (strncmp(_m, "METAR ", 6) && strncmp(_m, "SPECI ", 6))
313                 return false;
314         _m += 6;
315         _grpcount++;
316         return true;
317 }
318
319
320 // [A-Z]{4}
321 bool SGMetar::scanId()
322 {
323         char *m = _m;
324         for (int i = 0; i < 4; m++, i++)
325                 if (!(isalpha(*m) || isdigit(*m)))
326                         return false;
327         if (!scanBoundary(&m))
328                 return false;
329         strncpy(_icao, _m, 4);
330         _icao[4] = '\0';
331         _m = m;
332         _grpcount++;
333         return true;
334 }
335
336
337 // \d{6}Z
338 bool SGMetar::scanDate()
339 {
340         char *m = _m;
341         int day, hour, minute;
342         if (!scanNumber(&m, &day, 2))
343                 return false;
344         if (!scanNumber(&m, &hour, 2))
345                 return false;
346         if (!scanNumber(&m, &minute, 2))
347                 return false;
348         if (*m++ != 'Z')
349                 return false;
350         if (!scanBoundary(&m))
351                 return false;
352         _day = day;
353         _hour = hour;
354         _minute = minute;
355         _m = m;
356         _grpcount++;
357         return true;
358 }
359
360
361 // (NIL|AUTO|COR|RTD)
362 bool SGMetar::scanModifier()
363 {
364         char *m = _m;
365         int type;
366         if (!strncmp(m, "NIL", 3)) {
367                 _m += strlen(_m);
368                 return true;
369         }
370         if (!strncmp(m, "AUTO", 4))                     // automatically generated
371                 m += 4, type = AUTO;
372         else if (!strncmp(m, "COR", 3))                 // manually corrected
373                 m += 3, type = COR;
374         else if (!strncmp(m, "RTD", 3))                 // routine delayed
375                 m += 3, type = RTD;
376         else
377                 return false;
378         if (!scanBoundary(&m))
379                 return false;
380         _report_type = type;
381         _m = m;
382         _grpcount++;
383         return true;
384 }
385
386
387 // (\d{3}|VRB)\d{1,3}(G\d{2,3})?(KT|KMH|MPS)
388 bool SGMetar::scanWind()
389 {
390         char *m = _m;
391         int dir;
392         if (!strncmp(m, "VRB", 3))
393                 m += 3, dir = -1;
394         else if (!scanNumber(&m, &dir, 3))
395                 return false;
396
397         int i;
398         if (!scanNumber(&m, &i, 2, 3))
399                 return false;
400         double speed = i;
401
402         double gust = NaN;
403         if (*m == 'G') {
404                 m++;
405                 if (!scanNumber(&m, &i, 2, 3))
406                         return false;
407                 gust = i;
408         }
409         double factor;
410         if (!strncmp(m, "KT", 2))
411                 m += 2, factor = SG_KT_TO_MPS;
412         else if (!strncmp(m, "KMH", 3))
413                 m += 3, factor = SG_KMH_TO_MPS;
414         else if (!strncmp(m, "KPH", 3))         // ??
415                 m += 3, factor = SG_KMH_TO_MPS;
416         else if (!strncmp(m, "MPS", 3))
417                 m += 3, factor = 1.0;
418         else
419                 return false;
420         if (!scanBoundary(&m))
421                 return false;
422         _m = m;
423         _wind_dir = dir;
424         _wind_speed = speed * factor;
425         if (gust != NaN)
426                 _gust_speed = gust * factor;
427         _grpcount++;
428         return true;
429 }
430
431
432 // \d{3}V\d{3}
433 bool SGMetar::scanVariability()
434 {
435         char *m = _m;
436         int from, to;
437         if (!scanNumber(&m, &from, 3))
438                 return false;
439         if (*m++ != 'V')
440                 return false;
441         if (!scanNumber(&m, &to, 3))
442                 return false;
443         if (!scanBoundary(&m))
444                 return false;
445         _m = m;
446         _wind_range_from = from;
447         _wind_range_to = to;
448         _grpcount++;
449         return true;
450 }
451
452
453 bool SGMetar::scanVisibility()
454 // TODO: if only directed vis are given, do still set min/max
455 {
456         if (!strncmp(_m, "//// ", 5)) {         // spec compliant?
457                 _m += 5;
458                 _grpcount++;
459                 return true;
460         }
461
462         char *m = _m;
463         double distance;
464         int i, dir = -1;
465         int modifier = SGMetarVisibility::EQUALS;
466 // \d{4}(N|NE|E|SE|S|SW|W|NW)?
467         if (scanNumber(&m, &i, 4)) {
468                 if (*m == 'E')
469                         m++, dir = 90;
470                 else if (*m == 'W')
471                         m++, dir = 270;
472                 else if (*m == 'N') {
473                         m++;
474                         if (*m == 'E')
475                                 m++, dir = 45;
476                         else if (*m == 'W')
477                                 m++, dir = 315;
478                         else
479                                 dir = 0;
480                 } else if (*m == 'S') {
481                         m++;
482                         if (*m == 'E')
483                                 m++, dir = 135;
484                         else if (*m == 'W')
485                                 m++, dir = 225;
486                         else
487                                 dir = 180;
488                 }
489                 if (i == 0)
490                         i = 50, modifier = SGMetarVisibility::LESS_THAN;
491                 else if (i == 9999)
492                         i++, modifier = SGMetarVisibility::GREATER_THAN;
493                 distance = i;
494         } else {
495 // M?(\d{1,2}|\d{1,2}/\d{1,2}|\d{1,2} \d{1,2}/\d{1,2})(SM|KM)
496                 modifier = 0;
497                 if (*m == 'M')
498                         m++, modifier = SGMetarVisibility::LESS_THAN;
499
500                 if (!scanNumber(&m, &i, 1, 2))
501                         return false;
502                 distance = i;
503
504                 if (*m == '/') {
505                         m++;
506                         if (!scanNumber(&m, &i, 1, 2))
507                                 return false;
508                         distance /= i;
509                 } else if (*m == ' ') {
510                         m++;
511                         int denom;
512                         if (!scanNumber(&m, &i, 1, 2))
513                                 return false;
514                         if (*m++ != '/')
515                                 return false;
516                         if (!scanNumber(&m, &denom, 1, 2))
517                                 return false;
518                         distance += (double)i / denom;
519                 }
520
521                 if (!strncmp(m, "SM", 2))
522                         distance *= SG_SM_TO_METER, m += 2;
523                 else if (!strncmp(m, "KM", 2))
524                         distance *= 1000, m += 2;
525                 else
526                         return false;
527         }
528         if (!scanBoundary(&m))
529                 return false;
530
531         SGMetarVisibility *v;
532         if (dir != -1)
533                 v = &_dir_visibility[dir / 45];
534         else if (_min_visibility._distance == NaN)
535                 v = &_min_visibility;
536         else
537                 v = &_max_visibility;
538
539         v->_distance = distance;
540         v->_modifier = modifier;
541         v->_direction = dir;
542         _m = m;
543         _grpcount++;
544         return true;
545 }
546
547
548 // R\d\d[LCR]?/([PM]?\d{4}V)?[PM]?\d{4}(FT)?[DNU]?
549 bool SGMetar::scanRwyVisRange()
550 {
551         char *m = _m;
552         int i;
553         SGMetarRunway r;
554         if (*m++ != 'R')
555                 return false;
556         if (!scanNumber(&m, &i, 2))
557                 return false;
558         if (*m == 'L' || *m == 'C' || *m == 'R')
559                 m++;
560
561         char id[4];
562         strncpy(id, _m + 1, i = m - _m - 1);
563         id[i] = '\0';
564
565         if (*m++ != '/')
566                 return false;
567
568         int from, to;
569         if (*m == 'P')
570                 m++, r._min_visibility._modifier = SGMetarVisibility::GREATER_THAN;
571         else if (*m == 'M')
572                 m++, r._min_visibility._modifier = SGMetarVisibility::LESS_THAN;
573         if (!scanNumber(&m, &from, 4))
574                 return false;
575         if (*m == 'V') {
576                 m++;
577                 if (*m == 'P')
578                         m++, r._max_visibility._modifier = SGMetarVisibility::GREATER_THAN;
579                 else if (*m == 'M')
580                         m++, r._max_visibility._modifier = SGMetarVisibility::LESS_THAN;
581                 if (!scanNumber(&m, &to, 4))
582                         return false;
583         } else
584                 to = from;
585
586         if (!strncmp(m, "FT", 2)) {
587                 from = int(from * SG_FEET_TO_METER);
588                 to = int(to * SG_FEET_TO_METER);
589                 m += 2;
590         }
591         r._min_visibility._distance = from;
592         r._max_visibility._distance = to;
593
594         if (*m == '/')                                  // this is not in the spec!
595                 *m++;
596         if (*m == 'D')
597                 m++, r._min_visibility._tendency = SGMetarVisibility::DECREASING;
598         else if (*m == 'N')
599                 m++, r._min_visibility._tendency = SGMetarVisibility::STABLE;
600         else if (*m == 'U')
601                 m++, r._min_visibility._tendency = SGMetarVisibility::INCREASING;
602
603         if (!scanBoundary(&m))
604                 return false;
605         _m = m;
606
607         _runways[id]._min_visibility = r._min_visibility;
608         _runways[id]._max_visibility = r._max_visibility;
609         _grpcount++;
610         return true;
611 }
612
613
614 static const struct Token special[] = {
615         { "NSW",  "no significant weather" },
616         { "VCSH", "showers in the vicinity" },
617         { "VCTS", "thunderstorm in the vicinity" },
618         { 0, 0 }
619 };
620
621
622 static const struct Token description[] = {
623         { "SH", "showers of" },
624         { "TS", "thunderstorm with" },
625         { "BC", "patches of" },
626         { "BL", "blowing" },
627         { "DR", "low drifting" },
628         { "FZ", "freezing" },
629         { "MI", "shallow" },
630         { "PR", "partial" },
631         { 0, 0 }
632 };
633
634
635 static const struct Token phenomenon[] = {
636         { "DZ",   "drizzle" },
637         { "GR",   "hail" },
638         { "GS",   "small hail and/or snow pellets" },
639         { "IC",   "ice crystals" },
640         { "PE",   "ice pellets" },
641         { "RA",   "rain" },
642         { "SG",   "snow grains" },
643         { "SN",   "snow" },
644         { "UP",   "unknown precipitation" },
645         { "BR",   "mist" },
646         { "DU",   "widespread dust" },
647         { "FG",   "fog" },
648         { "FGBR", "fog bank" },
649         { "FU",   "smoke" },
650         { "HZ",   "haze" },
651         { "PY",   "spray" },
652         { "SA",   "sand" },
653         { "VA",   "volcanic ash" },
654         { "DS",   "duststorm" },
655         { "FC",   "funnel cloud/tornado waterspout" },
656         { "PO",   "well-developed dust/sand whirls" },
657         { "SQ",   "squalls" },
658         { "SS",   "sandstorm" },
659         { "UP",   "unknown" },  // ... due to failed automatic acquisition
660         { 0, 0 }
661 };
662
663
664 // (+|-|VC)?(NSW|MI|PR|BC|DR|BL|SH|TS|FZ)?((DZ|RA|SN|SG|IC|PE|GR|GS|UP){0,3})(BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS){0,3}
665 bool SGMetar::scanWeather()
666 {
667         char *m = _m;
668         string weather;
669         const struct Token *a;
670         if ((a = scanToken(&m, special))) {
671                 if (!scanBoundary(&m))
672                         return false;
673                 _weather.push_back(a->text);
674                 _m = m;
675                 return true;
676         }
677
678         string pre, post;
679         int intensity = 0;
680         if (*m == '-')
681                 m++, pre = "light ", intensity = 1;
682         else if (*m == '+')
683                 m++, pre = "heavy ", intensity = 3;
684         else if (!strncmp(m, "VC", 2))
685                 m += 2, post = "in the vicinity ";
686         else
687                 pre = "moderate ", intensity = 2;
688
689         int i;
690         for (i = 0; i < 3; i++) {
691                 if (!(a = scanToken(&m, description)))
692                         break;
693                 weather += string(a->text) + " ";
694         }
695         for (i = 0; i < 3; i++) {
696                 if (!(a = scanToken(&m, phenomenon)))
697                         break;
698                 weather += string(a->text) + " ";
699                 if (!strcmp(a->id, "RA"))
700                         _rain = intensity;
701                 else if (!strcmp(a->id, "HA"))
702                         _hail = intensity;
703                 else if (!strcmp(a->id, "SN"))
704                         _snow = intensity;
705         }
706         if (!weather.length())
707                 return false;
708         if (!scanBoundary(&m))
709                 return false;
710         _m = m;
711         weather = pre + weather + post;
712         weather.erase(weather.length() - 1);
713         _weather.push_back(weather);
714         _grpcount++;
715         return true;
716 }
717
718
719 static const struct Token cloud_types[] = {
720         { "AC",    "altocumulus" },
721         { "ACC",   "altocumulus castellanus" },
722         { "ACSL",  "altocumulus standing lenticular" },
723         { "AS",    "altostratus" },
724         { "CB",    "cumulonimbus" },
725         { "CBMAM", "cumulonimbus mammatus" },
726         { "CC",    "cirrocumulus" },
727         { "CCSL",  "cirrocumulus standing lenticular" },
728         { "CI",    "cirrus" },
729         { "CS",    "cirrostratus" },
730         { "CU",    "cumulus" },
731         { "CUFRA", "cumulus fractus" },
732         { "NS",    "nimbostratus" },
733         { "SAC",   "stratoaltocumulus" },               // guessed
734         { "SC",    "stratocumulus" },
735         { "SCSL",  "stratocumulus standing lenticular" },
736         { "ST",    "stratus" },
737         { "STFRA", "stratus fractus" },
738         { "TCU",   "towering cumulus" },
739         { 0, 0 }
740 };
741
742
743 // (FEW|SCT|BKN|OVC|SKC|CLR|CAVOK|VV)([0-9]{3}|///)?[:cloud_type:]?
744 bool SGMetar::scanSkyCondition()
745 {
746         char *m = _m;
747         int i;
748         SGMetarCloud cl;
749
750         if (!strncmp(m, "//////", 6)) {
751                 m += 6;
752                 if (!scanBoundary(&m))
753                         return false;
754                 _m = m;
755                 return true;
756         }
757
758         if (!strncmp(m, "CLR", i = 3)                           // clear
759                         || !strncmp(m, "SKC", i = 3)            // sky clear
760                         || !strncmp(m, "NSC", i = 3)            // no significant clouds
761                         || !strncmp(m, "CAVOK", i = 5)) {       // ceiling and visibility OK (implies 9999)
762                 m += i;
763                 if (!scanBoundary(&m))
764                         return false;
765
766                 if (i == 3) {
767                         cl._coverage = 0;
768                         _clouds.push_back(cl);
769                 } else {
770                         _cavok = true;
771                 }
772                 _m = m;
773                 return true;
774         }
775
776         if (!strncmp(m, "VV", i = 2))                           // vertical visibility
777                 ;
778         else if (!strncmp(m, "FEW", i = 3))
779                 cl._coverage = 1;
780         else if (!strncmp(m, "SCT", i = 3))
781                 cl._coverage = 2;
782         else if (!strncmp(m, "BKN", i = 3))
783                 cl._coverage = 3;
784         else if (!strncmp(m, "OVC", i = 3))
785                 cl._coverage = 4;
786         else
787                 return false;
788         m += i;
789
790         if (!strncmp(m, "///", 3))      // vis not measurable (e.g. because of heavy snowing)
791                 m += 3, i = -1;
792         else if (scanBoundary(&m)) {
793                 _m = m;
794                 return true;                            // ignore single OVC/BKN/...
795         } else if (!scanNumber(&m, &i, 3))
796                 i = -1;
797
798         if (cl._coverage == -1) {
799                 if (!scanBoundary(&m))
800                         return false;
801                 if (i == -1)                    // 'VV///'
802                         _vert_visibility._modifier = SGMetarVisibility::NOGO;
803                 else
804                         _vert_visibility._distance = i * 100 * SG_FEET_TO_METER;
805                 _m = m;
806                 return true;
807         }
808
809         if (i != -1)
810                 cl._altitude = i * 100 * SG_FEET_TO_METER;
811
812         const struct Token *a;
813         if ((a = scanToken(&m, cloud_types))) {
814                 cl._type = a->id;
815                 cl._type_long = a->text;
816         }
817         if (!scanBoundary(&m))
818                 return false;
819         _clouds.push_back(cl);
820         _m = m;
821         _grpcount++;
822         return true;
823 }
824
825
826 // M?[0-9]{2}/(M?[0-9]{2})?            (spec)
827 // (M?[0-9]{2}|XX)/(M?[0-9]{2}|XX)?    (Namibia)
828 bool SGMetar::scanTemperature()
829 {
830         char *m = _m;
831         int sign = 1, temp, dew;
832         if (!strncmp(m, "XX/XX", 5)) {          // not spec compliant!
833                 _m += 5;
834                 return scanBoundary(&_m);
835         }
836
837         if (*m == 'M')
838                 m++, sign = -1;
839         if (!scanNumber(&m, &temp, 2))
840                 return false;
841         temp *= sign;
842
843         if (*m++ != '/')
844                 return false;
845         if (!scanBoundary(&m)) {
846                 if (!strncmp(m, "XX", 2))       // not spec compliant!
847                         m += 2, sign = 0, dew = temp;
848                 else {
849                         sign = 1;
850                         if (*m == 'M')
851                                 m++, sign = -1;
852                         if (!scanNumber(&m, &dew, 2))
853                                 return false;
854                 }
855                 if (!scanBoundary(&m))
856                         return false;
857                 if (sign)
858                         _dewp = sign * dew;
859         }
860         _temp = temp;
861         _m = m;
862         _grpcount++;
863         return true;
864 }
865
866
867 double SGMetar::getRelHumidity() const
868 {
869         if (_temp == NaN || _dewp == NaN)
870                 return NaN;
871         double dewp = pow(10.0, 7.5 * _dewp / (237.7 + _dewp));
872         double temp = pow(10.0, 7.5 * _temp / (237.7 + _temp));
873         return dewp * 100 / temp;
874 }
875
876
877 // [AQ]\d{4}             (spec)
878 // [AQ]\d{2}(\d{2}|//)   (Namibia)
879 bool SGMetar::scanPressure()
880 {
881         char *m = _m;
882         double factor;
883         int press, i;
884
885         if (*m == 'A')
886                 factor = SG_INHG_TO_PA / 100;
887         else if (*m == 'Q')
888                 factor = 100;
889         else
890                 return false;
891         m++;
892         if (!scanNumber(&m, &press, 2))
893                 return false;
894         press *= 100;
895         if (!strncmp(m, "//", 2))       // not spec compliant!
896                 m += 2;
897         else if (scanNumber(&m, &i, 2))
898                 press += i;
899         else
900                 return false;
901         if (!scanBoundary(&m))
902                 return false;
903         _pressure = press * factor;
904         _m = m;
905         _grpcount++;
906         return true;
907 }
908
909
910 static const char *runway_deposit[] = {
911         "clear and dry",
912         "damp",
913         "wet or puddles",
914         "frost",
915         "dry snow",
916         "wet snow",
917         "slush",
918         "ice",
919         "compacted snow",
920         "frozen ridges"
921 };
922
923
924 static const char *runway_deposit_extent[] = {
925         0, "1-10%", "11-25%", 0, 0, "26-50%", 0, 0, 0, "51-100%"
926 };
927
928
929 static const char *runway_friction[] = {
930         0,
931         "poor braking action",
932         "poor/medium braking action",
933         "medium braking action",
934         "medium/good braking action",
935         "good braking action",
936         0, 0, 0,
937         "friction: unreliable measurement"
938 };
939
940
941 // \d\d(CLRD|[\d/]{4})(\d\d|//)
942 bool SGMetar::scanRunwayReport()
943 {
944         char *m = _m;
945         int i;
946         char id[4];
947         SGMetarRunway r;
948
949         if (!scanNumber(&m, &i, 2))
950                 return false;
951         if (i == 88)
952                 strcpy(id, "ALL");
953         else if (i == 99)
954                 strcpy(id, "REP");              // repetition of previous report
955         else if (i >= 50) {
956                 i -= 50;
957                 id[0] = i / 10 + '0', id[1] = i % 10 + '0', id[2] = 'R', id[3] = '\0';
958         } else
959                 id[0] = i / 10 + '0', id[1] = i % 10 + '0', id[2] = '\0';
960
961         if (!strncmp(m, "CLRD", 4)) {
962                 m += 4;                                                 // runway cleared
963                 r._deposit_string = "cleared";
964         } else {
965                 if (scanNumber(&m, &i, 1)) {
966                         r._deposit = i;
967                         r._deposit_string = runway_deposit[i];
968                 } else if (*m == '/')
969                         m++;
970                 else
971                         return false;
972
973                 if (*m == '1' || *m == '2' || *m == '5' || *m == '9') { // extent of deposit
974                         r._extent = *m - '0';
975                         r._extent_string = runway_deposit_extent[*m - '0'];
976                 } else if (*m != '/')
977                         return false;
978
979                 m++;
980                 i = -1;
981                 if (!strncmp(m, "//", 2))
982                         m += 2;
983                 else if (!scanNumber(&m, &i, 2))
984                         return false;
985
986                 if (i == 0)
987                         r._depth = 0.0005;                              // < 1 mm deep (let's say 0.5 :-)
988                 else if (i > 0 && i <= 90)
989                         r._depth = i / 1000.0;                          // i mm deep
990                 else if (i >= 92 && i <= 98)
991                         r._depth = (i - 90) / 20.0;
992                 else if (i == 99)
993                         r._comment = "runway not in use";
994                 else if (i == -1)                                       // no depth given ("//")
995                         ;
996                 else
997                         return false;
998         }
999         i = -1;
1000         if (m[0] == '/' && m[1] == '/')
1001                 m += 2;
1002         else if (!scanNumber(&m, &i, 2))
1003                 return false;
1004         if (i >= 1 && i < 90) {
1005                 r._friction = i / 100.0;
1006         } else if ((i >= 91 && i <= 95) || i == 99) {
1007                 r._friction_string = runway_friction[i - 90];
1008         }
1009         if (!scanBoundary(&m))
1010                 return false;
1011
1012         _runways[id]._deposit = r._deposit;
1013         _runways[id]._deposit_string = r._deposit_string;
1014         _runways[id]._extent = r._extent;
1015         _runways[id]._extent_string = r._extent_string;
1016         _runways[id]._depth = r._depth;
1017         _runways[id]._friction = r._friction;
1018         _runways[id]._friction_string = r._friction_string;
1019         _runways[id]._comment = r._comment;
1020         _m = m;
1021         _grpcount++;
1022         return true;
1023 }
1024
1025
1026 // WS (ALL RWYS?|RWY ?\d\d[LCR]?)?
1027 bool SGMetar::scanWindShear()
1028 {
1029         char *m = _m;
1030         if (strncmp(m, "WS", 2))
1031                 return false;
1032         m += 2;
1033         if (!scanBoundary(&m))
1034                 return false;
1035
1036         if (!strncmp(m, "ALL", 3)) {
1037                 m += 3;
1038                 if (!scanBoundary(&m))
1039                         return false;
1040                 if (strncmp(m, "RWY", 3))
1041                         return false;
1042                 m += 3;
1043                 if (*m == 'S')
1044                         m++;
1045                 if (!scanBoundary(&m))
1046                         return false;
1047                 _runways["ALL"]._wind_shear = true;
1048                 _m = m;
1049                 return true;
1050         }
1051
1052         char id[4], *mm;
1053         int i, cnt;
1054         for (cnt = 0;; cnt++) {                 // ??
1055                 if (strncmp(m, "RWY", 3))
1056                         break;
1057                 m += 3;
1058                 scanBoundary(&m);
1059                 mm = m;
1060                 if (!scanNumber(&m, &i, 2))
1061                         return false;
1062                 if (*m == 'L' || *m == 'C' || *m == 'R')
1063                         m++;
1064                 strncpy(id, mm, i = m - mm);
1065                 id[i] = '\0';
1066                 if (!scanBoundary(&m))
1067                         return false;
1068                 _runways[id]._wind_shear = true;
1069         }
1070         if (!cnt)
1071                 _runways["ALL"]._wind_shear = true;
1072         _m = m;
1073         return true;
1074 }
1075
1076
1077 bool SGMetar::scanTrendForecast()
1078 {
1079         char *m = _m;
1080         if (strncmp(m, "NOSIG", 5))
1081                 return false;
1082
1083         m += 5;
1084         if (!scanBoundary(&m))
1085                 return false;
1086         _m = m;
1087         return true;
1088 }
1089
1090
1091 // (BLU|WHT|GRN|YLO|AMB|RED)
1092 static const struct Token colors[] = {
1093         { "BLU", "Blue" },      // 2500 ft,  8.0 km
1094         { "WHT", "White" },     // 1500 ft,  5.0 km
1095         { "GRN", "Green" },     //  700 ft,  3.7 km
1096         { "YLO", "Yellow" },    //  300 ft,  1.6 km
1097         { "AMB", "Amber" },     //  200 ft,  0.8 km
1098         { "RED", "Red" },       // <200 ft, <0.8 km
1099         { 0, 0 }
1100 };
1101
1102
1103 bool SGMetar::scanColorState()
1104 {
1105         char *m = _m;
1106         const struct Token *a;
1107         if (!(a = scanToken(&m, colors)))
1108                 return false;
1109         if (!scanBoundary(&m))
1110                 return false;
1111         //printf(Y"Code %s\n"N, a->text);
1112         _m = m;
1113         return true;
1114 }
1115
1116
1117 bool SGMetar::scanRemark()
1118 {
1119         if (strncmp(_m, "RMK", 3))
1120                 return false;
1121         _m += 3;
1122         if (!scanBoundary(&_m))
1123                 return false;
1124
1125         while (*_m) {
1126                 if (!scanRunwayReport()) {
1127                         while (*_m && !isspace(*_m))
1128                                 _m++;
1129                         scanBoundary(&_m);
1130                 }
1131         }
1132         return true;
1133 }
1134
1135
1136 bool SGMetar::scanRemainder()
1137 {
1138         char *m = _m;
1139         if (!(strncmp(m, "NOSIG", 5))) {
1140                 m += 5;
1141                 if (scanBoundary(&m))
1142                         _m = m; //_comment.push_back("No significant tendency");
1143         }
1144
1145         if (!scanBoundary(&m))
1146                 return false;
1147         _m = m;
1148         return true;
1149 }
1150
1151
1152 bool SGMetar::scanBoundary(char **s)
1153 {
1154         if (**s && !isspace(**s))
1155                 return false;
1156         while (isspace(**s))
1157                 (*s)++;
1158         return true;
1159 }
1160
1161
1162 int SGMetar::scanNumber(char **src, int *num, int min, int max)
1163 {
1164         int i;
1165         char *s = *src;
1166         *num = 0;
1167         for (i = 0; i < min; i++) {
1168                 if (!isdigit(*s))
1169                         return 0;
1170                 else
1171                         *num = *num * 10 + *s++ - '0';
1172         }
1173         for (; i < max && isdigit(*s); i++)
1174                 *num = *num * 10 + *s++ - '0';
1175         *src = s;
1176         return i;
1177 }
1178
1179
1180 // find longest match of str in list
1181 const struct Token *SGMetar::scanToken(char **str, const struct Token *list)
1182 {
1183         const struct Token *longest = 0;
1184         int maxlen = 0, len;
1185         char *s;
1186         for (int i = 0; (s = list[i].id); i++) {
1187                 len = strlen(s);
1188                 if (!strncmp(s, *str, len) && len > maxlen) {
1189                         maxlen = len;
1190                         longest = &list[i];
1191                 }
1192         }
1193         *str += maxlen;
1194         return longest;
1195 }
1196
1197
1198 void SGMetarCloud::set(double alt, int cov)
1199 {
1200         _altitude = alt;
1201         if (cov != -1)
1202                 _coverage = cov;
1203 }
1204
1205
1206 void SGMetarVisibility::set(double dist, int dir, int mod, int tend)
1207 {
1208         _distance = dist;
1209         if (dir != -1)
1210                 _direction = dir;
1211         if (mod != -1)
1212                 _modifier = mod;
1213         if (tend != 1)
1214                 _tendency = tend;
1215 }
1216
1217 #undef NaN