[ PHPXref.com ] [ Generated: Sun Jul 20 19:57:02 2008 ] [ PHP Timeclock 1.02 ]
[ Index ]     [ Variables ]     [ Functions ]     [ Classes ]     [ Constants ]     [ Statistics ]

title

Body

[close]

/ -> phpweather.php (source)

   1  <?php
   2  
   3  ////////////////////////////////////////////
   4  /* Copied from PHP Weather version 1.62.  */
   5  /* Line 109 was added for php timeclock.  */
   6  /* Line 110 was edited for php timeclock. */
   7  ////////////////////////////////////////////
   8  
   9  /* Unsets old language variables and loads new ones. */
  10  
  11  if (isset($strings)) {
  12    /* The strings array is loaded - assume the same for the rest. */
  13    unset($strings);
  14    unset($wind_dir_text_short_array);
  15    unset($wind_dir_text_array);
  16    unset($weather_array);
  17    unset($cloud_condition_array);
  18  }
  19  
  20  /* Load the new strings */
  21  
  22  $wind_dir_text_short_array = array(
  23    'N',
  24    'N/NE',
  25    'NE',
  26    'E/NE',
  27    'E',
  28    'E/SE',
  29    'SE',
  30    'S/SE',
  31    'S',
  32    'S/SW',
  33    'SW',
  34    'W/SW',
  35    'W',
  36    'W/NW',
  37    'NW',
  38    'N/NW',
  39    'N');
  40  
  41  $cloud_condition_array = array(
  42    'SKC' => 'Clear',
  43    'CLR' => 'Clear',
  44    'VV'  => 'Vertical Visibility',
  45    'FEW' => 'Partly Cloudy',
  46    'SCT' => 'Scattered Clouds',
  47    'BKN' => 'Partly Cloudy',
  48    'OVC' => 'Overcast');
  49  
  50  /* Offset in hours to add to the time of a report. If all your times
  51   * are 2 hours off, then set this to -2.  */
  52  $weather_offset = 0;
  53  
  54    /* Make a connection to the MySQL database: */
  55    if (mysql_pconnect($db_hostname, $db_username, $db_password)) {
  56      mysql_select_db($db_name);
  57    } else {
  58      echo "<p>Unable to connect to MySQL database!</p>";
  59    }
  60  
  61  function store_speed($value, $windunit, &$meterspersec, &$knots, &$milesperhour) {
  62    /*
  63     * Helper function to convert and store speed based on unit.
  64     * &$meterspersec, &$knots and &$milesperhour are passed on
  65     * reference
  66     */
  67    if ($windunit == 'KT') {
  68      /* The windspeed measured in knots: */
  69      $knots = number_format($value);
  70      /* The windspeed measured in meters per second, rounded to one
  71       * decimal place: */
  72      $meterspersec = number_format($value * 0.51444, 1);
  73      /* The windspeed measured in miles per hour, rounded to one
  74       * decimal place: */
  75      $milesperhour = number_format($value * 1.1507695060844667, 1);
  76    } elseif ($windunit == 'MPS') {
  77      /* The windspeed measured in meters per second: */
  78      $meterspersec = number_format($value);
  79      /* The windspeed measured in knots, rounded to one decimal
  80       * place: */
  81      $knots = number_format($value / 0.51444, 1);
  82      /* The windspeed measured in miles per hour, rounded to one
  83       * decimal place: */
  84      $milesperhour = number_format($value / 0.51444 * 1.1507695060844667, 1);
  85    } elseif ($windunit == 'KMH') {
  86      /* The windspeed measured in kilometers per hour: */
  87      $meterspersec = number_format($value * 1000 / 3600, 1);
  88      $knots = number_format($value * 1000 / 3600 / 0.51444, 1);
  89      /* The windspeed measured in miles per hour, rounded to one
  90       * decimal place: */
  91      $milesperhour = number_format($knots * 1.1507695060844667, 1);
  92    }
  93  }
  94  
  95  function get_metar($station, $always_use_cache = 0) {
  96    /*
  97     * Looks in the database, and fetches a new metar is nesceary. If
  98     * $always_use_cache is true, then it ignores the timestamp of the
  99     * METAR and just returns it.
 100     * 
 101     * You should pass a ICAO station identifier, eg. 'EKYT' for
 102     * Aalborg, Denmark.
 103     */
 104  
 105    global $conn, $dbmMetar, $dbmTimestamp;
 106  
 107      $query = "SELECT metar, UNIX_TIMESTAMP(timestamp) FROM ".$db_prefix."metars WHERE station = '$station'";
 108      $result = mysql_query($query);
 109      @$metar_rows = mysql_num_rows($result); /* this suppresses a php error message if the metars db has not yet been created. */ 
 110      if (isset($metar_rows)) { /* found station */
 111        list($metar, $timestamp) = mysql_fetch_row($result);
 112      }
 113  
 114    if (isset($metar)) { /* found station */
 115      if ($always_use_cache || $timestamp > time() - 3600) {
 116        /* We have asked explicit for a cached metar, or the metar is
 117         * still fresh. */
 118        return $metar;
 119      } else {
 120        /* We looked in the cache, but the metar was too old. */
 121        return fetch_metar($station, 0);
 122      }
 123    } else {
 124      /* The station is new - we fetch a new METAR */
 125      return fetch_metar($station, 1);
 126    }
 127  }
 128  
 129  function fetch_metar($station, $new) {
 130    /*
 131     * Fetches a new METER from weather.noaa.gov. If the $new variable
 132     * is true, the metar is inserted, else it will replace the old
 133     * metar. The new METAR is returned.
 134     */
 135    global $conn, $dbmMetar, $dbmTimestamp;
 136  
 137    $metar = '';
 138    $station = strtoupper($station);
 139    
 140    /* We use the @ notation, because it might fail. */
 141    $file  = @file('http://weather.noaa.gov/pub/data/' .
 142                     "observations/metar/stations/$station.TXT");
 143  
 144    /* Here we test to see if we actually got a METAR. */
 145    if (is_array($file)) {
 146      $date = trim($file[0]);
 147      $metar = trim($file[1]);
 148      for ($i = 2; $i < count($file); $i++) {
 149        $metar .= ' ' . trim($file[i]);
 150      }
 151      
 152      /* The date is in the form 2000/10/09 14:50 UTC. This seperates
 153         the different parts. */
 154      $date_parts = explode(':', strtr($date, '/ ', '::'));
 155      $date_unixtime = gmmktime($date_parts[3], $date_parts[4],
 156                                0, $date_parts[1], $date_parts[2],
 157                                $date_parts[0]);
 158     
 159      if (!ereg('[0-9]{6}Z', $metar)) {
 160        /* Some reports dont even have a time-part, so we insert the
 161         * current time. This might not be the time of the report, but
 162         * it was broken anyway :-) */
 163        $metar = gmdate('dHi', $date_unixtime) . 'Z ' . $metar;
 164      }
 165      
 166      if ($date_unixtime < (time() - 3300)) {
 167        /* The timestamp in the metar is more than 55 minutes old. We
 168         * adjust the timestamp, so that we won't try to fetch a new
 169         * METAR within the next 5 minutes. After 5 minutes, the
 170         * timestamp will again be more than 1 hour old. */
 171        $date_unixtime = time() - 3300;
 172      }
 173  
 174    } else {
 175      /* If we end up here, it means that there was no file, we then set
 176       * the metar to and empty string. We set the date to time() - 3000
 177       * to give the server 10 minutes of peace. If the file is
 178       * unavailable, we don't want to stress the server. */
 179      $metar = '';
 180      $date_unixtime = time() - 3000;
 181    }
 182    
 183    /* It might seam strange, that we make a local date, but MySQL
 184     * expects a local when we insert the METAR. */
 185    $date = date('Y/m/d H:i', $date_unixtime);
 186  
 187      if ($new) {
 188        /* Insert the new record */
 189        $query = "INSERT INTO ".$db_prefix."metars SET station = '$station', " .
 190          "metar = '$metar', timestamp = '$date'";
 191      } else {
 192        /* Update the old record */
 193        $query = "UPDATE ".$db_prefix."metars SET metar = '$metar', " .
 194          "timestamp = '$date' WHERE station = '$station'";
 195      }
 196      mysql_query($query);
 197  
 198    return $metar;
 199  }
 200  
 201  function process_metar($metar) {
 202    /* This function decodes a raw METAR. The result is an associative
 203     * array with entries like 'temp_c', 'visibility_miles' etc.  */
 204  
 205    global $strings, $wind_dir_text_short_array, $wind_dir_text_array,
 206      $cloud_condition_array, $weather_array, $weather_offset;
 207  
 208    $temp_visibility_miles = '';
 209    $cloud_layers = 0;
 210    $decoded_metar['remarks'] = '';
 211    $decoded_metar['weather'] = '';
 212    
 213    $cloud_coverage = array('SKC' => '0',
 214          'CLR' => '0',
 215          'VV'  => '8/8',
 216          'FEW' => '1/8 - 2/8',
 217          'SCT' => '3/8 - 4/8',
 218          'BKN' => '5/8 - 7/8',
 219          'OVC' => '8/8');
 220    
 221    $decoded_metar['metar'] = $metar;
 222    $parts = split('[ ]+', $metar);
 223    $num_parts = count($parts);
 224    for ($i = 0; $i < $num_parts; $i++) {
 225      $part = $parts[$i];
 226  
 227      if (ereg('RMK|TEMPO|BECMG', $part)) {
 228        /* The rest of the METAR is either a remark or temporary
 229         * information. We skip the rest of the METAR. */
 230        $decoded_metar['remarks'] .= ' ' . $part;
 231        break;
 232      } elseif ($part == 'METAR') {
 233        /*
 234         * Type of Report: METAR
 235         */
 236        $decoded_metar['type'] = 'METAR';
 237      } elseif ($part == 'SPECI') {
 238        /*
 239         * Type of Report: SPECI
 240         */
 241        $decoded_metar['type'] = 'SPECI';
 242      } elseif (ereg('^[A-Z]{4}$', $part) && ! isset($decoded_metar['station']))  {
 243        /*
 244         * Station Identifier
 245         */
 246        $decoded_metar['station'] = $part;
 247      } elseif (ereg('([0-9]{2})([0-9]{2})([0-9]{2})Z', $part, $regs)) {
 248        /*
 249         * Date and Time of Report
 250         * We return a standard Unix UTC/GMT timestamp suitable for
 251         * gmdate()
 252         */
 253        $decoded_metar['time'] = gmmktime($regs[2] + $weather_offset, $regs[3], 0,
 254                                          gmdate('m'), $regs[1], gmdate('Y'));
 255      } elseif (ereg('(AUTO|COR|RTD|CC[A-Z]|RR[A-Z])', $part, $regs)) {
 256        /*
 257         * Report Modifier: AUTO, COR, CCx or RRx
 258         */
 259        $decoded_metar['report_mod'] = $regs[1];
 260      } elseif (ereg('([0-9]{3}|VRB)([0-9]{2,3}).*(KT|MPS|KMH)', $part, $regs)) {
 261        /* Wind Group */
 262        $windunit = $regs[3];  /* do ereg in two parts to retrieve unit first */
 263        /* now do ereg to get the actual values */
 264        ereg("([0-9]{3}|VRB)([0-9]{2,3})(G([0-9]{2,3})?$windunit)", $part, $regs);
 265        if ($regs[1] == 'VRB') {
 266          $decoded_metar['wind_deg'] = $strings['wind_vrb_long'];
 267          $decoded_metar['wind_dir_text'] = $strings['wind_vrb_long'];
 268          $decoded_metar['wind_dir_text_short'] = $strings['wind_vrb_short'];
 269        } else {
 270          $decoded_metar['wind_deg'] = $regs[1];
 271          $decoded_metar['wind_dir_text'] =
 272            $wind_dir_text_array[intval(round($regs[1]/22.5))];
 273          $decoded_metar['wind_dir_text_short'] =
 274            $wind_dir_text_short_array[intval(round($regs[1]/22.5))];
 275        }
 276        store_speed($regs[2],
 277                    $windunit,
 278                    $decoded_metar['wind_meters_per_second'],
 279                    $decoded_metar['wind_knots'],
 280                    $decoded_metar['wind_miles_per_hour']);
 281  
 282        if (isset($regs[4])) {
 283          /* We have a report with information about the gust. First we
 284             have the gust measured in knots: */
 285          store_speed($regs[4],$windunit,
 286            $decoded_metar['wind_gust_meters_per_second'],
 287            $decoded_metar['wind_gust_knots'],
 288            $decoded_metar['wind_gust_miles_per_hour']);
 289        }
 290      } elseif (ereg('^([0-9]{3})V([0-9]{3})$', $part, $regs)) {
 291        /*
 292         * Variable wind-direction
 293         */
 294        $decoded_metar['wind_var_beg'] = $regs[1];
 295        $decoded_metar['wind_var_end'] = $regs[2];
 296      } elseif ($part == 9999) {
 297        /* A strange value. When you look at other pages you see it
 298           interpreted like this (where I use > to signify 'Greater
 299           than'): */
 300        $decoded_metar['visibility_miles'] = '>6.2';
 301        $decoded_metar['visibility_km']    = '>10';
 302      } elseif(ereg('^([0-9]{4})$', $part, $regs)) {
 303        /* 
 304         * Visibility in meters (4 digits only)
 305         */
 306        $decoded_metar['visibility_km'] = number_format($regs[1]/1000, 1);
 307        $decoded_metar['visibility_miles'] =
 308          number_format( ($regs[1]/1000) / 1.609344, 1);
 309      } elseif (ereg('^[0-9]$', $part)) {
 310        /*
 311         * Temp Visibility Group, single digit followed by space
 312         */
 313        $temp_visibility_miles = $part;
 314      } elseif (ereg('^M?(([0-9]?)[ ]?([0-9])(/?)([0-9]*))SM$',
 315                     $temp_visibility_miles . ' ' .
 316                     $parts[$i], $regs)) {
 317        /*
 318         * Visibility Group
 319         */
 320        if ($regs[4] == '/') {
 321          $vis_miles = $regs[2] + $regs[3]/$regs[5];
 322        } else {
 323          $vis_miles = $regs[1];
 324        }
 325        if ($regs[0][0] == 'M') {
 326          /* The visibility measured in miles, prefixed with < to
 327             indicate 'Less than' */
 328          $decoded_metar['visibility_miles'] =
 329            '<' . number_format($vis_miles, 1);
 330          /* The visibility measured in kilometers. The value is rounded
 331             to one decimal place, prefixed with < to indicate 'Less
 332             than' */
 333          $decoded_metar['visibility_km']    =
 334            '<' . number_format($vis_miles * 1.609344, 1);
 335        } else {
 336          /* The visibility measured in mile.s */
 337          $decoded_metar['visibility_miles'] = number_format($vis_miles, 1);
 338          /* The visibility measured in kilometers, rounded to one
 339             decimal place. */
 340          $decoded_metar['visibility_km'] =
 341            number_format($vis_miles * 1.609344, 1);
 342        }
 343      } elseif ($part == 'CAVOK') {
 344        /* CAVOK: Used when the visibility is greather than 10
 345           kilometers, the lowest cloud-base is at 5000 feet and there
 346           is no significant weather. */
 347        $decoded_metar['visibility_km']    = '>10';
 348        $decoded_metar['visibility_miles'] = '>6.2';
 349        $decoded_metar['cloud_layer1_condition'] = 'CAVOK';
 350      } elseif (ereg('^R([0-9][0-9][RLC]?)/([MP]?[0-9]{4})V?(P?[0-9]{4})?F?T?$', $part, $regs)) {
 351        $decoded_metar['runway_nr'] = $regs[1];
 352        if ($regs[3]) {
 353    /* We have both min and max visibility. */
 354    $prefix = '';
 355    if ($regs[2][0] == 'M') {
 356      /* Less than. */
 357      $prefix = '<';
 358      $regs[2] = substr($regs[2], 1);
 359    }
 360    $decoded_metar['runway_vis_min_ft']    = $prefix . number_format($regs[2]);
 361    $decoded_metar['runway_vis_min_meter'] = $prefix . number_format($regs[2] * 0.3048);
 362  
 363    $prefix = '';
 364    if ($regs[3][0] == 'P') {
 365      /* Greather than. */
 366      $prefix = '>';
 367      $regs[3] = substr($regs[3], 1);
 368    }
 369    $decoded_metar['runway_vis_max_ft']    = $prefix . number_format($regs[3]);
 370    $decoded_metar['runway_vis_max_meter'] = $prefix . number_format($regs[3] * 0.3048);
 371      
 372        } else {
 373    /* We only have a single visibility. */
 374    $prefix = '';
 375    if ($regs[2][0] == 'M') {
 376      $prefix = '<';
 377      $regs[2] = substr($regs[2], 1);
 378    } elseif ($regs[2][0] == 'P') {
 379      $prefix = '>';
 380      $regs[2] = substr($regs[2], 1);
 381    }
 382    $decoded_metar['runway_vis_ft']    = $prefix . number_format($regs[2]);
 383    $decoded_metar['runway_vis_meter'] = $prefix . number_format($regs[2] * 0.3048);
 384        }
 385      } elseif (ereg('^(-|\+|VC)?(TS|SH|FZ|BL|DR|MI|BC|PR|RA|DZ|SN|SG|GR|' .
 386                     'GS|PE|IC|UP|BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS)+$',
 387                     $part)) {
 388        /*
 389         * Current weather-group
 390         */ 
 391        if ($part[0] == '-') {
 392          /* A light phenomenon */
 393          $decoded_metar['weather'] .= $strings['light'];
 394          $part = substr($part, 1);
 395        } elseif ($part[0] == '+') {
 396          /* A heavy phenomenon */
 397          $decoded_metar['weather'] .= $strings['heavy'];
 398          $part = substr($part, 1);
 399        } elseif ($part[0].$part[1] == 'VC') {
 400          /* Proximity Qualifier */
 401          $decoded_metar['weather'] .= $strings['nearby'];
 402          $part = substr($part, 2);
 403        } else {
 404          /* no intensity code => moderate phenomenon */
 405          $decoded_metar['weather'] .= $strings['moderate'];
 406        }
 407        
 408        while ($bite = substr($part, 0, 2)) {
 409          /* Now we take the first two letters and determine what they
 410             mean. We append this to the variable so that we gradually
 411             build up a phrase. */
 412          $decoded_metar['weather'] .= $weather_array[$bite];
 413          /* Here we chop off the two first letters, so that we can take
 414             a new bite at top of the while-loop. */
 415          $part = substr($part, 2);
 416        }
 417      } elseif (ereg('(SKC|CLR)', $part, $regs)) {
 418        /*
 419         * Cloud-layer-group.
 420         * There can be up to three of these groups, so we store them as
 421         * cloud_layer1, cloud_layer2 and cloud_layer3.
 422         */
 423        $cloud_layers++;
 424        /* Again we have to translate the code-characters to a
 425           meaningful string. */
 426        $decoded_metar['cloud_layer'. $cloud_layers.'_condition'] =
 427          $cloud_condition_array[$regs[1]];
 428        $decoded_metar['cloud_layer'.$cloud_layers.'_coverage'] =
 429          $cloud_coverage[$regs[1]];
 430      } elseif (ereg('^(VV|FEW|SCT|BKN|OVC)([0-9]{3})(CB|TCU)?$',
 431                     $part, $regs)) {
 432        /* We have found (another) a cloud-layer-group. There can be up
 433           to three of these groups, so we store them as cloud_layer1,
 434           cloud_layer2 and cloud_layer3. */
 435        $cloud_layers++;
 436        /* Again we have to translate the code-characters to a
 437           meaningful string. */
 438        if ($regs[1] == 'OVC') {
 439          $clouds_str_temp = '';
 440        } else {
 441          $clouds_str_temp = $strings['clouds'];
 442        }
 443        if ($regs[3] == 'CB') {
 444          /* cumulonimbus (CB) clouds were observed. */
 445          $decoded_metar['cloud_layer'.$cloud_layers.'_condition'] =
 446            $cloud_condition_array[$regs[1]] . $strings['clouds_cb'];
 447        } elseif ($regs[3] == 'TCU') {
 448          /* towering cumulus (TCU) clouds were observed. */
 449          $decoded_metar['cloud_layer'.$cloud_layers.'_condition'] =
 450            $cloud_condition_array[$regs[1]] . $strings['clouds_tcu'];
 451        } else {
 452          $decoded_metar['cloud_layer'.$cloud_layers.'_condition'] =
 453            $cloud_condition_array[$regs[1]] . $clouds_str_temp;
 454        }
 455        $decoded_metar['cloud_layer'.$cloud_layers.'_coverage'] =
 456          $cloud_coverage[$regs[1]];
 457        $decoded_metar['cloud_layer'.$cloud_layers.'_altitude_ft'] =
 458          $regs[2] *100;
 459        $decoded_metar['cloud_layer'.$cloud_layers.'_altitude_m'] =
 460          round($regs[2] * 30.48);
 461      } elseif (ereg('^(M?[0-9]{2})/(M?[0-9]{2})?$', $part, $regs)) {
 462        /*
 463         * Temperature/Dew Point Group
 464         * The temperature and dew-point measured in Celsius.
 465         */
 466        $decoded_metar['temp_c'] = number_format(strtr($regs[1], 'M', '-'));
 467        $decoded_metar['dew_c']  = number_format(strtr($regs[2], 'M', '-'));
 468        /* The temperature and dew-point measured in Fahrenheit, rounded
 469           to the nearest degree. */
 470        $decoded_metar['temp_f'] = round(strtr($regs[1], 'M', '-') * (9/5) + 32);
 471        $decoded_metar['dew_f']  = round(strtr($regs[2], 'M', '-') * (9/5) + 32);
 472      } elseif(ereg('A([0-9]{4})', $part, $regs)) {
 473        /*
 474         * Altimeter
 475         * The pressure measured in inHg
 476         */
 477        $decoded_metar['altimeter_inhg'] = number_format($regs[1]/100, 2);
 478        /* The pressure measured in mmHg, hPa and atm */
 479        $decoded_metar['altimeter_mmhg'] = number_format($regs[1] * 0.254, 1);
 480        $decoded_metar['altimeter_hpa']  = number_format($regs[1] * 0.33863881578947);
 481        $decoded_metar['altimeter_atm']  = number_format($regs[1] * 3.3421052631579e-4, 3);
 482      } elseif(ereg('Q([0-9]{4})', $part, $regs)) {
 483        /*
 484         * Altimeter
 485         * This is strange, the specification doesnt say anything about
 486         * the Qxxxx-form, but it's in the METARs.
 487         */
 488        /* The pressure measured in hPa */
 489        $decoded_metar['altimeter_hpa']  = number_format($regs[1]);
 490        /* The pressure measured in mmHg, inHg and atm */
 491        $decoded_metar['altimeter_mmhg'] = number_format($regs[1] * 0.7500616827, 1);
 492        $decoded_metar['altimeter_inhg'] = number_format($regs[1] * 0.0295299875, 2);
 493        $decoded_metar['altimeter_atm']  = number_format($regs[1] * 9.869232667e-4, 3);
 494      } elseif (ereg('^T([0-9]{4})([0-9]{4})', $part, $regs)) {
 495        /*
 496         * Temperature/Dew Point Group, coded to tenth of degree.
 497         * The temperature and dew-point measured in Celsius.
 498         */
 499        store_temp($regs[1],$decoded_metar,'temp_c','temp_f');
 500        store_temp($regs[2],$decoded_metar,'dew_c','dew_f');
 501      } elseif (ereg('^T([0-9]{4}$)', $part, $regs)) {
 502        store_temp($regs[1],$decoded_metar,'temp_c','temp_f');
 503      } elseif (ereg('^1([0-9]{4}$)', $part, $regs)) {
 504        /*
 505         * 6 hour maximum temperature Celsius, coded to tenth of degree
 506         */
 507        store_temp($regs[1],$decoded_metar,'temp_max6h_c','temp_max6h_f');
 508      } elseif (ereg('^2([0-9]{4}$)', $part, $regs)) {
 509        /*
 510         * 6 hour minimum temperature Celsius, coded to tenth of degree
 511         */
 512        store_temp($regs[1],$decoded_metar,'temp_min6h_c','temp_min6h_f');
 513      } elseif (ereg('^4([0-9]{4})([0-9]{4})$', $part, $regs)) {
 514        /*
 515         * 24 hour maximum and minimum temperature Celsius, coded to
 516         * tenth of degree
 517         */
 518        store_temp($regs[1],$decoded_metar,'temp_max24h_c','temp_max24h_f');
 519        store_temp($regs[2],$decoded_metar,'temp_min24h_c','temp_min24h_f');
 520      } elseif(ereg('^P([0-9]{4})', $part, $regs)) {
 521        /*
 522         * Precipitation during last hour in hundredths of an inch
 523         * (store as inches)
 524         */
 525        $decoded_metar['precip_in'] = number_format($regs[1]/100, 2);
 526        $decoded_metar['precip_mm'] = number_format(