00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00027 #ifdef HAVE_CONFIG_H
00028 # include <simgear_config.h>
00029 #endif
00030
00031 #include <string>
00032 #include <time.h>
00033
00034 #include <simgear/io/sg_socket.hxx>
00035 #include <simgear/debug/logstream.hxx>
00036 #include <simgear/structure/exception.hxx>
00037
00038 #include "metar.hxx"
00039
00040 #define NaN SGMetarNaN
00041
00066 SGMetar::SGMetar(const string& m, const string& proxy, const string& port,
00067 const string& auth, const time_t time) :
00068 _grpcount(0),
00069 _x_proxy(false),
00070 _year(-1),
00071 _month(-1),
00072 _day(-1),
00073 _hour(-1),
00074 _minute(-1),
00075 _report_type(-1),
00076 _wind_dir(-1),
00077 _wind_speed(NaN),
00078 _gust_speed(NaN),
00079 _wind_range_from(-1),
00080 _wind_range_to(-1),
00081 _temp(NaN),
00082 _dewp(NaN),
00083 _pressure(NaN),
00084 _rain(false),
00085 _hail(false),
00086 _snow(false),
00087 _cavok(false)
00088 {
00089 if (m.length() == 4 && isalnum(m[0]) && isalnum(m[1]) && isalnum(m[2]) && isalnum(m[3])) {
00090 for (int i = 0; i < 4; i++)
00091 _icao[i] = toupper(m[i]);
00092 _icao[4] = '\0';
00093 _data = loadData(_icao, proxy, port, auth, time);
00094 } else {
00095 _data = new char[m.length() + 2];
00096 strcpy(_data, m.c_str());
00097 _url = _data;
00098 }
00099 normalizeData();
00100
00101 _m = _data;
00102 _icao[0] = '\0';
00103
00104
00105 if (!scanPreambleDate())
00106 useCurrentDate();
00107 scanPreambleTime();
00108
00109
00110 scanType();
00111 if (!scanId() || !scanDate()) {
00112 delete[] _data;
00113 throw sg_io_exception("metar data bogus ", sg_location(_url));
00114 }
00115 scanModifier();
00116
00117
00118 scanWind();
00119 scanVariability();
00120 while (scanVisibility()) ;
00121 while (scanRwyVisRange()) ;
00122 while (scanWeather()) ;
00123 while (scanSkyCondition()) ;
00124 scanTemperature();
00125 scanPressure();
00126 while (scanSkyCondition()) ;
00127 while (scanRunwayReport()) ;
00128 scanWindShear();
00129
00130
00131 while (scanColorState()) ;
00132 scanTrendForecast();
00133 while (scanRunwayReport()) ;
00134 scanRemainder();
00135 scanRemark();
00136
00137 if (_grpcount < 4) {
00138 delete[] _data;
00139 throw sg_io_exception("metar data incomplete ", sg_location(_url));
00140 }
00141
00142 _url = "";
00143 }
00144
00145
00149 SGMetar::~SGMetar()
00150 {
00151 _clouds.clear();
00152 _runways.clear();
00153 _weather.clear();
00154 delete[] _data;
00155 }
00156
00157
00158 void SGMetar::useCurrentDate()
00159 {
00160 struct tm now;
00161 time_t now_sec = time(0);
00162 #ifdef _WIN32
00163 now = *gmtime(&now_sec);
00164 #else
00165 gmtime_r(&now_sec, &now);
00166 #endif
00167 _year = now.tm_year + 1900;
00168 _month = now.tm_mon + 1;
00169 }
00170
00171
00186 char *SGMetar::loadData(const char *id, const string& proxy, const string& port,
00187 const string& auth, time_t time)
00188 {
00189 const int buflen = 512;
00190 char buf[2 * buflen];
00191
00192 string host = proxy.empty() ? "weather.noaa.gov" : proxy;
00193 string path = "/pub/data/observations/metar/stations/";
00194
00195 path += string(id) + ".TXT";
00196 _url = "http://weather.noaa.gov" + path;
00197
00198 SGSocket *sock = new SGSocket(host, port.empty() ? "80" : port, "tcp");
00199 sock->set_timeout(10000);
00200 if (!sock->open(SG_IO_OUT)) {
00201 delete sock;
00202 throw sg_io_exception("cannot connect to ", sg_location(host));
00203 }
00204
00205 string get = "GET ";
00206 if (!proxy.empty())
00207 get += "http://weather.noaa.gov";
00208
00209 sprintf(buf, "%ld", time);
00210 get += path + " HTTP/1.0\015\012X-Time: " + buf + "\015\012";
00211
00212 if (!auth.empty())
00213 get += "Proxy-Authorization: " + auth + "\015\012";
00214
00215 get += "\015\012";
00216 sock->writestring(get.c_str());
00217
00218 int i;
00219
00220
00221 while ((i = sock->readline(buf, buflen))) {
00222 if (i <= 2 && isspace(buf[0]) && (!buf[1] || isspace(buf[1])))
00223 break;
00224 if (!strncmp(buf, "X-MetarProxy: ", 13))
00225 _x_proxy = true;
00226 }
00227 if (i) {
00228 i = sock->readline(buf, buflen);
00229 if (i)
00230 sock->readline(&buf[i], buflen);
00231 }
00232
00233 sock->close();
00234 delete sock;
00235
00236 char *b = buf;
00237 scanBoundary(&b);
00238 if (*b == '<')
00239 throw sg_io_exception("no metar data available from ",
00240 sg_location(_url));
00241
00242 char *metar = new char[strlen(b) + 2];
00243 strcpy(metar, b);
00244 return metar;
00245 }
00246
00247
00252 void SGMetar::normalizeData()
00253 {
00254 char *src, *dest;
00255 for (src = dest = _data; (*dest++ = *src++); )
00256 while (*src == ' ' && src[1] == ' ')
00257 src++;
00258 for (dest--; isspace(*--dest); ) ;
00259 *++dest = ' ';
00260 *++dest = '\0';
00261 }
00262
00263
00264
00265 bool SGMetar::scanPreambleDate()
00266 {
00267 char *m = _m;
00268 int year, month, day;
00269 if (!scanNumber(&m, &year, 4))
00270 return false;
00271 if (*m++ != '/')
00272 return false;
00273 if (!scanNumber(&m, &month, 2))
00274 return false;
00275 if (*m++ != '/')
00276 return false;
00277 if (!scanNumber(&m, &day, 2))
00278 return false;
00279 if (!scanBoundary(&m))
00280 return false;
00281 _year = year;
00282 _month = month;
00283 _day = day;
00284 _m = m;
00285 return true;
00286 }
00287
00288
00289
00290 bool SGMetar::scanPreambleTime()
00291 {
00292 char *m = _m;
00293 int hour, minute;
00294 if (!scanNumber(&m, &hour, 2))
00295 return false;
00296 if (*m++ != ':')
00297 return false;
00298 if (!scanNumber(&m, &minute, 2))
00299 return false;
00300 if (!scanBoundary(&m))
00301 return false;
00302 _hour = hour;
00303 _minute = minute;
00304 _m = m;
00305 return true;
00306 }
00307
00308
00309
00310 bool SGMetar::scanType()
00311 {
00312 if (strncmp(_m, "METAR ", 6) && strncmp(_m, "SPECI ", 6))
00313 return false;
00314 _m += 6;
00315 _grpcount++;
00316 return true;
00317 }
00318
00319
00320
00321 bool SGMetar::scanId()
00322 {
00323 char *m = _m;
00324 for (int i = 0; i < 4; m++, i++)
00325 if (!(isalpha(*m) || isdigit(*m)))
00326 return false;
00327 if (!scanBoundary(&m))
00328 return false;
00329 strncpy(_icao, _m, 4);
00330 _icao[4] = '\0';
00331 _m = m;
00332 _grpcount++;
00333 return true;
00334 }
00335
00336
00337
00338 bool SGMetar::scanDate()
00339 {
00340 char *m = _m;
00341 int day, hour, minute;
00342 if (!scanNumber(&m, &day, 2))
00343 return false;
00344 if (!scanNumber(&m, &hour, 2))
00345 return false;
00346 if (!scanNumber(&m, &minute, 2))
00347 return false;
00348 if (*m++ != 'Z')
00349 return false;
00350 if (!scanBoundary(&m))
00351 return false;
00352 _day = day;
00353 _hour = hour;
00354 _minute = minute;
00355 _m = m;
00356 _grpcount++;
00357 return true;
00358 }
00359
00360
00361
00362 bool SGMetar::scanModifier()
00363 {
00364 char *m = _m;
00365 int type;
00366 if (!strncmp(m, "NIL", 3)) {
00367 _m += strlen(_m);
00368 return true;
00369 }
00370 if (!strncmp(m, "AUTO", 4))
00371 m += 4, type = AUTO;
00372 else if (!strncmp(m, "COR", 3))
00373 m += 3, type = COR;
00374 else if (!strncmp(m, "RTD", 3))
00375 m += 3, type = RTD;
00376 else
00377 return false;
00378 if (!scanBoundary(&m))
00379 return false;
00380 _report_type = type;
00381 _m = m;
00382 _grpcount++;
00383 return true;
00384 }
00385
00386
00387
00388 bool SGMetar::scanWind()
00389 {
00390 char *m = _m;
00391 int dir;
00392 if (!strncmp(m, "VRB", 3))
00393 m += 3, dir = -1;
00394 else if (!scanNumber(&m, &dir, 3))
00395 return false;
00396
00397 int i;
00398 if (!scanNumber(&m, &i, 2, 3))
00399 return false;
00400 double speed = i;
00401
00402 double gust = NaN;
00403 if (*m == 'G') {
00404 m++;
00405 if (!scanNumber(&m, &i, 2, 3))
00406 return false;
00407 gust = i;
00408 }
00409 double factor;
00410 if (!strncmp(m, "KT", 2))
00411 m += 2, factor = SG_KT_TO_MPS;
00412 else if (!strncmp(m, "KMH", 3))
00413 m += 3, factor = SG_KMH_TO_MPS;
00414 else if (!strncmp(m, "KPH", 3))
00415 m += 3, factor = SG_KMH_TO_MPS;
00416 else if (!strncmp(m, "MPS", 3))
00417 m += 3, factor = 1.0;
00418 else
00419 return false;
00420 if (!scanBoundary(&m))
00421 return false;
00422 _m = m;
00423 _wind_dir = dir;
00424 _wind_speed = speed * factor;
00425 if (gust != NaN)
00426 _gust_speed = gust * factor;
00427 _grpcount++;
00428 return true;
00429 }
00430
00431
00432
00433 bool SGMetar::scanVariability()
00434 {
00435 char *m = _m;
00436 int from, to;
00437 if (!scanNumber(&m, &from, 3))
00438 return false;
00439 if (*m++ != 'V')
00440 return false;
00441 if (!scanNumber(&m, &to, 3))
00442 return false;
00443 if (!scanBoundary(&m))
00444 return false;
00445 _m = m;
00446 _wind_range_from = from;
00447 _wind_range_to = to;
00448 _grpcount++;
00449 return true;
00450 }
00451
00452
00453 bool SGMetar::scanVisibility()
00454
00455 {
00456 if (!strncmp(_m, "//// ", 5)) {
00457 _m += 5;
00458 _grpcount++;
00459 return true;
00460 }
00461
00462 char *m = _m;
00463 double distance;
00464 int i, dir = -1;
00465 int modifier = SGMetarVisibility::EQUALS;
00466
00467 if (scanNumber(&m, &i, 4)) {
00468 if (*m == 'E')
00469 m++, dir = 90;
00470 else if (*m == 'W')
00471 m++, dir = 270;
00472 else if (*m == 'N') {
00473 m++;
00474 if (*m == 'E')
00475 m++, dir = 45;
00476 else if (*m == 'W')
00477 m++, dir = 315;
00478 else
00479 dir = 0;
00480 } else if (*m == 'S') {
00481 m++;
00482 if (*m == 'E')
00483 m++, dir = 135;
00484 else if (*m == 'W')
00485 m++, dir = 225;
00486 else
00487 dir = 180;
00488 }
00489 if (i == 0)
00490 i = 50, modifier = SGMetarVisibility::LESS_THAN;
00491 else if (i == 9999)
00492 i++, modifier = SGMetarVisibility::GREATER_THAN;
00493 distance = i;
00494 } else {
00495
00496 if (*m == 'M')
00497 m++, modifier = SGMetarVisibility::LESS_THAN;
00498
00499 if (!scanNumber(&m, &i, 1, 2))
00500 return false;
00501 distance = i;
00502
00503 if (*m == '/') {
00504 m++;
00505 if (!scanNumber(&m, &i, 1, 2))
00506 return false;
00507 distance /= i;
00508 } else if (*m == ' ') {
00509 m++;
00510 int denom;
00511 if (!scanNumber(&m, &i, 1, 2))
00512 return false;
00513 if (*m++ != '/')
00514 return false;
00515 if (!scanNumber(&m, &denom, 1, 2))
00516 return false;
00517 distance += (double)i / denom;
00518 }
00519
00520 if (!strncmp(m, "SM", 2))
00521 distance *= SG_SM_TO_METER, m += 2;
00522 else if (!strncmp(m, "KM", 2))
00523 distance *= 1000, m += 2;
00524 else
00525 return false;
00526 }
00527 if (!scanBoundary(&m))
00528 return false;
00529
00530 SGMetarVisibility *v;
00531 if (dir != -1)
00532 v = &_dir_visibility[dir / 45];
00533 else if (_min_visibility._distance == NaN)
00534 v = &_min_visibility;
00535 else
00536 v = &_max_visibility;
00537
00538 v->_distance = distance;
00539 v->_modifier = modifier;
00540 v->_direction = dir;
00541 _m = m;
00542 _grpcount++;
00543 return true;
00544 }
00545
00546
00547
00548 bool SGMetar::scanRwyVisRange()
00549 {
00550 char *m = _m;
00551 int i;
00552 SGMetarRunway r;
00553 if (*m++ != 'R')
00554 return false;
00555 if (!scanNumber(&m, &i, 2))
00556 return false;
00557 if (*m == 'L' || *m == 'C' || *m == 'R')
00558 m++;
00559
00560 char id[4];
00561 strncpy(id, _m + 1, i = m - _m - 1);
00562 id[i] = '\0';
00563
00564 if (*m++ != '/')
00565 return false;
00566
00567 int from, to;
00568 if (*m == 'P')
00569 m++, r._min_visibility._modifier = SGMetarVisibility::GREATER_THAN;
00570 else if (*m == 'M')
00571 m++, r._min_visibility._modifier = SGMetarVisibility::LESS_THAN;
00572 if (!scanNumber(&m, &from, 4))
00573 return false;
00574 if (*m == 'V') {
00575 m++;
00576 if (*m == 'P')
00577 m++, r._max_visibility._modifier = SGMetarVisibility::GREATER_THAN;
00578 else if (*m == 'M')
00579 m++, r._max_visibility._modifier = SGMetarVisibility::LESS_THAN;
00580 if (!scanNumber(&m, &to, 4))
00581 return false;
00582 } else
00583 to = from;
00584
00585 if (!strncmp(m, "FT", 2)) {
00586 from = int(from * SG_FEET_TO_METER);
00587 to = int(to * SG_FEET_TO_METER);
00588 m += 2;
00589 }
00590 r._min_visibility._distance = from;
00591 r._max_visibility._distance = to;
00592
00593 if (*m == '/')
00594 *m++;
00595 if (*m == 'D')
00596 m++, r._min_visibility._tendency = SGMetarVisibility::DECREASING;
00597 else if (*m == 'N')
00598 m++, r._min_visibility._tendency = SGMetarVisibility::STABLE;
00599 else if (*m == 'U')
00600 m++, r._min_visibility._tendency = SGMetarVisibility::INCREASING;
00601
00602 if (!scanBoundary(&m))
00603 return false;
00604 _m = m;
00605
00606 _runways[id]._min_visibility = r._min_visibility;
00607 _runways[id]._max_visibility = r._max_visibility;
00608 _grpcount++;
00609 return true;
00610 }
00611
00612
00613 static const struct Token special[] = {
00614 { "NSW", "no significant weather" },
00615 { "VCSH", "showers in the vicinity" },
00616 { "VCTS", "thunderstorm in the vicinity" },
00617 { 0, 0 }
00618 };
00619
00620
00621 static const struct Token description[] = {
00622 { "SH", "showers of" },
00623 { "TS", "thunderstorm with" },
00624 { "BC", "patches of" },
00625 { "BL", "blowing" },
00626 { "DR", "low drifting" },
00627 { "FZ", "freezing" },
00628 { "MI", "shallow" },
00629 { "PR", "partial" },
00630 { 0, 0 }
00631 };
00632
00633
00634 static const struct Token phenomenon[] = {
00635 { "DZ", "drizzle" },
00636 { "GR", "hail" },
00637 { "GS", "small hail and/or snow pellets" },
00638 { "IC", "ice crystals" },
00639 { "PE", "ice pellets" },
00640 { "RA", "rain" },
00641 { "SG", "snow grains" },
00642 { "SN", "snow" },
00643 { "UP", "unknown precipitation" },
00644 { "BR", "mist" },
00645 { "DU", "widespread dust" },
00646 { "FG", "fog" },
00647 { "FGBR", "fog bank" },
00648 { "FU", "smoke" },
00649 { "HZ", "haze" },
00650 { "PY", "spray" },
00651 { "SA", "sand" },
00652 { "VA", "volcanic ash" },
00653 { "DS", "duststorm" },
00654 { "FC", "funnel cloud/tornado waterspout" },
00655 { "PO", "well-developed dust/sand whirls" },
00656 { "SQ", "squalls" },
00657 { "SS", "sandstorm" },
00658 { "UP", "unknown" },
00659 { 0, 0 }
00660 };
00661
00662
00663
00664 bool SGMetar::scanWeather()
00665 {
00666 char *m = _m;
00667 string weather;
00668 const struct Token *a;
00669 if ((a = scanToken(&m, special))) {
00670 if (!scanBoundary(&m))
00671 return false;
00672 _weather.push_back(a->text);
00673 _m = m;
00674 return true;
00675 }
00676
00677 string pre, post;
00678 int intensity = 0;
00679 if (*m == '-')
00680 m++, pre = "light ", intensity = 1;
00681 else if (*m == '+')
00682 m++, pre = "heavy ", intensity = 3;
00683 else if (!strncmp(m, "VC", 2))
00684 m += 2, post = "in the vicinity ";
00685 else
00686 pre = "moderate ", intensity = 2;
00687
00688 int i;
00689 for (i = 0; i < 3; i++) {
00690 if (!(a = scanToken(&m, description)))
00691 break;
00692 weather += string(a->text) + " ";
00693 }
00694 for (i = 0; i < 3; i++) {
00695 if (!(a = scanToken(&m, phenomenon)))
00696 break;
00697 weather += string(a->text) + " ";
00698 if (!strcmp(a->id, "RA"))
00699 _rain = intensity;
00700 else if (!strcmp(a->id, "HA"))
00701 _hail = intensity;
00702 else if (!strcmp(a->id, "SN"))
00703 _snow = intensity;
00704 }
00705 if (!weather.length())
00706 return false;
00707 if (!scanBoundary(&m))
00708 return false;
00709 _m = m;
00710 weather = pre + weather + post;
00711 weather.erase(weather.length() - 1);
00712 _weather.push_back(weather);
00713 _grpcount++;
00714 return true;
00715 }
00716
00717
00718 static const struct Token cloud_types[] = {
00719 { "AC", "altocumulus" },
00720 { "ACC", "altocumulus castellanus" },
00721 { "ACSL", "altocumulus standing lenticular" },
00722 { "AS", "altostratus" },
00723 { "CB", "cumulonimbus" },
00724 { "CBMAM", "cumulonimbus mammatus" },
00725 { "CC", "cirrocumulus" },
00726 { "CCSL", "cirrocumulus standing lenticular" },
00727 { "CI", "cirrus" },
00728 { "CS", "cirrostratus" },
00729 { "CU", "cumulus" },
00730 { "CUFRA", "cumulus fractus" },
00731 { "NS", "nimbostratus" },
00732 { "SAC", "stratoaltocumulus" },
00733 { "SC", "stratocumulus" },
00734 { "SCSL", "stratocumulus standing lenticular" },
00735 { "ST", "stratus" },
00736 { "STFRA", "stratus fractus" },
00737 { "TCU", "towering cumulus" },
00738 { 0, 0 }
00739 };
00740
00741
00742
00743 bool SGMetar::scanSkyCondition()
00744 {
00745 char *m = _m;
00746 int i;
00747 SGMetarCloud cl;
00748
00749 if (!strncmp(m, "//////", 6)) {
00750 m += 6;
00751 if (!scanBoundary(&m))
00752 return false;
00753 _m = m;
00754 return true;
00755 }
00756
00757 if (!strncmp(m, "CLR", i = 3)
00758 || !strncmp(m, "SKC", i = 3)
00759 || !strncmp(m, "NSC", i = 3)
00760 || !strncmp(m, "CAVOK", i = 5)) {
00761 m += i;
00762 if (!scanBoundary(&m))
00763 return false;
00764
00765 if (i == 3) {
00766 cl._coverage = 0;
00767 _clouds.push_back(cl);
00768 } else {
00769 _cavok = true;
00770 }
00771 _m = m;
00772 return true;
00773 }
00774
00775 if (!strncmp(m, "VV", i = 2))
00776 ;
00777 else if (!strncmp(m, "FEW", i = 3))
00778 cl._coverage = 1;
00779 else if (!strncmp(m, "SCT", i = 3))
00780 cl._coverage = 2;
00781 else if (!strncmp(m, "BKN", i = 3))
00782 cl._coverage = 3;
00783 else if (!strncmp(m, "OVC", i = 3))
00784 cl._coverage = 4;
00785 else
00786 return false;
00787 m += i;
00788
00789 if (!strncmp(m, "///", 3))
00790 m += 3, i = -1;
00791 else if (scanBoundary(&m)) {
00792 _m = m;
00793 return true;
00794 } else if (!scanNumber(&m, &i, 3))
00795 i = -1;
00796
00797 if (cl._coverage == -1) {
00798 if (!scanBoundary(&m))
00799 return false;
00800 if (i == -1)
00801 _vert_visibility._modifier = SGMetarVisibility::NOGO;
00802 else
00803 _vert_visibility._distance = i * 100 * SG_FEET_TO_METER;
00804 _m = m;
00805 return true;
00806 }
00807
00808 if (i != -1)
00809 cl._altitude = i * 100 * SG_FEET_TO_METER;
00810
00811 const struct Token *a;
00812 if ((a = scanToken(&m, cloud_types))) {
00813 cl._type = a->id;
00814 cl._type_long = a->text;
00815 }
00816 if (!scanBoundary(&m))
00817 return false;
00818 _clouds.push_back(cl);
00819 _m = m;
00820 _grpcount++;
00821 return true;
00822 }
00823
00824
00825
00826
00827 bool SGMetar::scanTemperature()
00828 {
00829 char *m = _m;
00830 int sign = 1, temp, dew;
00831 if (!strncmp(m, "XX/XX", 5)) {
00832 _m += 5;
00833 return scanBoundary(&_m);
00834 }
00835
00836 if (*m == 'M')
00837 m++, sign = -1;
00838 if (!scanNumber(&m, &temp, 2))
00839 return false;
00840 temp *= sign;
00841
00842 if (*m++ != '/')
00843 return false;
00844 if (!scanBoundary(&m)) {
00845 if (!strncmp(m, "XX", 2))
00846 m += 2, sign = 0, dew = temp;
00847 else {
00848 sign = 1;
00849 if (*m == 'M')
00850 m++, sign = -1;
00851 if (!scanNumber(&m, &dew, 2))
00852 return false;
00853 }
00854 if (!scanBoundary(&m))
00855 return false;
00856 if (sign)
00857 _dewp = sign * dew;
00858 }
00859 _temp = temp;
00860 _m = m;
00861 _grpcount++;
00862 return true;
00863 }
00864
00865
00866 double SGMetar::getRelHumidity() const
00867 {
00868 if (_temp == NaN || _dewp == NaN)
00869 return NaN;
00870 double dewp = pow(10.0, 7.5 * _dewp / (237.7 + _dewp));
00871 double temp = pow(10.0, 7.5 * _temp / (237.7 + _temp));
00872 return dewp * 100 / temp;
00873 }
00874
00875
00876
00877
00878 bool SGMetar::scanPressure()
00879 {
00880 char *m = _m;
00881 double factor;
00882 int press, i;
00883
00884 if (*m == 'A')
00885 factor = SG_INHG_TO_PA / 100;
00886 else if (*m == 'Q')
00887 factor = 100;
00888 else
00889 return false;
00890 m++;
00891 if (!scanNumber(&m, &press, 2))
00892 return false;
00893 press *= 100;
00894 if (!strncmp(m, "//", 2))
00895 m += 2;
00896 else if (scanNumber(&m, &i, 2))
00897 press += i;
00898 else
00899 return false;
00900 if (!scanBoundary(&m))
00901 return false;
00902 _pressure = press * factor;
00903 _m = m;
00904 _grpcount++;
00905 return true;
00906 }
00907
00908
00909 static const char *runway_deposit[] = {
00910 "clear and dry",
00911 "damp",
00912 "wet or puddles",
00913 "frost",
00914 "dry snow",
00915 "wet snow",
00916 "slush",
00917 "ice",
00918 "compacted snow",
00919 "frozen ridges"
00920 };
00921
00922
00923 static const char *runway_deposit_extent[] = {
00924 0, "1-10%", "11-25%", 0, 0, "26-50%", 0, 0, 0, "51-100%"
00925 };
00926
00927
00928 static const char *runway_friction[] = {
00929 0,
00930 "poor braking action",
00931 "poor/medium braking action",
00932 "medium braking action",
00933 "medium/good braking action",
00934 "good braking action",
00935 0, 0, 0,
00936 "friction: unreliable measurement"
00937 };
00938
00939
00940
00941 bool SGMetar::scanRunwayReport()
00942 {
00943 char *m = _m;
00944 int i;
00945 char id[4];
00946 SGMetarRunway r;
00947
00948 if (!scanNumber(&m, &i, 2))
00949 return false;
00950 if (i == 88)
00951 strcpy(id, "ALL");
00952 else if (i == 99)
00953 strcpy(id, "REP");
00954 else if (i >= 50) {
00955 i -= 50;
00956 id[0] = i / 10 + '0', id[1] = i % 10 + '0', id[2] = 'R', id[3] = '\0';
00957 } else
00958 id[0] = i / 10 + '0', id[1] = i % 10 + '0', id[2] = '\0';
00959
00960 if (!strncmp(m, "CLRD", 4)) {
00961 m += 4;
00962 r._deposit_string = "cleared";
00963 } else {
00964 if (scanNumber(&m, &i, 1)) {
00965 r._deposit = i;
00966 r._deposit_string = runway_deposit[i];
00967 } else if (*m == '/')
00968 m++;
00969 else
00970 return false;
00971
00972 if (*m == '1' || *m == '2' || *m == '5' || *m == '9') {
00973 r._extent = *m - '0';
00974 r._extent_string = runway_deposit_extent[*m - '0'];
00975 } else if (*m != '/')
00976 return false;
00977
00978 m++;
00979 i = -1;
00980 if (!strncmp(m, "//", 2))
00981 m += 2;
00982 else if (!scanNumber(&m, &i, 2))
00983 return false;
00984
00985 if (i == 0)
00986 r._depth = 0.0005;
00987 else if (i > 0 && i <= 90)
00988 r._depth = i / 1000.0;
00989 else if (i >= 92 && i <= 98)
00990 r._depth = (i - 90) / 20.0;
00991 else if (i == 99)
00992 r._comment = "runway not in use";
00993 else if (i == -1)
00994 ;
00995 else
00996 return false;
00997 }
00998 i = -1;
00999 if (m[0] == '/' && m[1] == '/')
01000 m += 2;
01001 else if (!scanNumber(&m, &i, 2))
01002 return false;
01003 if (i >= 1 && i < 90) {
01004 r._friction = i / 100.0;
01005 } else if ((i >= 91 && i <= 95) || i == 99) {
01006 r._friction_string = runway_friction[i - 90];
01007 }
01008 if (!scanBoundary(&m))
01009 return false;
01010
01011 _runways[id]._deposit = r._deposit;
01012 _runways[id]._deposit_string = r._deposit_string;
01013 _runways[id]._extent = r._extent;
01014 _runways[id]._extent_string = r._extent_string;
01015 _runways[id]._depth = r._depth;
01016 _runways[id]._friction = r._friction;
01017 _runways[id]._friction_string = r._friction_string;
01018 _runways[id]._comment = r._comment;
01019 _m = m;
01020 _grpcount++;
01021 return true;
01022 }
01023
01024
01025
01026 bool SGMetar::scanWindShear()
01027 {
01028 char *m = _m;
01029 if (strncmp(m, "WS", 2))
01030 return false;
01031 m += 2;
01032 if (!scanBoundary(&m))
01033 return false;
01034
01035 if (!strncmp(m, "ALL", 3)) {
01036 m += 3;
01037 if (!scanBoundary(&m))
01038 return false;
01039 if (strncmp(m, "RWY", 3))
01040 return false;
01041 m += 3;
01042 if (*m == 'S')
01043 m++;
01044 if (!scanBoundary(&m))
01045 return false;
01046 _runways["ALL"]._wind_shear = true;
01047 _m = m;
01048 return true;
01049 }
01050
01051 char id[4], *mm;
01052 int i, cnt;
01053 for (cnt = 0;; cnt++) {
01054 if (strncmp(m, "RWY", 3))
01055 break;
01056 m += 3;
01057 scanBoundary(&m);
01058 mm = m;
01059 if (!scanNumber(&m, &i, 2))
01060 return false;
01061 if (*m == 'L' || *m == 'C' || *m == 'R')
01062 m++;
01063 strncpy(id, mm, i = m - mm);
01064 id[i] = '\0';
01065 if (!scanBoundary(&m))
01066 return false;
01067 _runways[id]._wind_shear = true;
01068 }
01069 if (!cnt)
01070 _runways["ALL"]._wind_shear = true;
01071 _m = m;
01072 return true;
01073 }
01074
01075
01076 bool SGMetar::scanTrendForecast()
01077 {
01078 char *m = _m;
01079 if (strncmp(m, "NOSIG", 5))
01080 return false;
01081
01082 m += 5;
01083 if (!scanBoundary(&m))
01084 return false;
01085 _m = m;
01086 return true;
01087 }
01088
01089
01090
01091 static const struct Token colors[] = {
01092 { "BLU", "Blue" },
01093 { "WHT", "White" },
01094 { "GRN", "Green" },
01095 { "YLO", "Yellow" },
01096 { "AMB", "Amber" },
01097 { "RED", "Red" },
01098 { 0, 0 }
01099 };
01100
01101
01102 bool SGMetar::scanColorState()
01103 {
01104 char *m = _m;
01105 const struct Token *a;
01106 if (!(a = scanToken(&m, colors)))
01107 return false;
01108 if (!scanBoundary(&m))
01109 return false;
01110
01111 _m = m;
01112 return true;
01113 }
01114
01115
01116 bool SGMetar::scanRemark()
01117 {
01118 if (strncmp(_m, "RMK", 3))
01119 return false;
01120 _m += 3;
01121 if (!scanBoundary(&_m))
01122 return false;
01123
01124 while (*_m) {
01125 if (!scanRunwayReport()) {
01126 while (*_m && !isspace(*_m))
01127 _m++;
01128 scanBoundary(&_m);
01129 }
01130 }
01131 return true;
01132 }
01133
01134
01135 bool SGMetar::scanRemainder()
01136 {
01137 char *m = _m;
01138 if (!(strncmp(m, "NOSIG", 5))) {
01139 m += 5;
01140 if (scanBoundary(&m))
01141 _m = m;
01142 }
01143
01144 if (!scanBoundary(&m))
01145 return false;
01146 _m = m;
01147 return true;
01148 }
01149
01150
01151 bool SGMetar::scanBoundary(char **s)
01152 {
01153 if (**s && !isspace(**s))
01154 return false;
01155 while (isspace(**s))
01156 (*s)++;
01157 return true;
01158 }
01159
01160
01161 int SGMetar::scanNumber(char **src, int *num, int min, int max)
01162 {
01163 int i;
01164 char *s = *src;
01165 *num = 0;
01166 for (i = 0; i < min; i++) {
01167 if (!isdigit(*s))
01168 return 0;
01169 else
01170 *num = *num * 10 + *s++ - '0';
01171 }
01172 for (; i < max && isdigit(*s); i++)
01173 *num = *num * 10 + *s++ - '0';
01174 *src = s;
01175 return i;
01176 }
01177
01178
01179
01180 const struct Token *SGMetar::scanToken(char **str, const struct Token *list)
01181 {
01182 const struct Token *longest = 0;
01183 int maxlen = 0, len;
01184 const char *s;
01185 for (int i = 0; (s = list[i].id); i++) {
01186 len = strlen(s);
01187 if (!strncmp(s, *str, len) && len > maxlen) {
01188 maxlen = len;
01189 longest = &list[i];
01190 }
01191 }
01192 *str += maxlen;
01193 return longest;
01194 }
01195
01196
01197 void SGMetarCloud::set(double alt, int cov)
01198 {
01199 _altitude = alt;
01200 if (cov != -1)
01201 _coverage = cov;
01202 }
01203
01204
01205 void SGMetarVisibility::set(double dist, int dir, int mod, int tend)
01206 {
01207 _distance = dist;
01208 if (dir != -1)
01209 _direction = dir;
01210 if (mod != -1)
01211 _modifier = mod;
01212 if (tend != 1)
01213 _tendency = tend;
01214 }
01215
01216 #undef NaN