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