]> git.mxchange.org Git - simgear.git/blob - simgear/environment/metar.cxx
Vassilii Khachaturov:
[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 ", sg_location(_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 ", sg_location(_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 #if defined( _MSC_VER ) || defined ( __MINGW32__ )
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 ", sg_location(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 ", 
237                                 sg_location(_url));
238
239         char *metar = new char[strlen(b) + 2];  // make room for " \0"
240         strcpy(metar, b);
241         return metar;
242 }
243
244
245 /**
246   * Replace any number of subsequent spaces by just one space, and add
247   * a trailing space. This makes scanning for things like "ALL RWY" easier.
248   */
249 void SGMetar::normalizeData()
250 {
251         char *src, *dest;
252         for (src = dest = _data; (*dest++ = *src++); )
253                 while (*src == ' ' && src[1] == ' ')
254                         src++;
255         for (dest--; isspace(*--dest); ) ;
256         *++dest = ' ';
257         *++dest = '\0';
258 }
259
260
261 // \d\d\d\d/\d\d/\d\d
262 bool SGMetar::scanPreambleDate()
263 {
264         char *m = _m;
265         int year, month, day;
266         if (!scanNumber(&m, &year, 4))
267                 return false;
268         if (*m++ != '/')
269                 return false;
270         if (!scanNumber(&m, &month, 2))
271                 return false;
272         if (*m++ != '/')
273                 return false;
274         if (!scanNumber(&m, &day, 2))
275                 return false;
276         if (!scanBoundary(&m))
277                 return false;
278         _year = year;
279         _month = month;
280         _day = day;
281         _m = m;
282         return true;
283 }
284
285
286 // \d\d:\d\d
287 bool SGMetar::scanPreambleTime()
288 {
289         char *m = _m;
290         int hour, minute;
291         if (!scanNumber(&m, &hour, 2))
292                 return false;
293         if (*m++ != ':')
294                 return false;
295         if (!scanNumber(&m, &minute, 2))
296                 return false;
297         if (!scanBoundary(&m))
298                 return false;
299         _hour = hour;
300         _minute = minute;
301         _m = m;
302         return true;
303 }
304
305
306 // (METAR|SPECI)
307 bool SGMetar::scanType()
308 {
309         if (strncmp(_m, "METAR ", 6) && strncmp(_m, "SPECI ", 6))
310                 return false;
311         _m += 6;
312         _grpcount++;
313         return true;
314 }
315
316
317 // [A-Z]{4}
318 bool SGMetar::scanId()
319 {
320         char *m = _m;
321         for (int i = 0; i < 4; m++, i++)
322                 if (!(isalpha(*m) || isdigit(*m)))
323                         return false;
324         if (!scanBoundary(&m))
325                 return false;
326         strncpy(_icao, _m, 4);
327         _icao[4] = '\0';
328         _m = m;
329         _grpcount++;
330         return true;
331 }
332
333
334 // \d{6}Z
335 bool SGMetar::scanDate()
336 {
337         char *m = _m;
338         int day, hour, minute;
339         if (!scanNumber(&m, &day, 2))
340                 return false;
341         if (!scanNumber(&m, &hour, 2))
342                 return false;
343         if (!scanNumber(&m, &minute, 2))
344                 return false;
345         if (*m++ != 'Z')
346                 return false;
347         if (!scanBoundary(&m))
348                 return false;
349         _day = day;
350         _hour = hour;
351         _minute = minute;
352         _m = m;
353         _grpcount++;
354         return true;
355 }
356
357
358 // (NIL|AUTO|COR|RTD)
359 bool SGMetar::scanModifier()
360 {
361         char *m = _m;
362         int type;
363         if (!strncmp(m, "NIL", 3)) {
364                 _m += strlen(_m);
365                 return true;
366         }
367         if (!strncmp(m, "AUTO", 4))                     // automatically generated
368                 m += 4, type = AUTO;
369         else if (!strncmp(m, "COR", 3))                 // manually corrected
370                 m += 3, type = COR;
371         else if (!strncmp(m, "RTD", 3))                 // routine delayed
372                 m += 3, type = RTD;
373         else
374                 return false;
375         if (!scanBoundary(&m))
376                 return false;
377         _report_type = type;
378         _m = m;
379         _grpcount++;
380         return true;
381 }
382
383
384 // (\d{3}|VRB)\d{1,3}(G\d{2,3})?(KT|KMH|MPS)
385 bool SGMetar::scanWind()
386 {
387         char *m = _m;
388         int dir;
389         if (!strncmp(m, "VRB", 3))
390                 m += 3, dir = -1;
391         else if (!scanNumber(&m, &dir, 3))
392                 return false;
393
394         int i;
395         if (!scanNumber(&m, &i, 2, 3))
396                 return false;
397         double speed = i;
398
399         double gust = NaN;
400         if (*m == 'G') {
401                 m++;
402                 if (!scanNumber(&m, &i, 2, 3))
403                         return false;
404                 gust = i;
405         }
406         double factor;
407         if (!strncmp(m, "KT", 2))
408                 m += 2, factor = SG_KT_TO_MPS;
409         else if (!strncmp(m, "KMH", 3))
410                 m += 3, factor = SG_KMH_TO_MPS;
411         else if (!strncmp(m, "KPH", 3))         // ??
412                 m += 3, factor = SG_KMH_TO_MPS;
413         else if (!strncmp(m, "MPS", 3))
414                 m += 3, factor = 1.0;
415         else
416                 return false;
417         if (!scanBoundary(&m))
418                 return false;
419         _m = m;
420         _wind_dir = dir;
421         _wind_speed = speed * factor;
422         if (gust != NaN)
423                 _gust_speed = gust * factor;
424         _grpcount++;
425         return true;
426 }
427
428
429 // \d{3}V\d{3}
430 bool SGMetar::scanVariability()
431 {
432         char *m = _m;
433         int from, to;
434         if (!scanNumber(&m, &from, 3))
435                 return false;
436         if (*m++ != 'V')
437                 return false;
438         if (!scanNumber(&m, &to, 3))
439                 return false;
440         if (!scanBoundary(&m))
441                 return false;
442         _m = m;
443         _wind_range_from = from;
444         _wind_range_to = to;
445         _grpcount++;
446         return true;
447 }
448
449
450 bool SGMetar::scanVisibility()
451 // TODO: if only directed vis are given, do still set min/max
452 {
453         if (!strncmp(_m, "//// ", 5)) {         // spec compliant?
454                 _m += 5;
455                 _grpcount++;
456                 return true;
457         }
458
459         char *m = _m;
460         double distance;
461         int i, dir = -1;
462         int modifier = SGMetarVisibility::EQUALS;
463 // \d{4}(N|NE|E|SE|S|SW|W|NW)?
464         if (scanNumber(&m, &i, 4)) {
465                 if (*m == 'E')
466                         m++, dir = 90;
467                 else if (*m == 'W')
468                         m++, dir = 270;
469                 else if (*m == 'N') {
470                         m++;
471                         if (*m == 'E')
472                                 m++, dir = 45;
473                         else if (*m == 'W')
474                                 m++, dir = 315;
475                         else
476                                 dir = 0;
477                 } else if (*m == 'S') {
478                         m++;
479                         if (*m == 'E')
480                                 m++, dir = 135;
481                         else if (*m == 'W')
482                                 m++, dir = 225;
483                         else
484                                 dir = 180;
485                 }
486                 if (i == 0)
487                         i = 50, modifier = SGMetarVisibility::LESS_THAN;
488                 else if (i == 9999)
489                         i++, modifier = SGMetarVisibility::GREATER_THAN;
490                 distance = i;
491         } else {
492 // M?(\d{1,2}|\d{1,2}/\d{1,2}|\d{1,2} \d{1,2}/\d{1,2})(SM|KM)
493                 modifier = 0;
494                 if (*m == 'M')
495                         m++, modifier = SGMetarVisibility::LESS_THAN;
496
497                 if (!scanNumber(&m, &i, 1, 2))
498                         return false;
499                 distance = i;
500
501                 if (*m == '/') {
502                         m++;
503                         if (!scanNumber(&m, &i, 1, 2))
504                                 return false;
505                         distance /= i;
506                 } else if (*m == ' ') {
507                         m++;
508                         int denom;
509                         if (!scanNumber(&m, &i, 1, 2))
510                                 return false;
511                         if (*m++ != '/')
512                                 return false;
513                         if (!scanNumber(&m, &denom, 1, 2))
514                                 return false;
515                         distance += (double)i / denom;
516                 }
517
518                 if (!strncmp(m, "SM", 2))
519                         distance *= SG_SM_TO_METER, m += 2;
520                 else if (!strncmp(m, "KM", 2))
521                         distance *= 1000, m += 2;
522                 else
523                         return false;
524         }
525         if (!scanBoundary(&m))
526                 return false;
527
528         SGMetarVisibility *v;
529         if (dir != -1)
530                 v = &_dir_visibility[dir / 45];
531         else if (_min_visibility._distance == NaN)
532                 v = &_min_visibility;
533         else
534                 v = &_max_visibility;
535
536         v->_distance = distance;
537         v->_modifier = modifier;
538         v->_direction = dir;
539         _m = m;
540         _grpcount++;
541         return true;
542 }
543
544
545 // R\d\d[LCR]?/([PM]?\d{4}V)?[PM]?\d{4}(FT)?[DNU]?
546 bool SGMetar::scanRwyVisRange()
547 {
548         char *m = _m;
549         int i;
550         SGMetarRunway r;
551         if (*m++ != 'R')
552                 return false;
553         if (!scanNumber(&m, &i, 2))
554                 return false;
555         if (*m == 'L' || *m == 'C' || *m == 'R')
556                 m++;
557
558         char id[4];
559         strncpy(id, _m + 1, i = m - _m - 1);
560         id[i] = '\0';
561
562         if (*m++ != '/')
563                 return false;
564
565         int from, to;
566         if (*m == 'P')
567                 m++, r._min_visibility._modifier = SGMetarVisibility::GREATER_THAN;
568         else if (*m == 'M')
569                 m++, r._min_visibility._modifier = SGMetarVisibility::LESS_THAN;
570         if (!scanNumber(&m, &from, 4))
571                 return false;
572         if (*m == 'V') {
573                 m++;
574                 if (*m == 'P')
575                         m++, r._max_visibility._modifier = SGMetarVisibility::GREATER_THAN;
576                 else if (*m == 'M')
577                         m++, r._max_visibility._modifier = SGMetarVisibility::LESS_THAN;
578                 if (!scanNumber(&m, &to, 4))
579                         return false;
580         } else
581                 to = from;
582
583         if (!strncmp(m, "FT", 2)) {
584                 from = int(from * SG_FEET_TO_METER);
585                 to = int(to * SG_FEET_TO_METER);
586                 m += 2;
587         }
588         r._min_visibility._distance = from;
589         r._max_visibility._distance = to;
590
591         if (*m == '/')                                  // this is not in the spec!
592                 *m++;
593         if (*m == 'D')
594                 m++, r._min_visibility._tendency = SGMetarVisibility::DECREASING;
595         else if (*m == 'N')
596                 m++, r._min_visibility._tendency = SGMetarVisibility::STABLE;
597         else if (*m == 'U')
598                 m++, r._min_visibility._tendency = SGMetarVisibility::INCREASING;
599
600         if (!scanBoundary(&m))
601                 return false;
602         _m = m;
603
604         _runways[id]._min_visibility = r._min_visibility;
605         _runways[id]._max_visibility = r._max_visibility;
606         _grpcount++;
607         return true;
608 }
609
610
611 static const struct Token special[] = {
612         "NSW",  "no significant weather",
613         "VCSH", "showers in the vicinity",
614         "VCTS", "thunderstorm in the vicinity",
615         0, 0
616 };
617
618
619 static const struct Token description[] = {
620         "SH",   "showers of",
621         "TS",   "thunderstorm with",
622         "BC",   "patches of",
623         "BL",   "blowing",
624         "DR",   "low drifting",
625         "FZ",   "freezing",
626         "MI",   "shallow",
627         "PR",   "partial",
628         0, 0
629 };
630
631
632 static const struct Token phenomenon[] = {
633         "DZ",   "drizzle",
634         "GR",   "hail",
635         "GS",   "small hail and/or snow pellets",
636         "IC",   "ice crystals",
637         "PE",   "ice pellets",
638         "RA",   "rain",
639         "SG",   "snow grains",
640         "SN",   "snow",
641         "UP",   "unknown precipitation",
642         "BR",   "mist",
643         "DU",   "widespread dust",
644         "SG",   "fog",
645         "SGBR", "fog bank",
646         "FU",   "smoke",
647         "HZ",   "haze",
648         "PY",   "spray",
649         "SA",   "sand",
650         "VA",   "volcanic ash",
651         "DS",   "duststorm",
652         "FC",   "funnel cloud/tornado waterspout",
653         "PO",   "well-developed dust/sand whirls",
654         "SQ",   "squalls",
655         "SS",   "sandstorm",
656         "UP",   "unknown",      // ... due to failed automatic acquisition
657         0, 0
658 };
659
660
661 // (+|-|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}
662 bool SGMetar::scanWeather()
663 {
664         char *m = _m;
665         string weather;
666         const struct Token *a;
667         if ((a = scanToken(&m, special))) {
668                 if (!scanBoundary(&m))
669                         return false;
670                 _weather.push_back(a->text);
671                 _m = m;
672                 return true;
673         }
674
675         string pre, post;
676         int intensity = 0;
677         if (*m == '-')
678                 m++, pre = "light ", intensity = 1;
679         else if (*m == '+')
680                 m++, pre = "heavy ", intensity = 3;
681         else if (!strncmp(m, "VC", 2))
682                 m += 2, post = "in the vicinity ";
683         else
684                 pre = "moderate ", intensity = 2;
685
686         int i;
687         for (i = 0; i < 3; i++) {
688                 if (!(a = scanToken(&m, description)))
689                         break;
690                 weather += string(a->text) + " ";
691         }
692         for (i = 0; i < 3; i++) {
693                 if (!(a = scanToken(&m, phenomenon)))
694                         break;
695                 weather += string(a->text) + " ";
696                 if (!strcmp(a->id, "RA"))
697                         _rain = intensity;
698                 else if (!strcmp(a->id, "HA"))
699                         _hail = intensity;
700                 else if (!strcmp(a->id, "SN"))
701                         _snow = intensity;
702         }
703         if (!weather.length())
704                 return false;
705         if (!scanBoundary(&m))
706                 return false;
707         _m = m;
708         weather = pre + weather + post;
709         weather.erase(weather.length() - 1);
710         _weather.push_back(weather);
711         _grpcount++;
712         return true;
713 }
714
715
716 static const struct Token cloud_types[] = {
717         "AC",    "altocumulus",
718         "ACC",   "altocumulus castellanus",
719         "ACSL",  "altocumulus standing lenticular",
720         "AS",    "altostratus",
721         "CB",    "cumulonimbus",
722         "CBMAM", "cumulonimbus mammatus",
723         "CC",    "cirrocumulus",
724         "CCSL",  "cirrocumulus standing lenticular",
725         "CI",    "cirrus",
726         "CS",    "cirrostratus",
727         "CU",    "cumulus",
728         "CUFRA", "cumulus fractus",
729         "NS",    "nimbostratus",
730         "SAC",   "stratoaltocumulus",           // guessed
731         "SC",    "stratocumulus",
732         "SCSL",  "stratocumulus standing lenticular",
733         "ST",    "stratus",
734         "STFRA", "stratus fractus",
735         "TCU",   "towering cumulus",
736         0, 0
737 };
738
739
740 // (FEW|SCT|BKN|OVC|SKC|CLR|CAVOK|VV)([0-9]{3}|///)?[:cloud_type:]?
741 bool SGMetar::scanSkyCondition()
742 {
743         char *m = _m;
744         int i;
745         SGMetarCloud cl;
746
747         if (!strncmp(m, "CLR", i = 3)                           // clear
748                         || !strncmp(m, "SKC", i = 3)            // sky clear
749                         || !strncmp(m, "NSC", i = 3)            // no significant clouds
750                         || !strncmp(m, "CAVOK", i = 5)) {       // ceiling and visibility OK (implies 9999)
751                 m += i;
752                 if (!scanBoundary(&m))
753                         return false;
754
755                 if (i == 3) {
756                         cl._coverage = 0;
757                         _clouds.push_back(cl);
758                 } else {
759                         _cavok = true;
760                 }
761                 _m = m;
762                 return true;
763         }
764
765         if (!strncmp(m, "VV", i = 2))                           // vertical visibility
766                 ;
767         else if (!strncmp(m, "FEW", i = 3))
768                 cl._coverage = 1;
769         else if (!strncmp(m, "SCT", i = 3))
770                 cl._coverage = 2;
771         else if (!strncmp(m, "BKN", i = 3))
772                 cl._coverage = 3;
773         else if (!strncmp(m, "OVC", i = 3))
774                 cl._coverage = 4;
775         else
776                 return false;
777         m += i;
778
779         if (!strncmp(m, "///", 3))      // vis not measurable (e.g. because of heavy snowing)
780                 m += 3, i = -1;
781         else if (scanBoundary(&m)) {
782                 _m = m;
783                 return true;                            // ignore single OVC/BKN/...
784         } else if (!scanNumber(&m, &i, 3))
785                 i = -1;
786
787         if (cl._coverage == -1) {
788                 if (!scanBoundary(&m))
789                         return false;
790                 if (i == -1)                    // 'VV///'
791                         _vert_visibility._modifier = SGMetarVisibility::NOGO;
792                 else
793                         _vert_visibility._distance = i * 100 * SG_FEET_TO_METER;
794                 _m = m;
795                 return true;
796         }
797
798         if (i != -1)
799                 cl._altitude = i * 100 * SG_FEET_TO_METER;
800
801         const struct Token *a;
802         if ((a = scanToken(&m, cloud_types))) {
803                 cl._type = a->id;
804                 cl._type_long = a->text;
805         }
806         if (!scanBoundary(&m))
807                 return false;
808         _clouds.push_back(cl);
809         _m = m;
810         _grpcount++;
811         return true;
812 }
813
814
815 // M?[0-9]{2}/(M?[0-9]{2})?            (spec)
816 // (M?[0-9]{2}|XX)/(M?[0-9]{2}|XX)?    (Namibia)
817 bool SGMetar::scanTemperature()
818 {
819         char *m = _m;
820         int sign = 1, temp, dew;
821         if (!strncmp(m, "XX/XX", 5)) {          // not spec compliant!
822                 _m += 5;
823                 return scanBoundary(&_m);
824         }
825
826         if (*m == 'M')
827                 m++, sign = -1;
828         if (!scanNumber(&m, &temp, 2))
829                 return false;
830         temp *= sign;
831
832         if (*m++ != '/')
833                 return false;
834         if (!scanBoundary(&m)) {
835                 if (!strncmp(m, "XX", 2))       // not spec compliant!
836                         m += 2, sign = 0;
837                 else {
838                         sign = 1;
839                         if (*m == 'M')
840                                 m++, sign = -1;
841                         if (!scanNumber(&m, &dew, 2))
842                                 return false;
843                 }
844                 if (!scanBoundary(&m))
845                         return false;
846                 if (sign)
847                         _dewp = sign * dew;
848         }
849         _temp = temp;
850         _m = m;
851         _grpcount++;
852         return true;
853 }
854
855
856 double SGMetar::getRelHumidity() const
857 {
858         if (_temp == NaN || _dewp == NaN)
859                 return NaN;
860         double dewp = pow(10.0, 7.5 * _dewp / (237.7 + _dewp));
861         double temp = pow(10.0, 7.5 * _temp / (237.7 + _temp));
862         return dewp * 100 / temp;
863 }
864
865
866 // [AQ]\d{4}             (spec)
867 // [AQ]\d{2}(\d{2}|//)   (Namibia)
868 bool SGMetar::scanPressure()
869 {
870         char *m = _m;
871         double factor;
872         int press, i;
873
874         if (*m == 'A')
875                 factor = SG_INHG_TO_PA / 100;
876         else if (*m == 'Q')
877                 factor = 100;
878         else
879                 return false;
880         m++;
881         if (!scanNumber(&m, &press, 2))
882                 return false;
883         press *= 100;
884         if (!strncmp(m, "//", 2))       // not spec compliant!
885                 m += 2;
886         else if (scanNumber(&m, &i, 2))
887                 press += i;
888         else
889                 return false;
890         if (!scanBoundary(&m))
891                 return false;
892         _pressure = press * factor;
893         _m = m;
894         _grpcount++;
895         return true;
896 }
897
898
899 static const char *runway_deposit[] = {
900         "clear and dry",
901         "damp",
902         "wet or puddles",
903         "frost",
904         "dry snow",
905         "wet snow",
906         "slush",
907         "ice",
908         "compacted snow",
909         "frozen ridges"
910 };
911
912
913 static const char *runway_deposit_extent[] = {
914         0, "1-10%", "11-25%", 0, 0, "26-50%", 0, 0, 0, "51-100%"
915 };
916
917
918 static const char *runway_friction[] = {
919         0,
920         "poor braking action",
921         "poor/medium braking action",
922         "medium braking action",
923         "medium/good braking action",
924         "good braking action",
925         0, 0, 0,
926         "friction: unreliable measurement"
927 };
928
929
930 // \d\d(CLRD|[\d/]{4})(\d\d|//)
931 bool SGMetar::scanRunwayReport()
932 {
933         char *m = _m;
934         int i;
935         char id[4];
936         SGMetarRunway r;
937
938         if (!scanNumber(&m, &i, 2))
939                 return false;
940         if (i == 88)
941                 strcpy(id, "ALL");
942         else if (i == 99)
943                 strcpy(id, "REP");              // repetition of previous report
944         else if (i >= 50) {
945                 i -= 50;
946                 id[0] = i / 10 + '0', id[1] = i % 10 + '0', id[2] = 'R', id[3] = '\0';
947         } else
948                 id[0] = i / 10 + '0', id[1] = i % 10 + '0', id[2] = '\0';
949
950         if (!strncmp(m, "CLRD", 4)) {
951                 m += 4;                                                 // runway cleared
952                 r._deposit_string = "cleared";
953         } else {
954                 if (scanNumber(&m, &i, 1)) {
955                         r._deposit = i;
956                         r._deposit_string = runway_deposit[i];
957                 } else if (*m == '/')
958                         m++;
959                 else
960                         return false;
961                 if (*m == '1' || *m == '2' || *m == '5' || *m == '9') { // extent of deposit
962                         r._extent = *m - '0';
963                         r._extent_string = runway_deposit_extent[*m - '0'];
964                 } else if (*m != '/')
965                         return false;
966                 m++;
967                 i = -1;
968                 if (!strncmp(m, "//", 2))
969                         m += 2;
970                 else if (!scanNumber(&m, &i, 2))
971                         return false;
972
973                 if (i == 0)
974                         r._depth = 0.5;                                 // < 1 mm deep (let's say 0.5 :-)
975                 else if (i > 0 && i <= 90)
976                         r._depth = i / 1000.0;                          // i mm deep
977                 else if (i >= 92 && i <= 98)
978                         r._depth = (i - 90) / 20.0;
979                 else if (i == 99)
980                         r._comment = "runway not in use";
981                 else if (i == -1)                                       // no depth given ("//")
982                         ;
983                 else
984                         return false;
985         }
986         i = -1;
987         if (m[0] == '/' && m[1] == '/')
988                 m += 2;
989         else if (!scanNumber(&m, &i, 2))
990                 return false;
991         if (i >= 1 && i < 90) {
992                 r._friction = i / 100.0;
993         } else if ((i >= 91 && i <= 95) || i == 99) {
994                 r._friction_string = runway_friction[i - 90];
995         }
996         if (!scanBoundary(&m))
997                 return false;
998
999         _runways[id]._deposit = r._deposit;
1000         _runways[id]._extent = r._extent;
1001         _runways[id]._extent_string = r._extent_string;
1002         _runways[id]._depth = r._depth;
1003         _runways[id]._friction = r._friction;
1004         _runways[id]._friction_string = r._friction_string;
1005         _runways[id]._comment = r._comment;
1006         _m = m;
1007         _grpcount++;
1008         return true;
1009 }
1010
1011
1012 // WS (ALL RWYS?|RWY ?\d\d[LCR]?)?
1013 bool SGMetar::scanWindShear()
1014 {
1015         char *m = _m;
1016         if (strncmp(m, "WS", 2))
1017                 return false;
1018         m += 2;
1019         if (!scanBoundary(&m))
1020                 return false;
1021
1022         if (!strncmp(m, "ALL", 3)) {
1023                 m += 3;
1024                 if (!scanBoundary(&m))
1025                         return false;
1026                 if (strncmp(m, "RWY", 3))
1027                         return false;
1028                 m += 3;
1029                 if (*m == 'S')
1030                         m++;
1031                 if (!scanBoundary(&m))
1032                         return false;
1033                 _runways["ALL"]._wind_shear = true;
1034                 _m = m;
1035                 return true;
1036         }
1037
1038         char id[4], *mm;
1039         int i, cnt;
1040         for (cnt = 0;; cnt++) {                 // ??
1041                 if (strncmp(m, "RWY", 3))
1042                         break;
1043                 m += 3;
1044                 scanBoundary(&m);
1045                 mm = m;
1046                 if (!scanNumber(&m, &i, 2))
1047                         return false;
1048                 if (*m == 'L' || *m == 'C' || *m == 'R')
1049                         m++;
1050                 strncpy(id, mm, i = m - mm);
1051                 id[i] = '\0';
1052                 if (!scanBoundary(&m))
1053                         return false;
1054                 _runways[id]._wind_shear = true;
1055         }
1056         if (!cnt)
1057                 _runways["ALL"]._wind_shear = true;
1058         _m = m;
1059         return true;
1060 }
1061
1062
1063 bool SGMetar::scanTrendForecast()
1064 {
1065         char *m = _m;
1066         if (strncmp(m, "NOSIG", 5))
1067                 return false;
1068
1069         m += 5;
1070         if (!scanBoundary(&m))
1071                 return false;
1072         _m = m;
1073         return true;
1074 }
1075
1076
1077 // (BLU|WHT|GRN|YLO|AMB|RED)
1078 static const struct Token colors[] = {
1079         "BLU", "Blue",          // 2500 ft,  8.0 km
1080         "WHT", "White",         // 1500 ft,  5.0 km
1081         "GRN", "Green",         //  700 ft,  3.7 km
1082         "YLO", "Yellow",        //  300 ft,  1.6 km
1083         "AMB", "Amber",         //  200 ft,  0.8 km
1084         "RED", "Red",           // <200 ft, <0.8 km
1085         0, 0
1086 };
1087
1088
1089 bool SGMetar::scanColorState()
1090 {
1091         char *m = _m;
1092         const struct Token *a;
1093         if (!(a = scanToken(&m, colors)))
1094                 return false;
1095         if (!scanBoundary(&m))
1096                 return false;
1097         //printf(Y"Code %s\n"N, a->text);
1098         _m = m;
1099         return true;
1100 }
1101
1102
1103 bool SGMetar::scanRemark()
1104 {
1105         if (strncmp(_m, "RMK", 3))
1106                 return false;
1107         _m += 3;
1108         if (!scanBoundary(&_m))
1109                 return false;
1110
1111         while (*_m) {
1112                 if (!scanRunwayReport()) {
1113                         while (*_m && !isspace(*_m))
1114                                 _m++;
1115                         scanBoundary(&_m);
1116                 }
1117         }
1118         return true;
1119 }
1120
1121
1122 bool SGMetar::scanRemainder()
1123 {
1124         char *m = _m;
1125         if (!(strncmp(m, "NOSIG", 5))) {
1126                 m += 5;
1127                 if (scanBoundary(&m))
1128                         _m = m; //_comment.push_back("No significant tendency");
1129         }
1130
1131         if (!scanBoundary(&m))
1132                 return false;
1133         _m = m;
1134         return true;
1135 }
1136
1137
1138 bool SGMetar::scanBoundary(char **s)
1139 {
1140         if (**s && !isspace(**s))
1141                 return false;
1142         while (isspace(**s))
1143                 (*s)++;
1144         return true;
1145 }
1146
1147
1148 int SGMetar::scanNumber(char **src, int *num, int min, int max)
1149 {
1150         int i;
1151         char *s = *src;
1152         *num = 0;
1153         for (i = 0; i < min; i++) {
1154                 if (!isdigit(*s))
1155                         return 0;
1156                 else
1157                         *num = *num * 10 + *s++ - '0';
1158         }
1159         for (; i < max && isdigit(*s); i++)
1160                 *num = *num * 10 + *s++ - '0';
1161         *src = s;
1162         return i;
1163 }
1164
1165
1166 // find longest match of str in list
1167 const struct Token *SGMetar::scanToken(char **str, const struct Token *list)
1168 {
1169         const struct Token *longest = 0;
1170         int maxlen = 0, len;
1171         char *s;
1172         for (int i = 0; (s = list[i].id); i++) {
1173                 len = strlen(s);
1174                 if (!strncmp(s, *str, len) && len > maxlen) {
1175                         maxlen = len;
1176                         longest = &list[i];
1177                 }
1178         }
1179         *str += maxlen;
1180         return longest;
1181 }
1182
1183
1184 void SGMetarCloud::set(double alt, int cov)
1185 {
1186         _altitude = alt;
1187         if (cov != -1)
1188                 _coverage = cov;
1189 }
1190
1191
1192 void SGMetarVisibility::set(double dist, int dir, int mod, int tend)
1193 {
1194         _distance = dist;
1195         if (dir != -1)
1196                 _direction = dir;
1197         if (mod != -1)
1198                 _modifier = mod;
1199         if (tend != 1)
1200                 _tendency = tend;
1201 }
1202
1203 #undef NaN