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