home *** CD-ROM | disk | FTP | other *** search
/ Enter 2004 June / ENTER.ISO / files / xampp-win32-1.4.5-installer.exe / xampp / Metar.php < prev    next >
Encoding:
PHP Script  |  2004-03-24  |  53.3 KB  |  1,170 lines

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP version 4                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2004 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.0 of the PHP license,       |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available through the world-wide-web at                              |
  11. // | http://www.php.net/license/2_02.txt.                                 |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Alexander Wirtz <alex@pc4p.net>                             |
  17. // +----------------------------------------------------------------------+
  18. //
  19. // $Id: Metar.php,v 1.36 2004/03/31 12:32:58 eru Exp $
  20.  
  21. require_once "Services/Weather/Common.php";
  22.  
  23. require_once "DB.php";
  24.  
  25. // {{{ class Services_Weather_Metar
  26. /**
  27. * PEAR::Services_Weather_Metar
  28. *
  29. * This class acts as an interface to the metar service of weather.noaa.gov. It searches for
  30. * locations given in ICAO notation and retrieves the current weather data.
  31. *
  32. * Of course the parsing of the METAR-data has its limitations, as it follows the
  33. * Federal Meteorological Handbook No.1 with modifications to accomodate for non-US reports,
  34. * so if the report deviates from these standards, you won't get it parsed correctly.
  35. * Anything that is not parsed, is saved in the "noparse" array-entry, returned by
  36. * getWeather(), so you can do your own parsing afterwards. This limitation is specifically
  37. * given for remarks, as the class is not processing everything mentioned there, but you will
  38. * get the most common fields like precipitation and temperature-changes. Again, everything
  39. * not parsed, goes into "noparse".
  40. *
  41. * If you think, some important field is missing or not correctly parsed, please file a feature-
  42. * request/bugreport at http://pear.php.net/ and be sure to provide the METAR report with a
  43. * _detailed_ explanation!
  44. *
  45. * For a working example, please take a look at
  46. *     docs/Services_Weather/examples/metar-basic.php
  47. *
  48. * @author       Alexander Wirtz <alex@pc4p.net>
  49. * @link         http://weather.noaa.gov/weather/metar.shtml
  50. * @example      docs/Services_Weather/examples/metar-basic.php
  51. * @package      Services_Weather
  52. * @license      http://www.php.net/license/2_02.txt
  53. * @version      1.2
  54. */
  55. class Services_Weather_Metar extends Services_Weather_Common
  56. {
  57.     // {{{ properties
  58.     /**
  59.     * Information to access the location DB
  60.     *
  61.     * @var      object  DB                  $_db
  62.     * @access   private
  63.     */
  64.     var $_db;
  65.     
  66.     /**
  67.     * The source METAR uses
  68.     *
  69.     * @var      string                      $_source
  70.     * @access   private
  71.     */
  72.     var $_source;
  73.  
  74.     /**
  75.     * This path is used to find the METAR data
  76.     *
  77.     * @var      string                      $_sourcePath
  78.     * @access   private
  79.     */
  80.     var $_sourcePath;
  81.     // }}}
  82.  
  83.     // {{{ constructor
  84.     /**
  85.     * Constructor
  86.     *
  87.     * @param    array                       $options
  88.     * @param    mixed                       $error
  89.     * @throws   PEAR_Error
  90.     * @see      Science_Weather::Science_Weather
  91.     * @access   private
  92.     */
  93.     function Services_Weather_Metar($options, &$error)
  94.     {
  95.         $perror = null;
  96.         $this->Services_Weather_Common($options, $perror);
  97.         if (Services_Weather::isError($perror)) {
  98.             $error = $perror;
  99.             return;
  100.         }
  101.         
  102.         // Set options accordingly        
  103.         if (isset($options["dsn"])) {
  104.             if (isset($options["dbOptions"])) {
  105.                 $status = $this->setMetarDB($options["dsn"], $options["dbOptions"]);
  106.             } else {
  107.                 $status = $this->setMetarDB($options["dsn"]);
  108.             }
  109.         }
  110.         if (Services_Weather::isError($status)) {
  111.             $error = $status;
  112.             return;
  113.         }
  114.         
  115.         if (isset($options["source"])) {
  116.             if (isset($options["sourcePath"])) {
  117.                 $this->setMetarSource($options["source"], $options["sourcePath"]);
  118.             } else {
  119.                 $this->setMetarSource($options["source"]);
  120.             }
  121.         } else {
  122.             $this->setMetarSource("http");
  123.         }
  124.     }
  125.     // }}}
  126.  
  127.     // {{{ setMetarDB()
  128.     /**
  129.     * Sets the parameters needed for connecting to the DB, where the location-
  130.     * search is fetching its data from. You need to build a DB with the external
  131.     * tool buildMetarDB first, it fetches the locations and airports from a
  132.     * NOAA-website.
  133.     *
  134.     * @param    string                      $dsn
  135.     * @param    array                       $dbOptions
  136.     * @return   DB_Error|bool
  137.     * @throws   DB_Error
  138.     * @see      DB::parseDSN
  139.     * @access   public
  140.     */
  141.     function setMetarDB($dsn, $dbOptions = array())
  142.     {
  143.         $dsninfo = DB::parseDSN($dsn);
  144.         if (is_array($dsninfo) && !isset($dsninfo["mode"])) {
  145.             $dsninfo["mode"]= 0644;
  146.         }
  147.         
  148.         // Initialize connection to DB and store in object if successful
  149.         $db =  DB::connect($dsninfo, $dbOptions);
  150.         if (DB::isError($db)) {
  151.             return $db;
  152.         }
  153.         $this->_db = $db;
  154.  
  155.         return true;
  156.     }
  157.     // }}}
  158.  
  159.     // {{{ setMetarSource()
  160.     /**
  161.     * Sets the source, where the class tries to locate the METAR data
  162.     *
  163.     * Source can be http, ftp or file.
  164.     * An alternate sourcepath can be provided.
  165.     *
  166.     * @param    string                      $source
  167.     * @param    string                      $sourcePath
  168.     * @access   public
  169.     */
  170.     function setMetarSource($source, $sourcePath = "")
  171.     {
  172.         if (in_array($source, array("http", "ftp", "file"))) {
  173.             $this->_source = $source;
  174.         }
  175.         if (strlen($sourcePath)) {
  176.             $this->_sourcePath = $sourcePath;
  177.         } else {
  178.             switch ($source) {
  179.                 case "http":
  180.                     $this->_sourcePath = "http://weather.noaa.gov/pub/data/observations/metar/stations/";
  181.                     break;
  182.                 case "ftp":
  183.                     $this->_sourcePath = "ftp://weather.noaa.gov/data/observations/metar/stations/";
  184.                     break;
  185.                 case "file":
  186.                     $this->_sourcePath = "./";
  187.                     break;
  188.             }
  189.         }
  190.     }
  191.     // }}}
  192.  
  193.     // {{{ _checkLocationID()
  194.     /**
  195.     * Checks the id for valid values and thus prevents silly requests to METAR server
  196.     *
  197.     * @param    string                      $id
  198.     * @return   PEAR_Error|bool
  199.     * @throws   PEAR_Error::SERVICES_WEATHER_ERROR_NO_LOCATION
  200.     * @throws   PEAR_Error::SERVICES_WEATHER_ERROR_INVALID_LOCATION
  201.     * @access   private
  202.     */
  203.     function _checkLocationID($id)
  204.     {
  205.         if (!strlen($id)) {
  206.             return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_NO_LOCATION);
  207.         } elseif (!ctype_alpha($id) || (strlen($id) > 4)) {
  208.             return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_INVALID_LOCATION);
  209.         }
  210.  
  211.         return true;
  212.     }
  213.     // }}}
  214.  
  215.     // {{{ _parseWeatherData()
  216.     /**
  217.     * Parses the data returned by the provided source and caches it
  218.     *    
  219.     * METAR KPIT 091955Z COR 22015G25KT 3/4SM R28L/2600FT TSRA OVC010CB 18/16 A2992 RMK SLP045 T01820159
  220.     *
  221.     * @param    string                      $source
  222.     * @return   PEAR_Error|array
  223.     * @throws   PEAR_Error::SERVICES_WEATHER_ERROR_WRONG_SERVER_DATA
  224.     * @throws   PEAR_Error::SERVICES_WEATHER_ERROR_UNKNOWN_LOCATION
  225.     * @access   private
  226.     */
  227.     function _parseWeatherData($source)
  228.     {
  229.         static $compass;
  230.         static $clouds;
  231.         static $conditions;
  232.         static $sensors;
  233.         if (!isset($compass)) {
  234.             $compass = array(
  235.                 "N", "NNE", "NE", "ENE",
  236.                 "E", "ESE", "SE", "SSE",
  237.                 "S", "SSW", "SW", "WSW",
  238.                 "W", "WNW", "NW", "NNW"
  239.             );
  240.             $clouds    = array(
  241.                 "skc"         => "sky clear",
  242.                 "few"         => "few",
  243.                 "sct"         => "scattered",
  244.                 "bkn"         => "broken",
  245.                 "ovc"         => "overcast",
  246.                 "vv"          => "vertical visibility",
  247.                 "tcu"         => "Towering Cumulus",
  248.                 "cb"          => "Cumulonimbus",
  249.                 "clr"         => "clear below 12,000 ft"
  250.             );
  251.             $conditions = array(
  252.                 "+"           => "heavy",        "-"           => "light",
  253.  
  254.                 "vc"          => "vicinity",
  255.  
  256.                 "mi"          => "shallow",      "bc"          => "patches",
  257.                 "pr"          => "partial",      "ts"          => "thunderstorm",
  258.                 "bl"          => "blowing",      "sh"          => "showers",
  259.                 "dr"          => "low drifting", "fz"          => "freezing",
  260.  
  261.                 "dz"          => "drizzle",      "ra"          => "rain",
  262.                 "sn"          => "snow",         "sg"          => "snow grains",
  263.                 "ic"          => "ice crystals", "pe"          => "ice pellets",
  264.                 "gr"          => "hail",         "gs"          => "small hail/snow pellets",
  265.                 "up"          => "unknown precipitation",
  266.  
  267.                 "br"          => "mist",         "fg"          => "fog",
  268.                 "fu"          => "smoke",        "va"          => "volcanic ash",
  269.                 "sa"          => "sand",         "hz"          => "haze",
  270.                 "py"          => "spray",        "du"          => "widespread dust",
  271.  
  272.                 "sq"          => "squall",       "ss"          => "sandstorm",
  273.                 "ds"          => "duststorm",    "po"          => "well developed dust/sand whirls",
  274.                 "fc"          => "funnel cloud",
  275.  
  276.                 "+fc"         => "tornado/waterspout"
  277.             );
  278.             $sensors = array(
  279.                 "rvrno"     => "Runway Visual Range Detector offline",
  280.                 "pwino"     => "Present Weather Identifier offline",
  281.                 "pno"       => "Tipping Bucket Rain Gauge offline",
  282.                 "fzrano"    => "Freezing Rain Sensor offline",
  283.                 "tsno"      => "Lightning Detection System offline",
  284.                 "visno_loc" => "2nd Visibility Sensor offline",
  285.                 "chino_loc" => "2nd Ceiling Height Indicator offline"
  286.             );
  287.         }
  288.  
  289.         $metarCode = array(
  290.             "report"      => "METAR|SPECI",
  291.             "station"     => "\w{4}",
  292.             "update"      => "(\d{2})?(\d{4})Z",
  293.             "type"        => "AUTO|COR",
  294.             "wind"        => "(\d{3}|VAR|VRB)(\d{2,3})(G(\d{2}))?(\w{2,3})",
  295.             "windVar"     => "(\d{3})V(\d{3})",
  296.             "visibility1" => "\d",
  297.             "visibility2" => "M?(\d{4})|((\d{1,2}|(\d)\/(\d))(SM|KM))|(CAVOK)",
  298.             "runway"      => "R(\d{2})(\w)?\/(P|M)?(\d{4})(FT)?(V(P|M)?(\d{4})(FT)?)?(\w)?",
  299.             "condition"   => "(-|\+|VC)?(MI|BC|PR|TS|BL|SH|DR|FZ)?(DZ|RA|SN|SG|IC|PL|GR|GS|UP)?(BR|FG|FU|VA|DU|SA|HZ|PY)?(PO|SQ|FC|SS|DS)?",
  300.             "clouds"      => "(SKC|CLR|((FEW|SCT|BKN|OVC|VV)(\d{3})(TCU|CB)?))",
  301.             "temperature" => "(M)?(\d{2})\/((M)?(\d{2})|XX|\/\/)?",
  302.             "pressure"    => "(A)(\d{4})|(Q)(\d{4})",
  303.             "nosig"       => "NOSIG",
  304.             "remark"      => "RMK"
  305.         );
  306.         
  307.         $remarks = array(
  308.             "nospeci"     => "NOSPECI",
  309.             "autostation" => "AO(1|2)",
  310.             "presschg"    => "PRESS(R|F)R",
  311.             "seapressure" => "SLP(\d{3}|NO)",
  312.             "1hprecip"    => "P(\d{4})",
  313.             "6hprecip"    => "6(\d{4}|\/{4})",
  314.             "24hprecip"   => "7(\d{4}|\/{4})",
  315.             "snowdepth"   => "4\/(\d{3})",
  316.             "snowequiv"   => "933(\d{3})",
  317.             "cloudtypes"  => "8\/(\d|\/)(\d|\/)(\d|\/)",
  318.             "sunduration" => "98(\d{3})",
  319.             "1htempdew"   => "T(0|1)(\d{3})((0|1)(\d{3}))?",
  320.             "6hmaxtemp"   => "1(0|1)(\d{3})",
  321.             "6hmintemp"   => "2(0|1)(\d{3})",
  322.             "24htemp"     => "4(0|1)(\d{3})(0|1)(\d{3})",
  323.             "3hpresstend" => "5([0-8])(\d{3})",
  324.             "sensors"     => "RVRNO|PWINO|PNO|FZRANO|TSNO|VISNO_LOC|CHINO_LOC",
  325.             "maintain"    => "[\$]"
  326.         );        
  327.  
  328.         $data = @file($source);
  329.  
  330.         // Check for correct data, 2 lines in size
  331.         if (!$data || !is_array($data) || sizeof($data) < 2) {
  332.             return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_WRONG_SERVER_DATA);
  333.         } elseif (sizeof($data) > 2) {
  334.             return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_UNKNOWN_LOCATION);
  335.         } else {
  336.             if (SERVICES_WEATHER_DEBUG) {
  337.                 echo $data[0].$data[1];
  338.             }
  339.             // Ok, we have correct data, start with parsing the first line for the last update
  340.             $weatherData = array();
  341.             $weatherData["station"]   = "";
  342.             $weatherData["update"]    = strtotime(trim($data[0])." GMT");
  343.             $weatherData["updateRaw"] = $data[0];
  344.             // and prepare the second line for stepping through
  345.             $metar = explode(" ", trim($data[1]));
  346.  
  347.             for ($i = 0; $i < sizeof($metar); $i++) {
  348.                 // Check for whitespace and step loop, if nothing's there
  349.                 $metar[$i] = trim($metar[$i]);
  350.                 if (!strlen($metar[$i])) {
  351.                     continue;
  352.                 }
  353.  
  354.                 if (SERVICES_WEATHER_DEBUG) {
  355.                     $tab = str_repeat("\t", 2 - floor((strlen($metar[$i]) + 2) / 8));
  356.                     echo "\"".$metar[$i]."\"".$tab."-> ";
  357.                 }
  358.  
  359.                 $found = false;
  360.                 foreach ($metarCode as $key => $regexp) {
  361.                     // Check if current code matches current metar snippet
  362.                     if (($found = preg_match("/^".$regexp."$/i", $metar[$i], $result)) == true) {
  363.                         switch ($key) {
  364.                             case "station":
  365.                                 $weatherData["station"] = $result[0];
  366.                                 unset($metarCode["station"]);
  367.                                 break;
  368.                             case "wind":
  369.                                 // Parse wind data, first the speed, convert from kt to chosen unit
  370.                                 $weatherData["wind"] = $this->convertSpeed($result[2], strtolower($result[5]), "mph");
  371.                                 if ($result[1] == "VAR" || $result[1] == "VRB") {
  372.                                     // Variable winds
  373.                                     $weatherData["windDegrees"]   = "Variable";
  374.                                     $weatherData["windDirection"] = "Variable";
  375.                                 } else {
  376.                                     // Save wind degree and calc direction
  377.                                     $weatherData["windDegrees"]   = $result[1];
  378.                                     $weatherData["windDirection"] = $compass[round($result[1] / 22.5) % 16];
  379.                                 }
  380.                                 if (is_numeric($result[4])) {
  381.                                     // Wind with gusts...
  382.                                     $weatherData["windGust"] = $this->convertSpeed($result[4], strtolower($result[5]), "mph");
  383.                                 }
  384.                                 // We got that, unset
  385.                                 unset($metarCode["wind"]);
  386.                                 break;
  387.                             case "windVar":
  388.                                 // Once more wind, now variability around the current wind-direction
  389.                                 $weatherData["windVariability"] = array("from" => $result[1], "to" => $result[2]);
  390.                                 unset($metarCode["windVar"]);
  391.                                 break;
  392.                             case "visibility1":
  393.                                 // Visibility will come as x y/z, first the single digit part
  394.                                 $weatherData["visibility"] = $result[0];
  395.                                 unset($metarCode["visibility1"]);
  396.                                 break;
  397.                             case "visibility2":
  398.                                 if (is_numeric($result[1]) && ($result[1] == 9999)) {
  399.                                     // Upper limit of visibility range
  400.                                     $visibility = $this->convertDistance(10, "km", "sm");
  401.                                     $weatherData["visQualifier"] = "BEYOND";
  402.                                 } elseif (is_numeric($result[1])) {
  403.                                     // 4-digit visibility in m
  404.                                     $visibility = $this->convertDistance(($result[1]/1000), "km", "sm");
  405.                                     $weatherData["visQualifier"] = "AT";
  406.                                 } elseif (!isset($result[7]) || $result[7] != "CAVOK") {
  407.                                     if (is_numeric($result[3])) {
  408.                                         // visibility as one/two-digit number
  409.                                         $visibility = $this->convertDistance($result[3], $result[6], "sm");
  410.                                         $weatherData["visQualifier"] = "AT";
  411.                                     } else {
  412.                                         // the y/z part, add if we had a x part (see visibility1)
  413.                                         $visibility = $this->convertDistance($result[4] / $result[5], $result[6], "sm");
  414.                                         if (isset($weatherData["visibility"])) {
  415.                                             $visibility += $weatherData["visibility"];
  416.                                         }
  417.                                         if ($result[0]{0} == "M") {
  418.                                             $weatherData["visQualifier"] = "BELOW";
  419.                                         } else {
  420.                                             $weatherData["visQualifier"] = "AT";
  421.                                         } 
  422.                                     }
  423.                                 } else {
  424.                                     $weatherData["visQualifier"] = "BEYOND";
  425.                                     $visibility               = $this->convertDistance(10, "km", "sm");
  426.                                     $weatherData["clouds"]    = array("amount" => "none", "height" => "below 5000ft");
  427.                                     $weatherData["condition"] = "no significant weather";
  428.                                 }
  429.                                 $weatherData["visibility"] = $visibility;
  430.                                 unset($metarCode["visibility2"]);
  431.                                 break;
  432.                             case "condition":
  433.                                 // First some basic setups
  434.                                 if (!isset($weatherData["condition"])) {
  435.                                     $weatherData["condition"] = "";
  436.                                 } elseif (strlen($weatherData["condition"]) > 0) {
  437.                                     $weatherData["condition"] .= ",";
  438.                                 }
  439.  
  440.                                 if (in_array(strtolower($result[0]), $conditions)) {
  441.                                     // First try matching the complete string
  442.                                     $weatherData["condition"] .= " ".$conditions[strtolower($result[0])];
  443.                                 } else {
  444.                                     // No luck, match part by part
  445.                                     for ($c = 1; $c < sizeof($result); $c++) {
  446.                                         if (strlen($result[$c]) > 0) {
  447.                                             $weatherData["condition"] .= " ".$conditions[strtolower($result[$c])];
  448.                                         }
  449.                                     }
  450.                                 }
  451.                                 $weatherData["condition"] = trim($weatherData["condition"]);
  452.                                 break;
  453.                             case "clouds":
  454.                                 if (!isset($weatherData["clouds"])) {
  455.                                     $weatherData["clouds"] = array();
  456.                                 }
  457.  
  458.                                 if (sizeof($result) == 5) {
  459.                                     // Only amount and height
  460.                                     $cloud = array("amount" => $clouds[strtolower($result[3])], "height" => ($result[4]*100));
  461.                                 }
  462.                                 elseif (sizeof($result) == 6) {
  463.                                     // Amount, height and type
  464.                                     $cloud = array("amount" => $clouds[strtolower($result[3])], "height" => ($result[4]*100), "type" => $clouds[strtolower($result[5])]);
  465.                                 }
  466.                                 else {
  467.                                     // SKC or CLR
  468.                                     $cloud = array("amount" => $clouds[strtolower($result[0])]);
  469.                                 }
  470.                                 $weatherData["clouds"][] = $cloud;
  471.                                 break;
  472.                             case "temperature":
  473.                                 // normal temperature in first part
  474.                                 // negative value
  475.                                 if ($result[1] == "M") {
  476.                                     $result[2] *= -1;
  477.                                 }
  478.                                 $weatherData["temperature"] = $this->convertTemperature($result[2], "c", "f");
  479.                                 if (sizeof($result) > 4) {
  480.                                     // same for dewpoint
  481.                                     if ($result[4] == "M") {
  482.                                         $result[5] *= -1;
  483.                                     }
  484.                                     $weatherData["dewPoint"] = $this->convertTemperature($result[5], "c", "f");
  485.                                     $weatherData["humidity"] = $this->calculateHumidity($result[2], $result[5]);
  486.                                 }
  487.                                 if (isset($weatherData["wind"])) {
  488.                                     // Now calculate windchill from temperature and windspeed
  489.                                     $weatherData["feltTemperature"] = $this->calculateWindChill($weatherData["temperature"], $weatherData["wind"]);
  490.                                 }
  491.                                 unset($metarCode["temperature"]);
  492.                                 break;
  493.                             case "pressure":
  494.                                 if ($result[1] == "A") {
  495.                                     // Pressure provided in inches
  496.                                     $weatherData["pressure"] = $result[2] / 100;
  497.                                 } elseif ($result[3] == "Q") {
  498.                                     // ... in hectopascal
  499.                                     $weatherData["pressure"] = $this->convertPressure($result[4], "hpa", "in");
  500.                                 }
  501.                                 unset($metarCode["pressure"]);
  502.                                 break;
  503.                             case "nosig":
  504.                             case "nospeci":
  505.                                 // No change during the last hour
  506.                                 if (!isset($weatherData["remark"])) {
  507.                                     $weatherData["remark"] = array();
  508.                                 }
  509.                                 $weatherData["remark"]["nosig"] = "No changes in weather conditions";
  510.                                 unset($metarCode[$key]);
  511.                                 break;
  512.                             case "remark":
  513.                                 // Remark part begins
  514.                                 $metarCode = $remarks;
  515.                                 if (!isset($weatherData["remark"])) {
  516.                                     $weatherData["remark"] = array();
  517.                                 }
  518.                                 break;
  519.                             case "autostation":
  520.                                 // Which autostation do we have here?
  521.                                 if ($result[1] == 0) {
  522.                                     $weatherData["remark"]["autostation"] = "Automatic weatherstation w/o precipitation discriminator";
  523.                                 } else {
  524.                                     $weatherData["remark"]["autostation"] = "Automatic weatherstation w/ precipitation discriminator";
  525.                                 }
  526.                                 unset($metarCode["autostation"]);
  527.                                 break;
  528.                             case "presschg":
  529.                                 // Decoding for rapid pressure changes
  530.                                 if (strtolower($result[1]) == "r") {
  531.                                     $weatherData["remark"]["presschg"] = "Pressure rising rapidly";
  532.                                 } else {
  533.                                     $weatherData["remark"]["presschg"] = "Pressure falling rapidly";
  534.                                 }
  535.                                 unset($metarCode["presschg"]);
  536.                                 break;
  537.                             case "seapressure":
  538.                                 // Pressure at sea level (delivered in hpa)
  539.                                 // Decoding is a bit obscure as 982 gets 998.2
  540.                                 // whereas 113 becomes 1113 -> no real rule here
  541.                                 if (strtolower($result[1]) != "no") {
  542.                                     if ($result[1] > 500) {
  543.                                         $press = 900 + round($result[1] / 100, 1);
  544.                                     } else {
  545.                                         $press = 1000 + $result[1];
  546.                                     }
  547.                                     $weatherData["remark"]["seapressure"] = $this->convertPressure($press, "hpa", "in");
  548.                                 }
  549.                                 unset($metarCode["seapressure"]);
  550.                                 break;
  551.                             case "1hprecip":
  552.                                 // Precipitation for the last hour in inches
  553.                                 if (!isset($weatherData["precipitation"])) {
  554.                                     $weatherData["precipitation"] = array();
  555.                                 }
  556.                                 if (!is_numeric($result[1])) {
  557.                                     $precip = "indeterminable";
  558.                                 } elseif ($result[1] == "0000") {
  559.                                     $precip = "traceable";
  560.                                 }else {
  561.                                     $precip = $result[1] / 100;
  562.                                 }
  563.                                 $weatherData["precipitation"][] = array(
  564.                                     "amount" => $precip,
  565.                                     "hours"  => "1" 
  566.                                 );
  567.                                 unset($metarCode["1hprecip"]);
  568.                                 break;
  569.                             case "6hprecip":
  570.                                 // Same for last 3 resp. 6 hours... no way to determine
  571.                                 // which report this is, so keeping the text general
  572.                                 if (!isset($weatherData["precipitation"])) {
  573.                                     $weatherData["precipitation"] = array();
  574.                                 }
  575.                                 if (!is_numeric($result[1])) {
  576.                                     $precip = "indeterminable";
  577.                                 } elseif ($result[1] == "0000") {
  578.                                     $precip = "traceable";
  579.                                 }else {
  580.                                     $precip = $result[1] / 100;
  581.                                 }
  582.                                 $weatherData["precipitation"][] = array(
  583.                                     "amount" => $precip,
  584.                                     "hours"  => "3/6" 
  585.                                 );
  586.                                 unset($metarCode["6hprecip"]);
  587.                                 break;
  588.                             case "24hprecip":
  589.                                 // And the same for the last 24 hours
  590.                                 if (!isset($weatherData["precipitation"])) {
  591.                                     $weatherData["precipitation"] = array();
  592.                                 }
  593.                                 if (!is_numeric($result[1])) {
  594.                                     $precip = "indeterminable";
  595.                                 } elseif ($result[1] == "0000") {
  596.                                     $precip = "traceable";
  597.                                 }else {
  598.                                     $precip = $result[1] / 100;
  599.                                 }
  600.                                 $weatherData["precipitation"][] = array(
  601.                                     "amount" => $precip,
  602.                                     "hours"  => "24" 
  603.                                 );
  604.                                 unset($metarCode["24hprecip"]);
  605.                                 break;
  606.                             case "snowdepth":
  607.                                 // Snow depth in inches
  608.                                 $weatherData["remark"]["snowdepth"] = $result[1];
  609.                                 unset($metarCode["snowdepth"]);
  610.                                 break;
  611.                             case "snowequiv":
  612.                                 // Same for equivalent in Water... (inches)
  613.                                 $weatherData["remark"]["snowequiv"] = $result[1] / 10;
  614.                                 unset($metarCode["snowequiv"]);
  615.                                 break;
  616.                             case "cloudtypes":
  617.                                 // Cloud types, haven't found a way for decent decoding (yet)
  618.                                 unset($metarCode["cloudtypes"]);
  619.                                 break;
  620.                             case "sunduration":
  621.                                 // Duration of sunshine (in minutes)
  622.                                 $weatherData["remark"]["sunduration"] = "Total minutes of sunshine: ".$result[1];
  623.                                 unset($metarCode["sunduration"]);
  624.                                 break;
  625.                             case "1htempdew":
  626.                                 // Temperatures in the last hour in C
  627.                                 if ($result[1] == "1") {
  628.                                     $result[2] *= -1;
  629.                                 }
  630.                                 $weatherData["remark"]["1htemp"] = $this->convertTemperature($result[2] / 10, "c", "f");
  631.                                 
  632.                                 if (sizeof($result) > 3) {
  633.                                     // same for dewpoint
  634.                                     if ($result[4] == "1") {
  635.                                         $result[5] *= -1;
  636.                                     }
  637.                                     $weatherData["remark"]["1hdew"] = $this->convertTemperature($result[5] / 10, "c", "f");
  638.                                 }
  639.                                 unset($metarCode["1htempdew"]);
  640.                                 break;
  641.                             case "6hmaxtemp":
  642.                                 // Max temperature in the last 6 hours in C
  643.                                 if ($result[1] == "1") {
  644.                                     $result[2] *= -1;
  645.                                 }
  646.                                 $weatherData["remark"]["6hmaxtemp"] = $this->convertTemperature($result[2] / 10, "c", "f");
  647.                                 unset($metarCode["6hmaxtemp"]);
  648.                                 break;
  649.                             case "6hmintemp":
  650.                                 // Min temperature in the last 6 hours in C
  651.                                 if ($result[1] == "1") {
  652.                                     $result[2] *= -1;
  653.                                 }
  654.                                 $weatherData["remark"]["6hmintemp"] = $this->convertTemperature($result[2] / 10, "c", "f");
  655.                                 unset($metarCode["6hmintemp"]);
  656.                                 break;
  657.                             case "24htemp":
  658.                                 // Max/Min temperatures in the last 24 hours in C
  659.                                 if ($result[1] == "1") {
  660.                                     $result[2] *= -1;
  661.                                 }
  662.                                 $weatherData["remark"]["24hmaxtemp"] = $this->convertTemperature($result[2] / 10, "c", "f");
  663.  
  664.                                 if ($result[3] == "1") {
  665.                                     $result[4] *= -1;
  666.                                 }
  667.                                 $weatherData["remark"]["24hmintemp"] = $this->convertTemperature($result[4] / 10, "c", "f");
  668.                                 unset($metarCode["24htemp"]);
  669.                                 break;
  670.                             case "3hpresstend":
  671.                                 // We don't save the pressure during the day, so no decoding
  672.                                 // possible, sorry
  673.                                 unset($metarCode["3hpresstend"]);
  674.                                 break;
  675.                             case "sensors":
  676.                                 // We may have multiple broken sensors, so do not unset
  677.                                 if (!isset($weatherData["remark"]["sensors"])) {
  678.                                     $weatherData["remark"]["sensors"] = array();
  679.                                 }
  680.                                 $weatherData["remark"]["sensors"][strtolower($result[0])] = $sensors[strtolower($result[0])];
  681.                                 break;
  682.                             case "maintain":
  683.                                 $weatherData["remark"]["maintain"] = "Maintainance needed";
  684.                                 unset($metarCode["maintain"]);
  685.                                 break;
  686.                             default:
  687.                                 // Do nothing, just prevent further matching
  688.                                 unset($metarCode[$key]);
  689.                                 break;
  690.                         }
  691.                         if (SERVICES_WEATHER_DEBUG) {
  692.                             echo $key."\n";
  693.                         }
  694.                         break;
  695.                     }
  696.                 }
  697.                 if (!$found) {
  698.                     if (SERVICES_WEATHER_DEBUG) {
  699.                         echo "n/a\n";
  700.                     }
  701.                     if (!isset($weatherData["noparse"])) {
  702.                         $weatherData["noparse"] = array();
  703.                     }
  704.                     $weatherData["noparse"][] = $metar[$i];
  705.                 }
  706.             }
  707.         }
  708.         if (isset($weatherData["noparse"])) {
  709.             $weatherData["noparse"] = implode(" ",  $weatherData["noparse"]);
  710.         }
  711.  
  712.         return $weatherData;
  713.     }
  714.     // }}}
  715.  
  716.     // {{{ searchLocation()
  717.     /**
  718.     * Searches IDs for given location, returns array of possible locations or single ID
  719.     *
  720.     * @param    string|array                $location
  721.     * @param    bool                        $useFirst       If set, first ID of result-array is returned
  722.     * @return   PEAR_Error|array|string
  723.     * @throws   PEAR_Error::SERVICES_WEATHER_ERROR_UNKNOWN_LOCATION
  724.     * @throws   PEAR_Error::SERVICES_WEATHER_ERROR_DB_NOT_CONNECTED
  725.     * @throws   PEAR_Error::SERVICES_WEATHER_ERROR_INVALID_LOCATION
  726.     * @access   public
  727.     */
  728.     function searchLocation($location, $useFirst = false)
  729.     {
  730.         if (!isset($this->_db) || !DB::isConnection($this->_db)) {
  731.             return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_DB_NOT_CONNECTED);
  732.         }
  733.         
  734.         if (is_string($location)) {
  735.             // Try to part search string in name, state and country part
  736.             // and build where clause from it for the select
  737.             $location = explode(",", $location);
  738.             if (sizeof($location) >= 1) {
  739.                 $where  = "LOWER(name) LIKE '%".strtolower(trim($location[0]))."%'";
  740.             }
  741.             if (sizeof($location) == 2) {
  742.                 $where .= " AND LOWER(country) LIKE '%".strtolower(trim($location[1]))."%'";
  743.             } elseif (sizeof($location) == 3) {
  744.                 $where .= " AND LOWER(state) LIKE '%".strtolower(trim($location[1]))."%'";
  745.                 $where .= " AND LOWER(country) LIKE '%".strtolower(trim($location[2]))."%'";
  746.             }
  747.                 
  748.             // Create select, locations with ICAO first
  749.             $select = "SELECT icao, name, state, country, latitude, longitude ".
  750.                       "FROM metarLocations ".
  751.                       "WHERE ".$where." ".
  752.                       "ORDER BY icao DESC";
  753.             $result = $this->_db->query($select);
  754.             // Check result for validity
  755.             if (DB::isError($result)) {
  756.                 return $result;
  757.             } elseif (strtolower(get_class($result)) != "db_result" || $result->numRows() == 0) {
  758.                 return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_UNKNOWN_LOCATION);
  759.             }
  760.             
  761.             // Result is valid, start preparing the return
  762.             $icao = array();
  763.             while (($row = $result->fetchRow(DB_FETCHMODE_ASSOC)) != null) {
  764.                 $locicao = $row["icao"];
  765.                 // First the name of the location
  766.                 if (!strlen($row["state"])) {
  767.                     $locname = $row["name"].", ".$row["country"];
  768.                 } else {
  769.                     $locname = $row["name"].", ".$row["state"].", ".$row["country"];
  770.                 }
  771.                 if ($locicao != "----") {
  772.                     // We have a location with ICAO
  773.                     $icao[$locicao] = $locname;
  774.                 } else {
  775.                     // No ICAO, try finding the nearest airport
  776.                     $locicao = $this->searchAirport($row["latitude"], $row["longitude"]);
  777.                     if (!isset($icao[$locicao])) {
  778.                         $icao[$locicao] = $locname;
  779.                     }
  780.                 }
  781.             }
  782.             // Only one result? Return as string
  783.             if (sizeof($icao) == 1) {
  784.                 $icao = key($icao);
  785.             }
  786.         } elseif (is_array($location)) {
  787.             // Location was provided as coordinates, search nearest airport
  788.             $icao = $this->searchAirport($location[0], $location[1]);
  789.         } else {
  790.             return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_INVALID_LOCATION);
  791.         }
  792.  
  793.         return $icao;
  794.     }
  795.     // }}}
  796.  
  797.     // {{{ searchLocationByCountry()
  798.     /**
  799.     * Returns IDs with location-name for a given country or all available countries, if no value was given 
  800.     *
  801.     * @param    string                      $country
  802.     * @return   PEAR_Error|array
  803.     * @throws   PEAR_Error::SERVICES_WEATHER_ERROR_UNKNOWN_LOCATION
  804.     * @throws   PEAR_Error::SERVICES_WEATHER_ERROR_DB_NOT_CONNECTED
  805.     * @throws   PEAR_Error::SERVICES_WEATHER_ERROR_WRONG_SERVER_DATA
  806.     * @access   public
  807.     */
  808.     function searchLocationByCountry($country = "")
  809.     {
  810.         if (!isset($this->_db) || !DB::isConnection($this->_db)) {
  811.             return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_DB_NOT_CONNECTED);
  812.         }
  813.  
  814.         // Return the available countries as no country was given
  815.         if (!strlen($country)) {
  816.             $select = "SELECT DISTINCT(country) ".
  817.                       "FROM metarAirports ".
  818.                       "ORDER BY country ASC";
  819.             $countries = $this->_db->getCol($select);
  820.  
  821.             // As $countries is either an error or the true result,
  822.             // we can just return it
  823.             return $countries;
  824.         }
  825.  
  826.         // Now for the real search
  827.         $select = "SELECT icao, name, state, country ".
  828.                   "FROM metarAirports ".
  829.                   "WHERE LOWER(country) LIKE '%".strtolower(trim($country))."%' ".
  830.                   "ORDER BY name ASC";
  831.         $result = $this->_db->query($select);
  832.         // Check result for validity
  833.         if (DB::isError($result)) {
  834.             return $result;
  835.         } elseif (strtolower(get_class($result)) != "db_result" || $result->numRows() == 0) {
  836.             return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_UNKNOWN_LOCATION);
  837.         }
  838.  
  839.         // Construct the result
  840.         $locations = array();
  841.         while (($row = $result->fetchRow(DB_FETCHMODE_ASSOC)) != null) {
  842.             $locicao = $row["icao"];
  843.             // First the name of the location
  844.             if (!strlen($row["state"])) {
  845.                 $locname = $row["name"].", ".$row["country"];
  846.             } else {
  847.                 $locname = $row["name"].", ".$row["state"].", ".$row["country"];
  848.             }
  849.             $locations[$locicao] = $locname;
  850.         }
  851.  
  852.         return $locations;
  853.     }
  854.     // }}}
  855.  
  856.     // {{{ searchAirport()
  857.     /**
  858.     * Searches the nearest airport(s) for given coordinates, returns array of IDs or single ID
  859.     *
  860.     * @param    float                       $latitude
  861.     * @param    float                       $longitude
  862.     * @param    int                         $numResults
  863.     * @return   PEAR_Error|array|string
  864.     * @throws   PEAR_Error::SERVICES_WEATHER_ERROR_UNKNOWN_LOCATION
  865.     * @throws   PEAR_Error::SERVICES_WEATHER_ERROR_DB_NOT_CONNECTED
  866.     * @throws   PEAR_Error::SERVICES_WEATHER_ERROR_INVALID_LOCATION
  867.     * @access   public
  868.     */
  869.     function searchAirport($latitude, $longitude, $numResults = 1)
  870.     {
  871.         if (!isset($this->_db) || !DB::isConnection($this->_db)) {
  872.             return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_DB_NOT_CONNECTED);
  873.         }
  874.         if (!is_numeric($latitude) || !is_numeric($longitude)) {
  875.             return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_INVALID_LOCATION);
  876.         }           
  877.         
  878.         // Get all airports
  879.         $select = "SELECT icao, x, y, z FROM metarAirports";
  880.         $result = $this->_db->query($select);
  881.         if (DB::isError($result)) {
  882.             return $result;
  883.         } elseif (strtolower(get_class($result)) != "db_result" || $result->numRows() == 0) {
  884.             return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_UNKNOWN_LOCATION);
  885.         }
  886.  
  887.         // Result is valid, start search
  888.         // Initialize values
  889.         $min_dist = null;
  890.         $query    = $this->polar2cartesian($latitude, $longitude);
  891.         $search   = array("dist" => array(), "icao" => array());
  892.         while (($row = $result->fetchRow(DB_FETCHMODE_ASSOC)) != null) {
  893.             $icao = $row["icao"];
  894.             $air  = array($row["x"], $row["y"], $row["z"]);
  895.  
  896.             $dist = 0;
  897.             $d = 0;
  898.             // Calculate distance of query and current airport
  899.             // break off, if distance is larger than current $min_dist
  900.             for($d; $d < sizeof($air); $d++) {
  901.                 $t = $air[$d] - $query[$d];
  902.                 $dist += pow($t, 2);
  903.                 if ($min_dist != null && $dist > $min_dist) {
  904.                     break;
  905.                 }
  906.             }
  907.  
  908.             if ($d >= sizeof($air)) {
  909.                 // Ok, current airport is one of the nearer locations
  910.                 // add to result-array
  911.                 $search["dist"][] = $dist;
  912.                 $search["icao"][] = $icao;
  913.                 // Sort array for distance
  914.                 array_multisort($search["dist"], SORT_NUMERIC, SORT_ASC, $search["icao"], SORT_STRING, SORT_ASC);
  915.                 // If array is larger then desired results, chop off last one
  916.                 if (sizeof($search["dist"]) > $numResults) {
  917.                     array_pop($search["dist"]);
  918.                     array_pop($search["icao"]);
  919.                 }
  920.                 $min_dist = max($search["dist"]);
  921.             }
  922.         }
  923.         if ($numResults == 1) {
  924.             // Only one result wanted, return as string
  925.             return $search["icao"][0];
  926.         } elseif ($numResults > 1) {
  927.             // Return found locations
  928.             return $search["icao"];
  929.         } else {
  930.             return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_UNKNOWN_LOCATION);
  931.         }
  932.     }
  933.     // }}}
  934.  
  935.     // {{{ getUnits()
  936.     /**
  937.     * Returns the units for the current query
  938.     *
  939.     * @param    string                      $id
  940.     * @param    string                      $unitsFormat
  941.     * @return   array
  942.     * @deprecated
  943.     * @access   public
  944.     */
  945.     function getUnits($id = null, $unitsFormat = "")
  946.     {
  947.         return $this->getUnitsFormat($unitsFormat);
  948.     }
  949.     // }}}
  950.  
  951.     // {{{ getLocation()
  952.     /**
  953.     * Returns the data for the location belonging to the ID
  954.     *
  955.     * @param    string                      $id
  956.     * @return   PEAR_Error|array
  957.     * @throws   PEAR_Error
  958.     * @access   public
  959.     */
  960.     function getLocation($id = "")
  961.     {
  962.         $status = $this->_checkLocationID($id);
  963.  
  964.         if (Services_Weather::isError($status)) {
  965.             return $status;
  966.         }
  967.  
  968.         $locationReturn = array();
  969.  
  970.         if ($this->_cacheEnabled && ($location = $this->_cache->get("METAR-".$id, "location"))) {
  971.             // Grab stuff from cache
  972.             $this->_location = $location;
  973.             $locationReturn["cache"] = "HIT";
  974.         } elseif (isset($this->_db) && DB::isConnection($this->_db)) {
  975.             // Get data from DB
  976.             $select = "SELECT icao, name, state, country, latitude, longitude, elevation ".
  977.                       "FROM metarAirports WHERE icao='".$id."'";
  978.             $result = $this->_db->query($select);
  979.  
  980.             if (DB::isError($result)) {
  981.                 return $result;
  982.             } elseif (strtolower(get_class($result)) != "db_result" || $result->numRows() == 0) {
  983.                 return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_UNKNOWN_LOCATION);
  984.             }
  985.             // Result is ok, put things into object
  986.             $this->_location = $result->fetchRow(DB_FETCHMODE_ASSOC);
  987.  
  988.             if ($this->_cacheEnabled) {
  989.                 // ...and cache it
  990.                 $expire = constant("SERVICES_WEATHER_EXPIRES_LOCATION");
  991.                 $this->_cache->extSave("METAR-".$id, $this->_location, "", $expire, "location");
  992.             }
  993.  
  994.             $locationReturn["cache"] = "MISS";
  995.         } else {
  996.             $this->_location = array(
  997.                 "name"      => $id,
  998.                 "state"     => "",
  999.                 "country"   => "",
  1000.                 "latitude"  => "",
  1001.                 "longitude" => "",
  1002.                 "elevation" => ""
  1003.             );
  1004.         }
  1005.         // Stuff name-string together
  1006.         if (strlen($this->_location["state"]) && strlen($this->_location["country"])) {
  1007.             $locname = $this->_location["name"].", ".$this->_location["state"].", ".$this->_location["country"];
  1008.         } elseif (strlen($this->_location["country"])) {
  1009.             $locname = $this->_location["name"].", ".$this->_location["country"];
  1010.         } else {
  1011.             $locname = $this->_location["name"];
  1012.         }
  1013.         $locationReturn["name"]      = $locname;
  1014.         $locationReturn["latitude"]  = $this->_location["latitude"];
  1015.         $locationReturn["longitude"] = $this->_location["longitude"];
  1016.         $locationReturn["elevation"] = $this->_location["elevation"];
  1017.  
  1018.         return $locationReturn;
  1019.     }
  1020.     // }}}
  1021.  
  1022.     // {{{ getWeather()
  1023.     /**
  1024.     * Returns the weather-data for the supplied location
  1025.     *
  1026.     * @param    string                      $id
  1027.     * @param    string                      $unitsFormat
  1028.     * @return   PHP_Error|array
  1029.     * @throws   PHP_Error
  1030.     * @access   public
  1031.     */
  1032.     function getWeather($id = "", $unitsFormat = "")
  1033.     {
  1034.         $id     = strtoupper($id);
  1035.         $status = $this->_checkLocationID($id);
  1036.  
  1037.         if (Services_Weather::isError($status)) {
  1038.             return $status;
  1039.         }
  1040.  
  1041.         // Get other data
  1042.         $units    = $this->getUnitsFormat($unitsFormat);
  1043.         $location = $this->getLocation($id);
  1044.  
  1045.         if ($this->_cacheEnabled && ($weather = $this->_cache->get("METAR-".$id, "weather"))) {
  1046.             // Wee... it was cached, let's have it...
  1047.             $weatherReturn  = $weather;
  1048.             $this->_weather = $weatherReturn;
  1049.             $weatherReturn["cache"] = "HIT";
  1050.         } else {
  1051.             // Set the source
  1052.             if ($this->_source == "file") {
  1053.                 $source = realpath($this->_sourcePath.$id.".TXT");
  1054.             } else {
  1055.                 $source = $this->_sourcePath.$id.".TXT";
  1056.             }
  1057.  
  1058.             // Download and parse weather
  1059.             $weatherReturn  = $this->_parseWeatherData($source, $units);
  1060.  
  1061.             if (Services_Weather::isError($weatherReturn)) {
  1062.                 return $weatherReturn;
  1063.             }
  1064.             if ($this->_cacheEnabled) {
  1065.                 // Cache weather
  1066.                 $expire = constant("SERVICES_WEATHER_EXPIRES_WEATHER");
  1067.                 $this->_cache->extSave("METAR-".$id, $weatherReturn, $unitsFormat, $expire, "weather");
  1068.             }
  1069.             $this->_weather = $weatherReturn;
  1070.             $weatherReturn["cache"] = "MISS";
  1071.         }
  1072.  
  1073.         if (isset($weatherReturn["remark"])) {
  1074.             foreach ($weatherReturn["remark"] as $key => $val) {
  1075.                 switch ($key) {
  1076.                     case "seapressure":
  1077.                         $newVal = $this->convertPressure($val, "in", $units["pres"]);
  1078.                         break;
  1079.                     case "snowdepth":
  1080.                     case "snowequiv":
  1081.                         $newVal = $this->convertPressure($val, "in", $units["rain"]);
  1082.                         break;
  1083.                     case "1htemp":
  1084.                     case "1hdew":
  1085.                     case "6hmaxtemp":
  1086.                     case "6hmintemp":
  1087.                     case "24hmaxtemp":
  1088.                     case "24hmintemp":
  1089.                         $newVal = $this->convertTemperature($val, "f", $units["temp"]);
  1090.                         break;
  1091.                     default:
  1092.                         continue 2;
  1093.                         break;
  1094.                 }
  1095.                 $weatherReturn["remark"][$key] = $newVal;
  1096.             }
  1097.         }
  1098.  
  1099.         foreach ($weatherReturn as $key => $val) {
  1100.             switch ($key) {
  1101.                 case "station":
  1102.                     $newVal = $location["name"];
  1103.                     break;
  1104.                 case "update":
  1105.                     $newVal = gmdate(trim($this->_dateFormat." ".$this->_timeFormat), $val);
  1106.                     break;
  1107.                 case "wind":
  1108.                 case "windGust":
  1109.                     $newVal = $this->convertSpeed($val, "mph", $units["wind"]);
  1110.                     break;
  1111.                 case "visibility":
  1112.                     $newVal = $this->convertDistance($val, "sm", $units["vis"]);
  1113.                     break;
  1114.                 case "temperature":
  1115.                 case "dewPoint":
  1116.                 case "feltTemperature":
  1117.                     $newVal = $this->convertTemperature($val, "f", $units["temp"]);
  1118.                     break;
  1119.                 case "pressure":
  1120.                     $newVal = $this->convertPressure($val, "in", $units["pres"]);
  1121.                     break;
  1122.                 case "precipitation":
  1123.                     $newVal = array();
  1124.                     for ($p = 0; $p < sizeof($val); $p++) {
  1125.                         $newVal[$p] = array();
  1126.                         if (is_numeric($val[$p]["amount"])) {
  1127.                             $newVal[$p]["amount"] = $this->convertPressure($val[$p]["amount"], "in", $units["rain"]);
  1128.                         } else {
  1129.                             $newVal[$p]["amount"] = $val[$p]["amount"];
  1130.                         }
  1131.                         $newVal[$p]["hours"]  = $val[$p]["hours"];
  1132.                     }
  1133.                     break;
  1134. /*
  1135.                 case "remark":
  1136.                     $newVal = implode(", ", $val);
  1137.                     break;
  1138. */
  1139.                 default:
  1140.                     continue 2;
  1141.                     break;
  1142.             }
  1143.             $weatherReturn[$key] = $newVal;
  1144.         }
  1145.  
  1146.         return $weatherReturn;
  1147.     }
  1148.     // }}}
  1149.     
  1150.     // {{{ getForecast()
  1151.     /**
  1152.     * METAR has no forecast per se, so this function is just for
  1153.     * compatibility purposes.
  1154.     *
  1155.     * @param    string                      $int
  1156.     * @param    int                         $days
  1157.     * @param    string                      $unitsFormat
  1158.     * @return   bool
  1159.     * @access   public
  1160.     * @deprecated
  1161.     */
  1162.     function getForecast($id = null, $days = null, $unitsFormat = null)
  1163.     {
  1164.         return false;
  1165.     }
  1166.     // }}}
  1167. }
  1168. // }}}
  1169. ?>
  1170.