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