]> git.mxchange.org Git - simgear.git/blob - simgear/environment/metar.cxx
d0dd28c16ecd34ab123880543c539d3d4ce8039f
[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
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: ", 13))
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         { "FG",   "fog" },
645         { "FGBR", "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|FG|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
962                 if (*m == '1' || *m == '2' || *m == '5' || *m == '9') { // extent of deposit
963                         r._extent = *m - '0';
964                         r._extent_string = runway_deposit_extent[*m - '0'];
965                 } else if (*m != '/')
966                         return false;
967
968                 m++;
969                 i = -1;
970                 if (!strncmp(m, "//", 2))
971                         m += 2;
972                 else if (!scanNumber(&m, &i, 2))
973                         return false;
974
975                 if (i == 0)
976                         r._depth = 0.0005;                              // < 1 mm deep (let's say 0.5 :-)
977                 else if (i > 0 && i <= 90)
978                         r._depth = i / 1000.0;                          // i mm deep
979                 else if (i >= 92 && i <= 98)
980                         r._depth = (i - 90) / 20.0;
981                 else if (i == 99)
982                         r._comment = "runway not in use";
983                 else if (i == -1)                                       // no depth given ("//")
984                         ;
985                 else
986                         return false;
987         }
988         i = -1;
989         if (m[0] == '/' && m[1] == '/')
990                 m += 2;
991         else if (!scanNumber(&m, &i, 2))
992                 return false;
993         if (i >= 1 && i < 90) {
994                 r._friction = i / 100.0;
995         } else if ((i >= 91 && i <= 95) || i == 99) {
996                 r._friction_string = runway_friction[i - 90];
997         }
998         if (!scanBoundary(&m))
999                 return false;
1000
1001         _runways[id]._deposit = r._deposit;
1002         _runways[id]._deposit_string = r._deposit_string;
1003         _runways[id]._extent = r._extent;
1004         _runways[id]._extent_string = r._extent_string;
1005         _runways[id]._depth = r._depth;
1006         _runways[id]._friction = r._friction;
1007         _runways[id]._friction_string = r._friction_string;
1008         _runways[id]._comment = r._comment;
1009         _m = m;
1010         _grpcount++;
1011         return true;
1012 }
1013
1014
1015 // WS (ALL RWYS?|RWY ?\d\d[LCR]?)?
1016 bool SGMetar::scanWindShear()
1017 {
1018         char *m = _m;
1019         if (strncmp(m, "WS", 2))
1020                 return false;
1021         m += 2;
1022         if (!scanBoundary(&m))
1023                 return false;
1024
1025         if (!strncmp(m, "ALL", 3)) {
1026                 m += 3;
1027                 if (!scanBoundary(&m))
1028                         return false;
1029                 if (strncmp(m, "RWY", 3))
1030                         return false;
1031                 m += 3;
1032                 if (*m == 'S')
1033                         m++;
1034                 if (!scanBoundary(&m))
1035                         return false;
1036                 _runways["ALL"]._wind_shear = true;
1037                 _m = m;
1038                 return true;
1039         }
1040
1041         char id[4], *mm;
1042         int i, cnt;
1043         for (cnt = 0;; cnt++) {                 // ??
1044                 if (strncmp(m, "RWY", 3))
1045                         break;
1046                 m += 3;
1047                 scanBoundary(&m);
1048                 mm = m;
1049                 if (!scanNumber(&m, &i, 2))
1050                         return false;
1051                 if (*m == 'L' || *m == 'C' || *m == 'R')
1052                         m++;
1053                 strncpy(id, mm, i = m - mm);
1054                 id[i] = '\0';
1055                 if (!scanBoundary(&m))
1056                         return false;
1057                 _runways[id]._wind_shear = true;
1058         }
1059         if (!cnt)
1060                 _runways["ALL"]._wind_shear = true;
1061         _m = m;
1062         return true;
1063 }
1064
1065
1066 bool SGMetar::scanTrendForecast()
1067 {
1068         char *m = _m;
1069         if (strncmp(m, "NOSIG", 5))
1070                 return false;
1071
1072         m += 5;
1073         if (!scanBoundary(&m))
1074                 return false;
1075         _m = m;
1076         return true;
1077 }
1078
1079
1080 // (BLU|WHT|GRN|YLO|AMB|RED)
1081 static const struct Token colors[] = {
1082         { "BLU", "Blue" },      // 2500 ft,  8.0 km
1083         { "WHT", "White" },     // 1500 ft,  5.0 km
1084         { "GRN", "Green" },     //  700 ft,  3.7 km
1085         { "YLO", "Yellow" },    //  300 ft,  1.6 km
1086         { "AMB", "Amber" },     //  200 ft,  0.8 km
1087         { "RED", "Red" },       // <200 ft, <0.8 km
1088         { 0, 0 }
1089 };
1090
1091
1092 bool SGMetar::scanColorState()
1093 {
1094         char *m = _m;
1095         const struct Token *a;
1096         if (!(a = scanToken(&m, colors)))
1097                 return false;
1098         if (!scanBoundary(&m))
1099                 return false;
1100         //printf(Y"Code %s\n"N, a->text);
1101         _m = m;
1102         return true;
1103 }
1104
1105
1106 bool SGMetar::scanRemark()
1107 {
1108         if (strncmp(_m, "RMK", 3))
1109                 return false;
1110         _m += 3;
1111         if (!scanBoundary(&_m))
1112                 return false;
1113
1114         while (*_m) {
1115                 if (!scanRunwayReport()) {
1116                         while (*_m && !isspace(*_m))
1117                                 _m++;
1118                         scanBoundary(&_m);
1119                 }
1120         }
1121         return true;
1122 }
1123
1124
1125 bool SGMetar::scanRemainder()
1126 {
1127         char *m = _m;
1128         if (!(strncmp(m, "NOSIG", 5))) {
1129                 m += 5;
1130                 if (scanBoundary(&m))
1131                         _m = m; //_comment.push_back("No significant tendency");
1132         }
1133
1134         if (!scanBoundary(&m))
1135                 return false;
1136         _m = m;
1137         return true;
1138 }
1139
1140
1141 bool SGMetar::scanBoundary(char **s)
1142 {
1143         if (**s && !isspace(**s))
1144                 return false;
1145         while (isspace(**s))
1146                 (*s)++;
1147         return true;
1148 }
1149
1150
1151 int SGMetar::scanNumber(char **src, int *num, int min, int max)
1152 {
1153         int i;
1154         char *s = *src;
1155         *num = 0;
1156         for (i = 0; i < min; i++) {
1157                 if (!isdigit(*s))
1158                         return 0;
1159                 else
1160                         *num = *num * 10 + *s++ - '0';
1161         }
1162         for (; i < max && isdigit(*s); i++)
1163                 *num = *num * 10 + *s++ - '0';
1164         *src = s;
1165         return i;
1166 }
1167
1168
1169 // find longest match of str in list
1170 const struct Token *SGMetar::scanToken(char **str, const struct Token *list)
1171 {
1172         const struct Token *longest = 0;
1173         int maxlen = 0, len;
1174         char *s;
1175         for (int i = 0; (s = list[i].id); i++) {
1176                 len = strlen(s);
1177                 if (!strncmp(s, *str, len) && len > maxlen) {
1178                         maxlen = len;
1179                         longest = &list[i];
1180                 }
1181         }
1182         *str += maxlen;
1183         return longest;
1184 }
1185
1186
1187 void SGMetarCloud::set(double alt, int cov)
1188 {
1189         _altitude = alt;
1190         if (cov != -1)
1191                 _coverage = cov;
1192 }
1193
1194
1195 void SGMetarVisibility::set(double dist, int dir, int mod, int tend)
1196 {
1197         _distance = dist;
1198         if (dir != -1)
1199                 _direction = dir;
1200         if (mod != -1)
1201                 _modifier = mod;
1202         if (tend != 1)
1203                 _tendency = tend;
1204 }
1205
1206 #undef NaN