You are here

weather_parser.inc in Weather 5

File

weather_parser.inc
View source
<?php

/*
 * Copyright (C) 2006 Tobias Toedter <t.toedter@gmx.net>
 *
 * This file is part of the Drupal Weather module.
 *
 * Weather is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Weather is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Weather; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

/**
 * Parses a raw METAR data string
 */
function weather_parse_metar($metar_raw_string) {
  $metar = array();
  $metar['#raw'] = $metar_raw_string;
  $raw_items = preg_split('/\\s+/', strtoupper($metar_raw_string));

  // Run the data through the METAR routines
  foreach ($raw_items as $metar_raw) {
    if (_weather_parse_stop($metar_raw, $metar)) {
      break;
    }
    _weather_parse_icao($metar_raw, $metar);
    _weather_parse_timestamp($metar_raw, $metar);
    _weather_parse_reporttype($metar_raw, $metar);
    _weather_parse_wind($metar_raw, $metar);
    _weather_parse_visibility($metar_raw, $metar);
    _weather_parse_condition($metar_raw, $metar);
    _weather_parse_phenomena($metar_raw, $metar);
    _weather_parse_temperature($metar_raw, $metar);
    _weather_parse_pressure($metar_raw, $metar);
  }
  return $metar;
}

/**
 * Decide whether to stop parsing
 * 
 * @param string Raw METAR data to parse
 * @param array Parsed METAR data, will be altered
 * @return boolean
 */
function _weather_parse_stop($metar_raw, &$metar) {
  if (preg_match('/^(BECMG|TEMPO|NOSIG|RMK)$/', $metar_raw)) {
    return true;
  }
  else {
    return false;
  }
}

/**
 * Extract the ICAO code
 * 
 * ICAO = International Civil Aviation Organization, this is a four
 * letter airport code, e. g. EDDH
 * 
 * @param string Raw METAR data to parse
 * @param array Parsed METAR data, will be altered
 */
function _weather_parse_icao($metar_raw, &$metar) {
  if (preg_match('/^([A-Z]{4}|K[A-Z0-9]{3})$/', $metar_raw) and !isset($metar['icao'])) {
    $metar['icao'] = $metar_raw;
  }
}

/**
 * Extract the timestamp
 * 
 * @param string Raw METAR data to parse
 * @param array Parsed METAR data, will be altered
 */
function _weather_parse_timestamp($metar_raw, &$metar) {
  if (preg_match('/^([0-9]{2})([0-9]{2})([0-9]{2})Z$/', $metar_raw, $matches)) {
    $timestamp['year'] = gmdate('Y');
    $timestamp['month'] = gmdate('n');
    $timestamp['day'] = $matches[1];
    $timestamp['hour'] = $matches[2];
    $timestamp['minute'] = $matches[3];

    // if the current day is lower than the one from the METAR data,
    // it must be a day from last month
    // Note: in case even the year wraps (month = 0 -> month = 12, year--),
    // the gmmktime() function further down will take care of this.
    if (gmdate('d') < $timestamp['day']) {
      $timestamp['month']--;
    }
    $metar['reported_on'] = gmmktime($timestamp['hour'], $timestamp['minute'], 0, $timestamp['month'], $timestamp['day'], $timestamp['year']);
  }
}

/**
 * Extract the report type
 * 
 * This may be missing in the METAR data. If present, it should be
 * either AUTO or COR.
 * 
 * @param string Raw METAR data to parse
 * @param array Parsed METAR data, will be altered
 */
function _weather_parse_reporttype($metar_raw, &$metar) {
  if (preg_match('/^(AUTO|COR)$/', $metar_raw)) {
    $metar['reporttype'] = $metar_raw;
  }
}

/**
 * Extract the wind information
 * 
 * @param string Raw METAR data to parse
 * @param array Parsed METAR data, will be altered
 */
function _weather_parse_wind($metar_raw, &$metar) {
  if (preg_match('/^' . '([0-9]{3}|VRB)' . '([0-9]{2,3})' . '(G([0-9]{2,3}))?' . '(KT)' . '$/', $metar_raw, $matches)) {
    $metar['wind']['direction'] = (int) $matches[1];
    $wind_speed = (int) $matches[2];
    $wind_gusts = (int) $matches[4];
    $wind_unit = $matches[5];

    // Do a conversion to other formats
    switch ($wind_unit) {
      case 'KT':

        // Convert to km/h and mph
        // nautical mile = 1852 meters
        // statue mile = 1609.344 meters
        $metar['wind']['speed_kmh'] = round($wind_speed * 1.852, 1);
        $metar['wind']['gusts_kmh'] = round($wind_gusts * 1.852, 1);
        $metar['wind']['speed_mph'] = round($wind_speed * 1.151, 1);
        $metar['wind']['gusts_mph'] = round($wind_gusts * 1.151, 1);
        break;
    }
  }
  else {
    if (preg_match('/^' . '([0-9]{3})' . 'V' . '([0-9]{3})' . '$/', $metar_raw, $matches)) {
      $metar['wind']['variable_start'] = (int) $matches[1];
      $metar['wind']['variable_end'] = (int) $matches[2];
    }
  }
}

/**
 * Extract the visibility information
 * 
 * @param string Raw METAR data to parse
 * @param array Parsed METAR data, will be altered
 */
function _weather_parse_visibility($metar_raw, &$metar) {
  if (preg_match('/^([0-9])$/', $metar_raw, $matches)) {

    // Special case: A single digit, e.g. in 1 1/2SM
    $metar['visibility']['#visibility_miles'] = $matches[1];
  }
  else {
    if (preg_match('/^' . '(M?)([0-9])(\\/?)([0-9]*)' . 'SM' . '$/', $metar_raw, $matches)) {
      if ($matches[3] == '/') {

        // This is a fractional visibility, we need to convert this
        $visibility = $metar['visibility']['#visibility_miles'] + $matches[2] / $matches[4];
      }
      else {
        $visibility = $matches[2] . $matches[4];
      }
      $metar['visibility']['miles'] = $visibility;
      $metar['visibility']['kilometers'] = round($visibility * 1.609344, 1);
    }
    else {
      if (preg_match('/^([0-9]{4})$/', $metar_raw, $matches)) {
        $metar['visibility']['kilometers'] = round($matches[1] / 1000, 1);
        $metar['visibility']['miles'] = round($metar['visibility']['kilometers'] / 1.609344, 1);
      }
    }
  }
}

/**
 * Extract the phenomena information
 * 
 * @param string Raw METAR data to parse
 * @param array Parsed METAR data, will be altered
 */
function _weather_parse_phenomena($metar_raw, &$metar) {
  if (preg_match('/^' . '(-|\\+|VC)?' . '(SH|TS|FZ)?' . 'RA' . '$/', $metar_raw, $matches)) {
    $phen = array();
    switch ($matches[1]) {
      case '-':
        $phen['#light'] = true;
        break;
      case '+':
        $phen['#heavy'] = true;
        break;
      default:
        $phen['#moderate'] = true;
    }
    switch ($matches[2]) {
      case 'SH':
        $phen['#showers'] = true;
        break;
      case 'FZ':
        $phen['#freezing'] = true;
        break;
    }
    $metar['phenomena']['rain'] = $phen;
  }
  else {
    if (preg_match('/^' . '(-|\\+|VC)?' . '(FZ)?' . 'DZ' . '$/', $metar_raw, $matches)) {
      $phen = array();
      switch ($matches[1]) {
        case '-':
          $phen['#light'] = true;
          break;
        case '+':
          $phen['#heavy'] = true;
          break;
        default:
          $phen['#moderate'] = true;
      }
      switch ($matches[2]) {
        case 'FZ':
          $phen['#freezing'] = true;
          break;
      }
      $metar['phenomena']['drizzle'] = $phen;
    }
  }
}

/**
 * Extract the condition information
 * 
 * @param string Raw METAR data to parse
 * @param array Parsed METAR data, will be altered
 */
function _weather_parse_condition($metar_raw, &$metar) {
  $ordering = array(
    1 => array(
      'CLR' => 'clear',
    ),
    2 => array(
      'FEW' => 'few',
    ),
    3 => array(
      'SCT' => 'scattered',
    ),
    4 => array(
      'BKN' => 'broken',
    ),
    5 => array(
      'OVC' => 'overcast',
    ),
  );
  if (preg_match('/^' . '(FEW|SCT|BKN|OVC)([0-9]{3})' . '(CB|TCU)?' . '$/', $metar_raw, $matches)) {
    foreach ($ordering as $order => $data) {
      if (key($data) == $matches[1]) {
        $metar['#condition_text'][] = $data[key($data)];
        $metar['#condition_order'][] = $order;
        break;
      }
    }
  }
  else {
    if (preg_match('/^' . '(CLR|SKC|CAVOK)' . '$/', $metar_raw, $matches)) {
      $metar['#condition_text'][] = 'clear';
      $metar['#condition_order'][] = 1;
      if ($matches[1] == 'CAVOK') {
        $metar['visibility']['kilometers'] = 10;
        $metar['visibility']['miles'] = round($metar['visibility']['kilometers'] / 1.609344, 1);
      }
    }
    else {
      if (preg_match('/^' . '(NSC|NCD)' . '$/', $metar_raw, $matches)) {

        // NSC means no significant clouds,
        // NCD is from automatic stations, no cloud detected
        $metar['#condition_text'][] = 'no-significant-clouds';
        $metar['#condition_order'][] = 1;
      }
      else {
        if (preg_match('/^' . 'VV[0-9]{3}' . '$/', $metar_raw, $matches)) {
          $metar['#condition_text'][] = 'overcast';
          $metar['#condition_order'][] = 5;
        }
      }
    }
  }
  if (isset($metar['#condition_order'])) {
    foreach ($metar['#condition_order'] as $index => $order) {
      if ($order > $metar['condition_order']) {
        $metar['condition_order'] = $order;
        $metar['condition_text'] = $metar['#condition_text'][$index];
      }
    }
  }
}

/**
 * Extract the temperature information
 * 
 * @param string Raw METAR data to parse
 * @param array Parsed METAR data, will be altered
 */
function _weather_parse_temperature($metar_raw, &$metar) {
  if (preg_match('/^' . '(M?[0-9]{2})' . '\\/' . '(M?[0-9]{2})?' . '$/', $metar_raw, $matches)) {
    $metar['temperature']['celsius'] = (int) strtr($matches[1], 'M', '-');
    $metar['temperature']['fahrenheit'] = round($metar['temperature']['celsius'] * 9 / 5 + 32, 1);
    if (isset($matches[2])) {
      $metar['dewpoint']['celsius'] = (int) strtr($matches[2], 'M', '-');
      $metar['dewpoint']['fahrenheit'] = round($metar['dewpoint']['celsius'] * 9 / 5 + 32, 1);
    }
  }
}

/**
 * Extract the pressure information
 * 
 * @param string Raw METAR data to parse
 * @param array Parsed METAR data, will be altered
 */
function _weather_parse_pressure($metar_raw, &$metar) {
  if (preg_match('/^' . '(A|Q)([0-9]{4})' . '$/', $metar_raw, $matches)) {
    if ($matches[1] == 'A') {

      // Pressure is given in inch Hg
      $metar['pressure']['inHg'] = $matches[2] / 100;
      $metar['pressure']['hPa'] = round(33.8639 * $metar['pressure']['inHg'], 0);
    }
    else {

      // Pressure is given in HektoPascal, hPa
      $metar['pressure']['hPa'] = (int) $matches[2];
      $metar['pressure']['inHg'] = round($metar['pressure']['hPa'] * 0.02953, 2);
    }
  }
}

Functions

Namesort descending Description
weather_parse_metar Parses a raw METAR data string
_weather_parse_condition Extract the condition information
_weather_parse_icao Extract the ICAO code
_weather_parse_phenomena Extract the phenomena information
_weather_parse_pressure Extract the pressure information
_weather_parse_reporttype Extract the report type
_weather_parse_stop Decide whether to stop parsing
_weather_parse_temperature Extract the temperature information
_weather_parse_timestamp Extract the timestamp
_weather_parse_visibility Extract the visibility information
_weather_parse_wind Extract the wind information