array("STATION" => "STATION", "OBSERVED" => "OBSERVED", "NOW" => "NOW", "AGE" => "AGE", "WIND" => "WIND", "VISIBILITY" => "VISIBILITY", "CLOUDS" => "CLOUDS", "TEMP" => "TEMP", "TEMPONLY" => "TEMPONLY", "DEWPT" => "DEWPT", "HUMIDITY" => "HUMIDITY", "BAROMETER" => "BAROMETER")); $metarnames = array(); $latitudes = array(); $longitudes = array(); $fontselection = array(); $metarlongnames = array(); $temparray = array(); $metars = array(); $lang = "en"; $metarfilename = getenv('DOCUMENT_ROOT') . "/metars.txt"; $fd = fopen ($metarfilename, "r"); while (!feof ($fd)) { $buffer = fgets($fd, 4096); $metarfile[] = $buffer; } fclose ($fd); $contents = ''; //reset the variable if (!empty($metarfile)) { for($k=0;$k "$contents[4]"); $latitudes = $latitudes + $temparray; $temparray = array ("$contents[0]" => "$contents[5]"); $longitudes = $longitudes + $temparray; $temparray = array ("$contents[0]" => "$contents[6]"); $fontselection = $fontselection + $temparray; $temparray = array ("$contents[0]" => "$contents[1]"); $metarlongnames = $metarlongnames + $temparray; } } foreach ( $metarnames as $metarname ) { $fileData = ""; $fileName = "http://weather.noaa.gov/pub/data/observations/metar/stations/" . $metarname . ".TXT"; $fp = fopen($fileName, "r"); while (!feof($fp)){ $fileData .= fread($fp, 8192);} //$metar = preg_replace( '|\n|', ' ', $fileData ); // if (strpos ($metar, "METAR METAR ") > 0) { // $metar = explode("METAR METAR ", $metar); } // else { $metar = explode("\n", $fileData); $metars[$metarname] = $metar[1]; } echo 'Title: METAR Temps' . chr(10); echo "Refresh: 5" . chr(10); echo 'Font: 1, 20, 1, "Arial"' . chr(10); echo 'Font: 2, 12, 1, "Arial"' . chr(10); echo 'Threshold: 999' . chr(10); echo 'Color: 200 215 175' . chr(10); foreach ( $metarnames as $metarname ) { //update value $wxInfo['STATION'] = $metarname; $wxInfo['CONDITIONS'] = ""; $wxInfo['AGE'] = ""; process_metar($metars[$metarname],$wxInfo); if (empty($wxInfo['CONDITIONS'])) { $observed = ucwords($wxInfo['CLOUDS']); } else { $observed = ucwords($wxInfo['CONDITIONS']); } if ($wxInfo['TEMPONLY'] > 80) { $wxInfo['TEMP'] .= " HI: " . $wxInfo['HEAT INDEX']; } if ($wxInfo['TEMPONLY'] < 51 && $wxInfo['WIND'] != 'calm') { $wxInfo['TEMP'] .= " WC: " . $wxInfo['WIND CHILL']; } if ($wxInfo['AGE'] < 120 && $wxInfo['AGE'] > -50) { echo 'Text: ' . $latitudes[$metarname] . ' ,' . trim($longitudes[$metarname]) . ', '. $fontselection[$metarname] .', "'. $wxInfo['TEMPONLY'] .'°", "' . $metarlongnames[$metarname] . '\n' . $observed . '\n' . $wxInfo['TEMP'] . '\n' . $wxInfo['HUMIDITY'] . ' RH\n' . $wxInfo['BAROMETER'] . '\n' . 'Wind: ' . $wxInfo['WIND'] . '\n' . $wxInfo['OBSERVED'] . '\n' . $wxInfo['AGE'] . ' min ago"' . chr(10); } } //-------------------------------------------------------------------- // This function formats observation time in the local time zone of // server, the current local time on server, and time difference since // observation. $utc is a UNIX timestamp for Universal Coordinated // Time (Greenwich Mean Time or Zulu Time). function set_time_data($utc, &$wxInfo) { $timeZoneOffset = -18000; $local = $utc + $timeZoneOffset; $wxInfo['OBSERVED'] = date('g:i A m/d/y',$local); $now = time(); $wxInfo['NOW'] = date('D M j, H:i T',$now); $timeDiff = floor(($now - $utc) / 60); $wxInfo['AGE'] = $timeDiff; } //---------------------------------------------------------------------- //This function directs the examination of each group of the //METAR. The problem with a METAR is that not all the groups have //to be there. Some groups could be missing. Fortunately, the //groups must be in a specific order. (This function also assumes that a //METAR is well-formed, that is, no typographical mistakes.) This //function uses a function variable to organize the sequence in which to //decode each group. Each function checks to see if it can decode //the current METAR part. If not, then the group pointer is advanced for //the next function to try. If yes, the function decodes that part of //the METAR and advances the METAR pointer and group pointer. (If the //function can be called again to decode similar information, then the //group pointer does not get advanced.) function process_metar($metar, &$wxInfo) { if ($metar != '') { $metarParts = explode(' ',$metar); $groupName = array('get_station', 'get_time', 'get_station_type', 'get_wind', 'get_var_wind', 'get_visibility', 'get_runway', 'get_conditions', 'get_cloud_cover', 'get_temperature', 'get_altimeter'); $metarPtr = 1; // get_station identity is ignored $group = 1; while ($group < count($groupName)) { $part = $metarParts[$metarPtr]; $groupName[$group]($part,$metarPtr,$group,$wxInfo); // $groupName is a function variable } } else $wxInfo['ERROR'] = 'Data not available'; } //---------------------------------------------------------------- // Ignore station code. Script assumes this matches requesting // $station. This function is never called. It is here for // completeness of documentation. function get_station($part, &$metarPtr, &$group, &$wxInfo) { if (strlen($part) == 4 and $group == 0) { $group++; $metarPtr++; } } function get_time($part, &$metarPtr, &$group, &$wxInfo) { // Ignore observation time. This information is found in the // first line of the NWS file. // Format is ddhhmmZ where dd = day, hh = hours, mm = minutes // in UTC time. if (substr($part,-1) == 'Z') $utc = gmmktime ( substr($part, 2, 2), substr($part, 4, 2), 0, gmdate("m") ,substr($part, 0, 2), gmdate("Y")); set_time_data($utc,$wxInfo); $metarPtr++; $group++; } function get_station_type($part, &$metarPtr, &$group, &$wxInfo) { // Ignore station type if present. if ($part == 'AUTO' || $part == 'COR') $metarPtr++; $group++; } //------------------------------------------------------------------------- // Decodes wind direction and speed information. // Format is dddssKT where ddd = degrees from North, ss = speed, // KT for knots or dddssGggKT where G stands for gust and gg = gust // speed. (ss or gg can be a 3-digit number.) // KT can be replaced with MPH for meters per second or KMH for //kilometers per hour. function get_wind($part, &$metarPtr, &$group, &$wxInfo) { if (ereg('^([0-9G]{5,10}|VRB[0-9]{2,3})(KT|MPS|KMH)$',$part,$pieces)) { $part = $pieces[1]; $unit = $pieces[2]; if ($part == '00000') { $wxInfo['WIND'] = 'calm'; // no wind } else { ereg('([0-9]{3}|VRB)([0-9]{2,3})G?([0-9]{2,3})?',$part,$pieces); if ($pieces[1] == 'VRB') $direction = 'varies'; else { $angle = (integer) $pieces[1]; $compass = array('N','NNE','NE','ENE','E','ESE','SE','SSE', 'S','SSW','SW','WSW','W','WNW','NW','NNW'); $direction = $compass[round($angle / 22.5) % 16]; } if ($pieces[3] == 0) $gust = ''; else $gust = ', gusting to ' . speed($pieces[3], $unit); $wxInfo['WIND'] = speed($pieces[2], $unit) . " " . $direction . $gust; } $metarPtr++; } $group++; } function speed($part, $unit) { global $lang; // Convert wind speed into miles per hour. // Some other common conversion factors (to 6 significant digits): // 1 mi/hr = 1.15080 knots = 0.621371 km/hr = 2.23694 m/s // 1 ft/s = 1.68781 knots = 0.911344 km/hr = 3.28084 m/s // 1 knot = 0.539957 km/hr = 1.94384 m/s // 1 km/hr = 1.852 knots = 3.6 m/s // 1 m/s = 0.514444 knots = 0.277778 km/s if ($unit == 'KT') $speed = 1.1508 * $part; // from knots elseif ($unit == 'MPS') $speed = 2.23694 * $part; // from meters per second else $speed = 0.621371 * $part; // from km per hour $speedkph = $speed / 0.621371; if ($lang=="en") $speed ="" . round($speed) . " mph"; else $speed = "" . round($speedkph) . " km/h"; return $speed; } function get_var_wind($part, &$metarPtr, &$group, &$wxInfo) { // Ignore variable wind direction information if present. // Format is fffVttt where V stands for varies from fff // degrees to ttt degrees. if (ereg('([0-9]{3})V([0-9]{3})',$part,$pieces)) $metarPtr++; $group++; } //------------------------------------------------------------------ // Decodes visibility information. This function will be called a // second time if visibility is limited to an integer mile plus a // fraction part. // Format is mmSM for mm = statute miles, or m n/dSM for m = mile // and n/d = fraction of a mile, or just a 4-digit number nnnn (with // leading zeros) for nnnn = meters. function get_visibility($part, &$metarPtr, &$group, &$wxInfo) { global $lang; static $integerMile = ''; if (strlen($part) == 1) { // visibility is limited to a whole mile plus a fraction part $integerMile = $part . ' '; $metarPtr++; } elseif (substr($part,-2) == 'SM') { // visibility is in miles $part = substr($part,0,strlen($part)-2); if (substr($part,0,1) == 'M') { $prefix = 'less than '; $part = substr($part, 1); } else $prefix = ''; if ($lang == "en") { if (($integerMile == '' && ereg('[/]',$part,$pieces)) || $part == '1') $unit = ' mile'; else $unit = ' miles'; } $kmVis = round( $part * 1.6 ); if ($lang=="en") $wxInfo['VISIBILITY'] = $prefix . $integerMile . " $part $unit ($kmVis km)"; else $wxInfo['VISIBILITY'] = "$kmVis km"; $metarPtr++; $group++; } elseif (substr($part,-2) == 'KM') { // unknown (Reported by NFFN in Fiji) $metarPtr++; $group++; } elseif (ereg('^([0-9]{4})$',$part,$pieces)) { // visibility is in meters $distance = round($part/ 621.4, 1); // convert to miles if ($distance > 5) $distance = round($distance); if ($distance <= 1) $unit = ' mile'; else $unit = ' miles'; $wxInfo['VISIBILITY'] = $distance . $unit; $metarPtr++; $group++; } elseif ($part == 'CAVOK') { // good weather $wxInfo['VISIBILITY'] = 'greater than 7 miles'; // or 10 km $wxInfo['CONDITIONS'] = ''; $wxInfo['CLOUDS'] = 'clear skies'; $metarPtr++; $group += 4; // can skip the next 3 groups } else { $group++; } } function get_runway($part, &$metarPtr, &$group, &$wxInfo) { // Ignore runway information if present. Maybe called a second time. // Format is Rrrr/vvvvFT where rrr = runway number and // vvvv = visibility in feet. if (substr($part,0,1) == 'R' && substr($part,0,2) <> 'RA') $metarPtr++; else $group++; } //----------------------------------------------------------------------- // Decodes current weather conditions. This function maybe called several // times to decode all conditions. To learn more about weather condition // codes, visit section 12.6.8 - Present Weather Group of the Federal // Meteorological Handbook No. 1 at // www.nws.noaa.gov/oso/oso1/oso12/fmh1/fmh1ch12.htm function get_conditions($part, &$metarPtr, &$group, &$wxInfo) { static $wxCode = array( 'VC' => 'nearby', 'MI' => 'shallow', 'PR' => 'partial', 'BC' => 'patches of', 'DR' => 'drifting', 'BL' => 'blowing', 'SH' => 'showers', 'TS' => 'thunderstorm', 'FZ' => 'freezing', 'DZ' => 'drizzle', 'RA' => 'rain', 'SN' => 'snow', 'SG' => 'snow grains', 'IC' => 'ice crystals', 'PE' => 'ice pellets', 'GR' => 'hail', 'GS' => 'small hail', // and/or snow pellets 'UP' => 'unknown', 'BR' => 'mist', 'FG' => 'fog', 'FU' => 'smoke', 'VA' => 'volcanic ash', 'DU' => 'widespread dust', 'SA' => 'sand', 'HZ' => 'haze', 'PY' => 'spray', 'PO' => 'dustdevils', 'SQ' => 'strong winds', 'FC' => 'tornado', 'SS' => 'sandstorm/duststorm'); if (ereg('^(-|\+|VC)?(TS|SH|FZ|BL|DR|MI|BC|PR|RA|DZ|SN|SG|GR|GS|PE|IC|UP|BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS)+$',$part,$pieces)) { if (strlen($wxInfo['CONDITIONS']) == 0) $join = ''; else $join = ' and '; if (substr($part,0,1) == '-') { $prefix = 'light '; $part = substr($part,1); } elseif (substr($part,0,1) == '+') { $prefix = 'heavy '; $part = substr($part,1); } else $prefix = ''; // moderate conditions have no descriptor $wxInfo['CONDITIONS'] .= $join . $prefix; // The 'showers' code 'SH' is moved behind the next 2-letter code to make the English translation read better. if (substr($part,0,2) == 'SH') $part = substr($part,2,2) . substr($part,0,2). substr($part, 4); while ($code = substr($part,0,2)) { if (substr($part,0,4) == 'TSRA') { $wxInfo['CONDITIONS'] .= 'thunderstorm' . ' '; $part = substr($part,4); } else { $wxInfo['CONDITIONS'] .= $wxCode[$code] . ' '; $part = substr($part,2); } $metarPtr++; } } else { $group++; } } //-------------------------------------------------------------------------- // Decodes cloud cover information. This function maybe called several times // to decode all cloud layer observations. Only the last layer is saved. // Format is SKC or CLR for clear skies, or cccnnn where ccc = 3-letter // code and nnn = altitude of cloud layer in hundreds of feet. 'VV' seems // to be used for very low cloud layers. (Other conversion factor: // 1 m = 3.28084 ft) function get_cloud_cover($part, &$metarPtr, &$group, &$wxInfo) { global $lang; static $cloudCode = array( "en" => array( 'SKC' => 'clear skies', 'CLR' => 'clear skies', 'FEW' => 'partly cloudy', 'SCT' => 'scattered clouds', 'BKN' => 'mostly cloudy', 'OVC' => 'overcast', 'VV' => 'vertical visibility'), "fr" => array( 'SKC' => 'ciel clair', 'CLR' => 'ciel clair', 'FEW' => 'peu nuageux', 'SCT' => 'assez nuageux', 'BKN' => 'très nuageux', 'OVC' => 'couvert', 'VV' => 'vertical visibility')); if ($part == 'SKC' || $part == 'CLR') { $wxInfo['CLOUDS'] = $cloudCode[$lang][$part]; $metarPtr++; $group++; } else { if (ereg('^([A-Z]{2,3})([0-9]{3})',$part,$pieces)) { // codes for CB and TCU are ignored $wxInfo['CLOUDS'] = $cloudCode[$lang][$pieces[1]]; if ($pieces[1] == 'VV') { $altitude = (integer) 100 * $pieces[2]; // units are feet $wxInfo['CLOUDS'] .= " to $altitude ft"; } $metarPtr++; } else { $group++; } } } //------------------------------------------------------------------------- // Decodes temperature and dew point information. Relative humidity is // calculated. Also, depending on the temperature, Heat Index or Wind // Chill Temperature is calculated. // Format is tt/dd where tt = temperature and dd = dew point temperature. // All units are in Celsius. A 'M' preceeding the tt or dd indicates a // negative temperature. Some stations do not report dew point, so the // format is tt/ or tt/XX. function get_temperature($part, &$metarPtr, &$group, &$wxInfo) { global $lang; if (ereg('^(M?[0-9]{2})/(M?[0-9]{2}|[X]{2})?$',$part,$pieces)) { $tempC = (integer) strtr($pieces[1], 'M', '-'); $tempF = round(1.8 * $tempC + 32); $wxInfo['TEMPONLY'] = $tempF; $wxInfo['TEMP'] = $tempF . "°F"; get_wind_chill($tempF, $wxInfo); if (strlen($pieces[2]) != 0 && $pieces[2] != 'XX') { $dewC = (integer) strtr($pieces[2], 'M', '-'); $dewF = round(1.8 * $dewC + 32); if ($lang == "en") $wxInfo['DEWPT'] = $dewF . "F (" . $dewC . "C)"; else $wxInfo['DEWPT'] = $dewC . "C"; $rh = round(100 * pow((112 - (0.1 * $tempC) + $dewC) / (112 + (0.9 * $tempC)), 8)); $wxInfo['HUMIDITY'] = $rh . '%'; get_heat_index($tempF, $rh, $wxInfo); } $metarPtr++; $group++; } else { $group++; } } function get_heat_index($tempF, $rh, &$wxInfo) { // Calculate Heat Index based on temperature in F and relative //humidity (65 = 65%) if ($tempF > 79) { $hiF = -42.379 + 2.04901523 * $tempF + 10.14333127 * $rh - 0.22475541 * $tempF * $rh; $hiF += -0.00683783 * pow($tempF, 2) - 0.05481717 * pow($rh, 2); $hiF += 0.00122874 * pow($tempF, 2) * $rh + 0.00085282 * $tempF * pow($rh, 2); $hiF += -0.00000199 * pow($tempF, 2) * pow($rh, 2); $hiF = round($hiF); if ($hiF < $tempF) { $hiF = $tempF; } $wxInfo['HEAT INDEX'] = $hiF. "°F"; } } function get_wind_chill($tempF, &$wxInfo) { global $lang; // Calculate Wind Chill Temperature based on temperature in F and // wind speed in miles per hour if ($tempF < 51 && $wxInfo['WIND'] != 'calm') { $pieces = explode(' ', $wxInfo['WIND']); $windspeed = (integer) $pieces[0]; // wind speed must be in mph $chillF = 35.74 + 0.6215 * $tempF - 35.75 * pow($windspeed, 0.16) + 0.4275 * $tempF * pow($windspeed, 0.16); $chillF = round($chillF); if ($chillF > $tempF) { $chillF = $tempF; } $wxInfo['WIND CHILL'] = $chillF . "°F"; } } //----------------------------------------------------------------------- // Decodes altimeter or barometer information. // Format is Annnn where nnnn represents a real number as nn.nn in // inches of Hg, // or Qpppp where pppp = hectoPascals. // Some other common conversion factors: // 1 millibar = 1 hPa // 1 in Hg = 0.02953 hPa // 1 mm Hg = 25.4 in Hg = 0.750062 hPa // 1 lb/sq in = 0.491154 in Hg = 0.014504 hPa // 1 atm = 0.33421 in Hg = 0.0009869 hPa function get_altimeter($part, &$metarPtr, &$group, &$wxInfo) { if (ereg('^(A|Q)([0-9]{4})',$part,$pieces)) { if ($pieces[1] == 'A') { $pressureIN = substr($pieces[2],0,2) . '.' . substr($pieces[2],2); // units are inches Hg, converts to hectoPascals $pressureHPA = round($pressureIN / 0.02953); } else { $pressureHPA = (integer) $pieces[2]; // units are hectoPascals $pressureIN = round(0.02953 * $pressureHPA,2); // convert to inches Hg } $wxInfo['BAROMETER'] = "$pressureIN in."; $metarPtr++; $group++; } else { $group++; } } ?>