<?php
/**
 * Horde_Service_Weather class for abstracting access to various weather
 * providers.
 *
 * Copyright 2011-2017 Horde LLC (http://www.horde.org/)
 *
 * @author   Michael J Rubinsky <mrubinsk@horde.org>
 * @license  http://www.horde.org/licenses/bsd BSD
 * @category Horde
 * @package  Service_Weather
 */

/**
 * Horde_Service_Weather class
 *
 * @author   Michael J Rubinsky <mrubinsk@horde.org>
 * @category Horde
 * @package  Service_Weather
 */

class Horde_Service_Weather
{
    /** Forecast length constants **/
    const FORECAST_3DAY = 3;
    const FORECAST_5DAY = 5;
    const FORECAST_7DAY = 7;
    const FORECAST_10DAY = 10;

    /** Standard forecast summary **/
    const FORECAST_TYPE_STANDARD = 1;

    /** Detailed forecast, contains a day/night component for each day **/
    const FORECAST_TYPE_DETAILED = 2;

    /** Hourly forecast **/
    const FORECAST_TYPE_HOURLY = 3;

    const FORECAST_FIELD_WIND = 'wind';
    const FORECAST_FIELD_PRECIPITATION = 'pop';
    const FORECAST_FIELD_HUMIDITY = 'humidity';
    const FORECAST_FIELD_ACCUMULATION = 'accum';

    /** Unit constants **/
    const UNITS_STANDARD = 1;
    const UNITS_METRIC = 2;

    /** Conversion constants **/
    const CONVERSION_MPH_TO_KNOTS = 0.868976242;
    const CONVERSION_KPH_TO_KNOTS = 0.5399568;
    const CONVERSION_MPH_TO_KPH = 1.609344;
    const CONVERSION_KPH_TO_MPH = 0.621371192;
    const CONVERSION_MB_TO_INCHES = 0.0295301;
    const CONVERSION_KM_TO_SM  = 0.6213699;

    /** Location search types **/
    const SEARCHTYPE_STANDARD = 1;
    const SEARCHTYPE_IP = 2;
    const SEARCHTYPE_ZIP = 3;
    const SEARCHTYPE_CITYSTATE = 4;

    /**
     * Utility function to return textual cardinal compass directions from degress.
     *
     * @param integer $degree  The degree direction (0 - 360).
     *
     * @return string  The cardinal direction.
     * @since 2.3.0
     */
    public static function degToDirection($degree)
    {
        $cardinal = array('N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW');
        $deg_delta = (int)($degree/22.5 + .5);

        return $cardinal[$deg_delta % 16];
    }

    /**
     * Calculate windchill from temperature and windspeed.
     *
     * Temperature has to be entered in deg F, speed in mph!
     *
     * @param double  $temperature  The temperature in degrees F.
     * @param double  $speed        The wind speed in MPH.
     *
     * @return double  The windchill factor.
     * @link    http://www.nws.noaa.gov/om/windchill/
     */
    public static function calculateWindChill($temperature, $speed)
    {
        return (35.74 + 0.6215 * $temperature - 35.75 * pow($speed, 0.16) + 0.4275 * $temperature * pow($speed, 0.16));
    }

    /**
     * Calculate humidity from temperature and dewpoint
     * This is only an approximation, there is no exact formula, this
     * one here is called Magnus-Formula
     *
     * Temperature and dewpoint have to be entered in deg C!
     *
     * @param double $temperature  Temperature in degrees C.
     * @param double $dewPoint     Dewpoint in degrees C.
     *
     * @return  double
     * @link    http://www.faqs.org/faqs/meteorology/temp-dewpoint/
     */
    public static function calculateHumidity($temperature, $dewPoint)
    {
        // First calculate saturation steam pressure for both temperatures
        if ($temperature >= 0) {
            $a = 7.5;
            $b = 237.3;
        } else {
            $a = 7.6;
            $b = 240.7;
        }
        $tempSSP = 6.1078 * pow(10, ($a * $temperature) / ($b + $temperature));

        if ($dewPoint >= 0) {
            $a = 7.5;
            $b = 237.3;
        } else {
            $a = 7.6;
            $b = 240.7;
        }
        $dewSSP  = 6.1078 * pow(10, ($a * $dewPoint) / ($b + $dewPoint));

        return (100 * $dewSSP / $tempSSP);
    }

    /**
     * Calculate dewpoint from temperature and humidity
     * This is only an approximation, there is no exact formula, this
     * one here is called Magnus-Formula
     *
     * Temperature has to be entered in deg C!
     *
     * @param double $temperature  Temperature in degrees C.
     * @param double $humidity     Humidity.
     *
     * @return double
     * @link    http://www.faqs.org/faqs/meteorology/temp-dewpoint/
     */
    public static function calculateDewPoint($temperature, $humidity)
    {
        if ($temperature >= 0) {
            $a = 7.5;
            $b = 237.3;
        } else {
            $a = 7.6;
            $b = 240.7;
        }

        // First calculate saturation steam pressure for temperature
        $SSP = 6.1078 * pow(10, ($a * $temperature) / ($b + $temperature));

        // Steam pressure
        $SP  = $humidity / 100 * $SSP;

        $v   = log($SP / 6.1078, 10);

        return ($b * $v / ($a - $v));
    }

    /**
     * Convert pressure between in, hpa, mb, mm and atm
     *
     * @param double $pressure  The pressure in $from units.
     * @param  string $from  Units converting from.
     * @param  string $to    Units converting to.
     *
     * @return float  The converted pressure
     */
    public static function convertPressure($pressure, $from, $to)
    {
        $factor = array(
            'in' => array(
                'in' => 1,
                'hpa' => 33.863887,
                'mb' => 33.863887,
                'mm' => 25.4,
                'atm' => 0.0334213
            ),
            'hpa' => array(
                'in' => 0.02953,
                'hpa' => 1,
                'mb' => 1,
                'mm' => 0.7500616,
                'atm' => 0.0009869
            ),
            'mb' => array(
                'in' => 0.02953,
                'hpa' => 1,
                'mb' => 1,
                'mm' => 0.7500616,
                'atm' => 0.0009869
            ),
            'mm' => array(
                'in' => 0.0393701,
                'hpa' => 1.3332239,
                'mb' => 1.3332239,
                'mm' => 1,
                'atm' => 0.0013158
            ),
            'atm' => array(
                'in' => 29,921258,
                'hpa' => 1013.2501,
                'mb' => 1013.2501,
                'mm' => 759.999952,
                'atm' => 1
            )
        );

        $from = strtolower($from);
        $to   = strtolower($to);

        return ($pressure * $factor[$from][$to]);
    }

    /**
     * Convert speed between mph, kph, kt, mps, fps and bft
     *
     * Function will return 'false' when trying to convert from
     * Beaufort, as it is a scale and not a true measurement
     *
     * @param double $speed     The speed in $from units.
     * @param string $from      The units to convert from.
     * @param string $to        The units to convert to.
     *
     * @return double|integer|boolean
     * @link    http://www.spc.noaa.gov/faq/tornado/beaufort.html
     */
    public static function convertSpeed($speed, $from, $to)
    {
        $factor = array(
            'mph' => array(
                'mph' => 1,
                'kph' => 1.609344,
                'kt' => 0.8689762,
                'mps' => 0.44704,
                'fps' => 1.4666667
            ),
            'kph' => array(
                'mph' => 0.6213712,
                'kph' => 1,
                'kt' => 0.5399568,
                'mps' => 0.2777778,
                'fps' => 0.9113444
            ),
            'kt'  => array(
                'mph' => 1.1507794,
                'kph' => 1.852,
                'kt' => 1,
                'mps' => 0.5144444,
                'fps' => 1.6878099
            ),
            'mps' => array(
                'mph' => 2.2369363,
                'kph' => 3.6,
                'kt' => 1.9438445,
                'mps' => 1,
                'fps' => 3.2808399
            ),
            'fps' => array(
                'mph' => 0.6818182,
                'kph' => 1.09728,
                'kt' => 0.5924838,
                'mps' => 0.3048,
                'fps' => 1
            )
        );

        $from = strtolower($from);
        $to   = strtolower($to);

        if ($from == 'bft') {
            return false;
        } elseif ($to == 'bft') {
            $beaufort = array(
                  1,   3,   6,  10,
                 16,  21,  27,  33,
                 40,  47,  55,  63
            );
            $speed = round($speed * $factor[$from]['kt'], 0);
            for ($i = 0; $i < sizeof($beaufort); $i++) {
                if ($speed <= $beaufort[$i]) {
                    return $i;
                }
            }
            return sizeof($beaufort);
        } else {
            return ($speed * $factor[$from][$to]);
        }
    }

    /**
     * Convert distance between m, km, ft and sm
     *
     * @param double $distance  The distance in $from units.
     * @param string $from      The units to convert from.
     * @param string $to        The units to convert to.
     *
     * @return double
     */
    public static function convertDistance($distance, $from, $to)
    {
        $factor = array(
            'm' => array(
                'm' => 1,
                'km' => 1000,
                'ft' => 3.280839895,
                'sm' => 0.0006213699
            ),
            'km' => array(
                'm' => 0.001,
                'km' => 1,
                'ft' => 3280.839895,
                'sm' => 0.6213699
            ),
            'ft' => array(
                'm' => 0.3048,
                'km' => 0.0003048,
                'ft' => 1,
                'sm' => 0.0001894
            ),
            'sm' => array(
                'm' => 0.0016093472,
                'km' => 1.6093472,
                'ft' => 5280.0106,
                'sm' => 1
            )
        );
        $to   = strtolower($to);
        $from = strtolower($from);

        return round($distance * $factor[$from][$to]);
    }

    /**
     * Convert temperature between f and c
     *
     * @param double $temperature  The temperature in $from units.
     * @param string $from         Units to convert from.
     * @param string $to           Units to convert to.
     *
     * @return double
     */
    public static function convertTemperature($temperature, $from, $to)
    {
        if ($temperature == 'N/A') {
            return $temperature;
        }
        $from = strtolower(substr($from, 0, 1));
        $to   = strtolower(substr($to, 0, 1));

        $result = array(
            'f' => array(
                'f' => $temperature,
                'c' => ($temperature - 32) / 1.8
            ),
            'c' => array(
                'f' => 1.8 * $temperature + 32,
                'c' => $temperature
            )
        );

        return $result[$from][$to];
    }

}
