CVS: phpweather data_retrieval.php,1.31,1.32 defaults-dist.php,1.17,1...
Brought to you by:
iridium
From: Etienne T. <eti...@us...> - 2003-09-08 04:20:49
|
Update of /cvsroot/phpweather/phpweather In directory sc8-pr-cvs1:/tmp/cvs-serv24907 Modified Files: data_retrieval.php defaults-dist.php phpweather.php Log Message: Added TAF support. Modified data_retrieval.php, defaults-dist.php, phpweather.php, pw_db_mysql.php, pw_db_null.php. Needs to add output functionality and support all backends. Index: data_retrieval.php =================================================================== RCS file: /cvsroot/phpweather/phpweather/data_retrieval.php,v retrieving revision 1.31 retrieving revision 1.32 diff -u -3 -r1.31 -r1.32 --- data_retrieval.php 1 Jul 2003 09:34:54 -0000 1.31 +++ data_retrieval.php 8 Sep 2003 04:20:44 -0000 1.32 @@ -3,9 +3,9 @@ require_once(PHPWEATHER_BASE_DIR . '/db_layer.php'); /** - * This class contains all the logic needed to get and store METARs. + * This class contains all the logic needed to get and store METARs and TAFs. * - * It manages the caching system, the station and the METAR. + * It manages the caching system, the station, the METAR and the TAF. * * @author Martin Geisler <gim...@gi...> * @version $Id$ @@ -26,6 +26,19 @@ var $metar; /** + * The TAF is stored here. + * + * The property is used whenever someone wants access to the raw + * TAF. This should be used for reading only, if you want to + * change the TAF (for testing purposes etc.), then use + * set_taf() instead. + * + * @var string + * @see set_taf() + */ + var $taf; + + /** * Data associated with the current ICAO. * * @var array The array has three entries: name, country, @@ -129,20 +142,18 @@ } /** - * Retrieves a METAR report using fsockopen(). + * Retrieves a file using fsockopen(). * - * The communication with the proxy (if one is needed) and the NWS + * The communication with the proxy (if one is needed) and the host * is done using fsockopen(). This should be used when the file() * function is disabled. * * @access private - * @param string The ICAO for which the report will be fetched. - * @return array The raw METAR report line by line in an array. - * @see get_metar_file() + * @param string The host + * @param string The location of the file + * @return array The raw file line by line in an array. */ - function get_metar_socket($icao) { - $host = 'weather.noaa.gov'; - $location = "/pub/data/observations/metar/stations/$icao.TXT"; + function get_file_socket($host,$location) { $request = "HTTP/1.1\r\n" . "If-Modified-Since: Sat, 29 Oct 1994 09:00:00 GMT\r\n" . "Pragma: no-cache\r\n". @@ -164,7 +175,7 @@ "Connection: Close\r\n\r\n"; } - $metar_data = false; + $data = false; if ($fp) { fputs($fp, $request); @@ -179,15 +190,32 @@ /* We know now, that the following lines are the contents. */ while ($line = fgets($fp, 1024)) { - $metar_data[] = $line; + $data[] = $line; } fclose($fp); } } - return $metar_data; + return $data; } + /** + * Retrieves a METAR report using fsockopen(). + * + * The communication with the proxy (if one is needed) and the NWS + * is done using fsockopen(). This should be used when the file() + * function is disabled. + * + * @access private + * @param string The ICAO for which the report will be fetched. + * @return array The raw METAR report line by line in an array. + * @see get_metar_file() + */ + function get_metar_socket($icao) { + $host = 'weather.noaa.gov'; + $location = "/pub/data/observations/metar/stations/$icao.TXT"; + return $this->get_file_socket($host,$location); + } /** * Retrieves a METAR report using file(). @@ -207,7 +235,7 @@ return @file('http://' . $host . $location); } - + /** * Tries to get a METAR from the database. * @@ -266,7 +294,6 @@ } } - /** * Fetches a METAR from the Internet. * @@ -359,6 +386,248 @@ return $metar; } + + /** + * Retrieves a raw TAF, either from the web or from a database. + * + * If the TAF is already set, then it just returns that. If it's + * not set, then it tries to get it from the database. + * + * @access public + * @return string The raw TAF. + */ + function get_taf() { + if (empty($this->taf)) { + /* The TAF is not set - we try to load it */ + $this->debug('The TAF is not set, I\'ll try to find the TAF in the database.'); + return $this->get_taf_from_db(); + } else { + $this->debug('The TAF was set - I\'ll just use that.'); + return $this->taf; + } + } + + /** + * Sets the TAF directly, for testing etc + * + * It loads and decodes the TAF if it is different from the old + * one. If the new TAF is the same as the old one, nothing is + * changed. + * + * Also sets the ICAO to be correct for this TAF. + * + * @access public + * @param string The TAF we want decoded. + * @param string The icao of the station. + */ + function set_taf($new_taf) { + + if ($new_taf != $this->get_taf()) { + $this->debug('Loading a TAF manually.'); + $this->properties['icao'] = strtoupper(substr($new_taf,0,4)); + $this->icao_data = $this->db->lookup_icao($this->get_icao()); + $this->taf = $new_taf; + $this->decoded_taf = $this->decode_taf(); + } + } + + /** + * Retrieves a TAF report using fsockopen(). + * + * The communication with the proxy (if one is needed) and the NWS + * is done using fsockopen(). This should be used when the file() + * function is disabled. + * + * @access private + * @param string The ICAO for which the report will be fetched. + * @return array The raw TAF report line by line in an array. + * @see get_taf_file() + */ + function get_taf_socket($icao) { + $host = 'weather.noaa.gov'; + $location = "/pub/data/forecasts/taf/stations/$icao.TXT"; + return $this->get_file_socket($host,$location); + } + + /** + * Retrieves a TAF report using file(). + * + * The communication with the NWS is done using file(). This should + * only be used when a direct connection to the NWS can be + * established, the proxy settings isn't used with file(). + * + * @access private + * @param string The ICAO for which the report will be fetched. + * @return array The raw TAF report line by line in an array. + * @see get_taf_socket() + */ + function get_taf_file($icao) { + $host = 'weather.noaa.gov'; + $location = "/pub/data/forecasts/taf/stations/$icao.TXT"; + $this->debug("getting TAF from location: http://$host/$location"); + return @file('http://' . $host . $location); + } + + /** + * Tries to get a TAF from the database. + * + * It looks in the database, and fetches a new TAF if necessary. + * You do not have to be connected to the database before you call + * this function, just make sure that you have passed the right + * properties to the object. + * + * If $this->properties['always_use_db'] is True, then it ignores + * the timestamp of the TAF and just returns it. Otherwise it will + * try to get a new TAF from the web, if the old one is older than + * one hour. + * + * @access public + * @return string The raw TAF. + */ + function get_taf_from_db() { + if (!$this->db->connect()) { + return false; + } + + if ($data = $this->db->get_taf($this->get_icao())) { /* found station */ + $this->debug('get_taf_from_db(): Found the TAF in the database'); + list($taf, $timestamp) = $data; + + /* We set the TAF right away, and then count on + * get_taf_from_web() to set it to something else, if + * necessary. + */ + $this->taf = $taf; + if ($this->properties['always_use_db'] || + $timestamp > time() - $this->properties['cache_timeout']) { + + /* We have asked explicit for a cached TAF, or the TAF is + * still fresh. Either way - we return the TAF we found in + * the database. + */ + $this->debug('get_taf_from_db(): Using previously cached TAF for <code>' . + $this->get_location() . '</code>. The TAF expires in ' . + ($timestamp + $this->properties['cache_timeout'] - time()) . + ' seconds.'); + return $taf; + } else { + /* The TAF is too old, so we fetch new */ + $this->debug('get_taf_from_db(): The TAF for <code>' . + $this->get_location() . '</code> was ' . + (time() - $this->properties['cache_timeout'] - $timestamp) . + ' seconds too old.'); + return $this->get_taf_from_web(false); + } + } else { + /* We need to get a new TAF from the web. */ + $this->debug('get_taf_from_db(): New station <code>' . + $this->get_location() . '</code> - fetching a new TAF.'); + return $this->get_taf_from_web(true); + } + + } + + /** + * Fetches a TAF from the Internet. + * + * The TAF is fetched via HTTP from the National Weather Services + * public server. The files can be found under the + * http://weather.noaa.gov/pub/data/observations/taf/stations/ + * directory as ICAO.TXT where ICAO is replaced by the actual ICAO. + * + * @param boolean Should the station be inserted into the database, + * or should we update an already existing entry? + * @access public + * @return string The raw TAF. + */ + function get_taf_from_web($new_station) { + + $taf = ''; + $icao = $this->get_icao(); + + switch ($this->properties['fetch_method']) { + case 'file': + $taf_data = $this->get_taf_file($icao); + break; + case 'fsockopen': + default: + $taf_data = $this->get_taf_socket($icao); + break; + } + + /* Here we test to see if we actually got a TAF. */ + if (!empty($taf_data) && count($taf_data)>0) { + /* The first line in the file is the date */ + $date = trim(array_shift($taf_data)); + /* The remaining lines are the TAF itself. This will merge the + * remaining lines into one line by removing new-lines: + */ + $taf = ereg_replace("[\n\r ]+", ' ', trim(implode(' ', $taf_data))); + + $date = explode(':', strtr($date, '/ ', '::')); + if ($date[2] > gmdate('j')) { + /* The day is greater that the current day of month. This + * implies, that the report is from last month. + */ + $date[1]--; + } + $timestamp = gmmktime($date[3], $date[4], 0, + $date[1], $date[2], $date[0]); + + if (!ereg('[0-9]{6}Z', $taf)) { + /* Some reports don't even have a time-part, so we insert the + * current time. This might not be the time of the report, but + * it was broken anyway :-) + */ + $taf = gmdate('dHi', $timestamp) . 'Z ' . $taf; + } + + //ET here adjust because reports are updated less often than matars + //tmp always set the timeout to 1 hour from now + $timestamp = time() - $this->properties['cache_timeout'] + 60*60; + +// if ($timestamp < (time() - $this->properties['cache_timeout'] + 300)) { +// /* The timestamp in the TAF is more than 55 minutes old. We +// * adjust the timestamp, so that we won't try to fetch a new +// * TAF within the next 5 minutes. After 5 minutes, the +// * timestamp will again be more than 1 hour old. +// */ +// $timestamp = time() - $this->properties['cache_timeout'] + 300; +// } + + } + + else { + /* If we end up here, it means that there was no file. If the + * station was a new station, we set the taf to an empty + * string, else we just use the old TAF. We adjust the time + * stored in the database in both cases, so that the server + * isn't stressed too much. + */ + if ($new_station) { + $taf = ''; + } else { + $taf = $this->taf; + } + $timestamp = time() - $this->properties['cache_timeout'] + 600; + } + + /* We then cache the TAF in our database */ + if ($new_station) { + $this->debug('get_taf_from_web(): Inserting new TAF for <code>' . + $this->get_location() . '</code>'); + $this->db->insert_taf($icao, $taf, $timestamp); + } else { + $this->debug('get_taf_from_web(): Updating TAF for <code>' . + $this->get_location() . '</code>'); + $this->db->update_taf($icao, $taf, $timestamp); + } + /* We update and return the TAF */ + $this->taf = $taf; + return $taf; + } + + /** Index: defaults-dist.php =================================================================== RCS file: /cvsroot/phpweather/phpweather/defaults-dist.php,v retrieving revision 1.17 retrieving revision 1.18 diff -u -3 -r1.17 -r1.18 --- defaults-dist.php 20 Apr 2003 13:12:42 -0000 1.17 +++ defaults-dist.php 8 Sep 2003 04:20:44 -0000 1.18 @@ -35,6 +35,7 @@ $this->properties['db_port'] = ''; $this->properties['db_metars'] = 'metars'; /* pw_db_dba.php */ +$this->properties['db_tafs'] = 'tafs'; /* pw_db_dba.php */ $this->properties['db_stations'] = 'stations'; $this->properties['db_countries'] = 'countries'; Index: phpweather.php =================================================================== RCS file: /cvsroot/phpweather/phpweather/phpweather.php,v retrieving revision 1.33 retrieving revision 1.34 diff -u -3 -r1.33 -r1.34 --- phpweather.php 14 Jun 2003 23:34:59 -0000 1.33 +++ phpweather.php 8 Sep 2003 04:20:44 -0000 1.34 @@ -1,5 +1,5 @@ <?php - + /** * The base directory of the PHP Weather installation. * @@ -41,6 +41,13 @@ * @var array */ var $decoded_metar; + + /** + * The decoded TAF is stored here. + * + * @var array + */ + var $decoded_taf; /** * This constructor does nothing besides passing the input down the @@ -681,6 +688,327 @@ $this->decoded_metar = $decoded_metar; return $decoded_metar; } + + /** + * Decodes a raw TAF. + * + * This function loops over the various parts of the raw TAF, and + * stores the different bits in $decoded_taf. It uses get_taf() to + * retrieve the TAF, so it is not necessary to connect to the database + * before you call this function. + * + * @return array The decoded TAF. + * @see $decoded_taf + * @access public + */ + function decode_taf() { + + /* initialization */ + $temp_visibility_miles = ''; + $decoded_taf = array(); + $decoded_taf['remarks'] = ''; + $decoded_taf['taf'] = $this->get_taf(); + $decoded_taf['location'] = $this->get_location(); + + if($this->taf!='') $parts = explode(' ', $this->taf); + else $parts = false; + $num_parts = count($parts); + if($parts === false || $num_parts==0) { + $this->decoded_taf = false; + $this->debug('decode_taf, parts is empty'); + return false; + } + + $current_period = 0; + $periods = array(); + $periods[0] = 'COMPLETE'; + $remarks_index = $num_parts - 1; + + $decoded_taf['icao'] = $parts[0]; + $decoded_taf['time_emit'] = $parts[1]; + $decoded_taf['time_use'] = $parts[2]; + $periods[0] .= ' '.$parts[2]; + + /* first pass to get remarks and periods */ + for ($i = 3; $i < $num_parts; $i++) { + $part = $parts[$i]; + if($i<$num_parts-1) $part2 = $parts[$i+1]; + else $part2 = ''; + + if ($part=='RMK') { + /* The rest of the TAF is either a remark or temporary + * information. We keep the remark. + */ + $remarks_index = $i; + for($j=$i;$j<$num_parts; $j++) + $decoded_taf['remarks'] .= ' ' . $parts[$j]; + break; + } + else if ( (substr($part,0,2)=='FM') || $part=='BECMG' || + ($part=='TEMPO') || (substr($part,0,4)=='PROB') ) { + $current_period++; + $periods[$current_period] = $part; + } + else { + $periods[$current_period] .= ' '.$part; + } + + } + + $decoded_periods = array(); + + /* for each period, parse the data */ + if(count($periods)>0) { + for($j=0;$j<count($periods);$j++) { + $tmp_period = $periods[$j]; + $data_period = $tmp_period; + $parts = explode(' ', $tmp_period); + $num_parts = count($parts); + + $decoded_periods[$j] = array(); + $decoded_period = & $decoded_periods[$j]; + + $first_i = 1; + $time_from = $time_to = false; + $type = $prob = false; + if ( (substr($parts[0],0,2)=='FM') ) { + $type = 'FM'; + $time_from = substr($parts[0],2,4); + $time_to = false; + $first_i = 1; + } + else if ($parts[0]=='BECMG') { + $type = 'BECMG'; + $first_i = 2; + } + else if ($parts[0]=='TEMPO') { + $type = 'TEMPO'; + $time_from = substr($parts[1],0,2).'00'; + $time_to = substr($parts[1],2,2).'00'; + $first_i = 2; + } + else if (substr($parts[0],0,4)=='PROB') { + $type = 'PROB'; + $time_from = substr($parts[1],0,2).'00'; + $time_to = substr($parts[1],2,2).'00'; + $prob = intval(substr($parts[0],4)); + $first_i = 2; + } + else if($parts[0]=='COMPLETE'){ + $type = 'COMPLETE'; + $data_period = substr($data_period,9); + $time_from = substr($parts[1],2,2).'00'; + $time_to = substr($parts[1],4,2).'00'; + $first_i = 2; + } + else { + $first_i = $num_parts; + } + + $decoded_period['data'] = $data_period; + $decoded_period['type'] = $type; + $decoded_period['time_from'] = $time_from; + $decoded_period['time_to'] = $time_to; + $decoded_period['prob'] = $prob; + + /* pass each element of the period */ + for($i=$first_i;$i<$num_parts;$i++) { + $part = $parts[$i]; + + if (ereg('^([0-9]{3}|VRB)([0-9]{2,3})G?([0-9]{2,3})?(KT)', $part, $regs)) { + /* Wind Group */ + + $decoded_period['wind']['deg'] = $regs[1]; + $this->store_speed($regs[2], + $regs[4], + $decoded_period['wind']['knots'], + $decoded_period['wind']['meters_per_second'], + $decoded_period['wind']['miles_per_hour']); + + if (!empty($regs[3])) { + + /* We have a report with information about the gust. + * First we have the gust measured in knots. + */ + $this->store_speed($regs[3], + $regs[4], + $decoded_period['wind']['gust_knots'], + $decoded_period['wind']['gust_meters_per_second'], + $decoded_period['wind']['gust_miles_per_hour']); + } + } elseif (ereg('^([0-9]{3})V([0-9]{3})$', $part, $regs) && + !empty($decoded_period['wind']['deg'])) { + /* + * Variable wind-direction + */ + $decoded_period['wind']['var_beg'] = $regs[1]; + $decoded_period['wind']['var_end'] = $regs[2]; + } elseif (ereg('^([0-9]{4})([NS]?[EW]?)$', $part, $regs)) { + /* + * Visibility in meters (4 digits only) + */ + unset($group); + + if ($regs[1] == '0000') { + /* Special low value */ + + $group['prefix'] = -1; /* Less than */ + $group['meter'] = 50; + $group['km'] = 0.05; + $group['ft'] = 164; + $group['miles'] = 0.031; + } elseif ($regs[1] == '9999') { + /* Special high value */ + $group['prefix'] = 1; + $group['meter'] = 10000; + $group['km'] = 10; + $group['ft'] = 32800; + $group['miles'] = 6.2; + } else { + /* Normal visibility, returned in both small and large units. */ + $group['prefix'] = 0; + $group['km'] = number_format($regs[1]/1000, 1); + $group['miles'] = number_format($regs[1]/1609.344, 1); + $group['meter'] = $regs[1] * 1; + $group['ft'] = round($regs[1] * 3.28084); + } + if (!empty($regs[2])) { + $group['dir'] = $regs[2]; + } + $decoded_period['visibility'][] = $group; + + } elseif (ereg('^[0-9]$', $part)) { + /* + * Temp Visibility Group, single digit followed by space. + */ + $temp_visibility_miles = $part; + + } elseif ($part=='P6SM') { + unset($group); + $group['prefix'] = 1; + $vis_miles = 6; + $group['miles'] = number_format($vis_miles, 1); + $group['ft'] = round($vis_miles * 5280, 1); + $group['km'] = number_format($vis_miles * 1.6093, 1); + $group['meter'] = round($vis_miles * 1609.3); + $decoded_period['visibility'][] = $group; + + } elseif (ereg('^[M]?(([0-9]?)[ ]?([0-9])(/?)([0-9]*))SM$', + $temp_visibility_miles . ' ' . $part, $regs)) { + /* + * Visibility Group + */ + unset($group); + + if ($regs[4] == '/') { + $vis_miles = $regs[2] + $regs[3]/$regs[5]; + } else { + $vis_miles = $regs[1]; + } + if ($regs[0][0] == 'M') { + /* Prefix - less than */ + $group['prefix'] = -1; + } else { + $group['prefix'] = 0; + } + + /* The visibility measured in miles */ + $group['miles'] = number_format($vis_miles, 1); + + /* The visibility measured in feet */ + $group['ft'] = round($vis_miles * 5280, 1); + + /* The visibility measured in kilometers */ + $group['km'] = number_format($vis_miles * 1.6093, 1); + + /* The visibility measured in meters */ + $group['meter'] = round($vis_miles * 1609.3); + + $decoded_period['visibility'][] = $group; + + } elseif (ereg('^(VC)?' . /* Proximity */ + '(-|\+)?' . /* Intensity */ + '(MI|PR|BC|DR|BL|SH|TS|FZ)?' . /* Descriptor */ + '((DZ|RA|SN|SG|IC|PL|GR|GS|UP)+)?' . /* Precipitation */ + '(BR|FG|FU|VA|DU|SA|HZ|PY)?' . /* Obscuration */ + '(PO|SQ|FC|SS)?$', /* Other */ + $part, $regs)) { + /* + * Current weather-group. + */ + $decoded_period['weather'][] = + array('proximity' => $regs[1], + 'intensity' => $regs[2], + 'descriptor' => $regs[3], + 'precipitation' => $regs[4], + 'obscuration' => $regs[6], + 'other' => $regs[7]); + + } elseif ($part == 'SKC' || $part == 'CLR') { + /* Cloud-group */ + $decoded_period['clouds'][]['condition'] = $part; + + } elseif (ereg('^(VV|FEW|SCT|BKN|OVC)([0-9]{3}|///)' . + '(CB|TCU)?$', $part, $regs)) { + /* We have found (another) a cloud-layer-group. */ + unset($group); + + $group['condition'] = $regs[1]; + if (!empty($regs[3])) { + $group['cumulus'] = $regs[3]; + } + if ($regs[2] == '000') { + /* '000' is a special height. */ + $group['ft'] = 100; + $group['meter'] = 30; + $group['prefix'] = -1; /* Less than */ + } elseif ($regs[2] == '///') { + /* '///' means height nil */ + $group['ft'] = 'nil'; + $group['meter'] = 'nil'; + } else { + $group['ft'] = $regs[2] *100; + $group['meter'] = round($regs[2] * 30.48); + } + $decoded_period['clouds'][] = $group; + + } elseif (ereg('^WS([0-9]{3})/([0-9]{3})([0-9]{2})KT$', $part, $regs)) { + /* We have found a Wind Shear group. example WS011/27050KT */ + unset($ws); + if ($regs[1] == '000') { + /* '000' is a special height. */ + $ws['ft'] = 100; + $ws['meter'] = 30; + } elseif ($regs[1] == '///') { + /* '///' means height nil */ + $ws['ft'] = 'nil'; + $ws['meter'] = 'nil'; + } else { + $ws['ft'] = $regs[1] *100; + $ws['meter'] = round($regs[1] * 30.48); + } + $ws['wind']['dir'] = $regs[2]; + $this->store_speed($regs[3],'KT', + $ws['wind']['knots'], + $ws['wind']['meters_per_second'], + $ws['wind']['miles_per_hour']); + $decoded_period['wind_shear'][] = $ws; + } + } + } + $decoded_taf['periods'] = $decoded_periods; + $decoded_taf['remarks'] = trim($decoded_taf['remarks']); + } + + /* Finally we store our decoded TAF in $this->decoded_taf so + * that other methods can use it. + */ + + $this->decoded_taf = $decoded_taf; + return $decoded_taf; + + } + } ?> |