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