Services_Weather
[ class tree: Services_Weather ] [ index: Services_Weather ] [ all elements ]

Source for file Metar.php

Documentation is available at Metar.php

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

Documentation generated on Mon, 11 Mar 2019 10:14:19 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.