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