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