new Weather block
Dan Chlopek <[email protected]> Mon, 15 Dec 2003 15:13:44 -0600
| Newsgroups | gmane.comp.horde.jonah |
|---|---|
| Message-ID | <[email protected]> |
Rick Emery wrote: > Quoting Dan Chlopek <[email protected]>: > > > I've been trying to port the ejse driver from Jonah into a standalone Horde > > block that looks up weather by zip code. I'm having some difficulty trying > > to > > get it all working, but once I've managed to get something that works, I'll > > post it to the appropriate list.. > > Great! Well, I finally have something that works. Its my first attempt at php, so it may not be too pretty. It's basically a port of the ejse driver from jonah, along with some functions needed to parse the xml. It works standalone from horde/lib/Block. The block uses the Horde cache interface to cache the weather data. All you need to do is specify your zip code, and a refresh time in the layout preferences. It also does metric/standard conversion. Enjoy. Regards, Dan -- Jonah mailing list Frequently Asked Questions: http://horde.org/faq/ To unsubscribe, mail: [email protected]
weather.php
(application/x-php, 21 KB)
<?php
class Horde_Block_weather extends Horde_Block {
var $_app = 'horde';
var $_type = 'weather';
/**
* The XML parser.
* @var resource $_parser
*/
var $_parser;
/**
* The current conditions.
* @var array $_conditions
*/
var $_conditions;
/**
* The forecast.
* @var array $_forecast
*/
var $_forecast;
/**
* The current day
* @var integer $_currentDay
*/
var $_currentDay;
/**
* Where we are in the XML document.
* @var string $_currentTag
*/
var $_currentTag;
/**
* Keys that tell us where we are in the XML document.
* @var array $_keys
*/
var $_keys = array('cityKey' => '^EXTENDEDWEATHERINFO^INFO^LOCATION',
'tempKey' => '^EXTENDEDWEATHERINFO^INFO^TEMPRATURE',
'realtempKey' => '^EXTENDEDWEATHERINFO^INFO^FEELSLIKE',
'conditionsKey' => '^EXTENDEDWEATHERINFO^INFO^FORECAST',
'visibilityKey' => '^EXTENDEDWEATHERINFO^INFO^VISIBILITY',
'barometerKey' => '^EXTENDEDWEATHERINFO^INFO^PRESSURE',
'uvKey' => '^EXTENDEDWEATHERINFO^INFO^UVINDEX',
'humidityKey' => '^EXTENDEDWEATHERINFO^INFO^HUMIDITY',
'windDirKey' => '^EXTENDEDWEATHERINFO^INFO^WIND',
'regionKey' => '^EXTENDEDWEATHERINFO^INFO^REPORTEDAT',
'dateKey' => '^EXTENDEDWEATHERINFO^INFO^LASTUPDATED',
'forecastDay1Key' => '^EXTENDEDWEATHERINFO^DAY1^DAY',
'forecastDay2Key' => '^EXTENDEDWEATHERINFO^DAY2^DAY',
'forecastDay3Key' => '^EXTENDEDWEATHERINFO^DAY3^DAY',
'forecastDay4Key' => '^EXTENDEDWEATHERINFO^DAY4^DAY',
'forecastDay5Key' => '^EXTENDEDWEATHERINFO^DAY5^DAY',
'forecastDay6Key' => '^EXTENDEDWEATHERINFO^DAY6^DAY',
'forecastDay7Key' => '^EXTENDEDWEATHERINFO^DAY7^DAY',
'forecastHigh1Key' => '^EXTENDEDWEATHERINFO^DAY1^HIGH',
'forecastHigh2Key' => '^EXTENDEDWEATHERINFO^DAY2^HIGH',
'forecastHigh3Key' => '^EXTENDEDWEATHERINFO^DAY3^HIGH',
'forecastHigh4Key' => '^EXTENDEDWEATHERINFO^DAY4^HIGH',
'forecastHigh5Key' => '^EXTENDEDWEATHERINFO^DAY5^HIGH',
'forecastHigh6Key' => '^EXTENDEDWEATHERINFO^DAY6^HIGH',
'forecastHigh7Key' => '^EXTENDEDWEATHERINFO^DAY7^HIGH',
'forecastLow1Key' => '^EXTENDEDWEATHERINFO^DAY1^LOW',
'forecastLow2Key' => '^EXTENDEDWEATHERINFO^DAY2^LOW',
'forecastLow3Key' => '^EXTENDEDWEATHERINFO^DAY3^LOW',
'forecastLow4Key' => '^EXTENDEDWEATHERINFO^DAY4^LOW',
'forecastLow5Key' => '^EXTENDEDWEATHERINFO^DAY5^LOW',
'forecastLow6Key' => '^EXTENDEDWEATHERINFO^DAY6^LOW',
'forecastLow7Key' => '^EXTENDEDWEATHERINFO^DAY7^LOW',
'forecastConditions1Key' => '^EXTENDEDWEATHERINFO^DAY1^FORECAST',
'forecastConditions2Key' => '^EXTENDEDWEATHERINFO^DAY2^FORECAST',
'forecastConditions3Key' => '^EXTENDEDWEATHERINFO^DAY3^FORECAST',
'forecastConditions4Key' => '^EXTENDEDWEATHERINFO^DAY4^FORECAST',
'forecastConditions5Key' => '^EXTENDEDWEATHERINFO^DAY5^FORECAST',
'forecastConditions6Key' => '^EXTENDEDWEATHERINFO^DAY6^FORECAST',
'forecastConditions7Key' => '^EXTENDEDWEATHERINFO^DAY7^FORECAST',
'forecast1Wid' => '^EXTENDEDWEATHERINFO^DAY1^ICONINDEX',
'forecast2Wid' => '^EXTENDEDWEATHERINFO^DAY2^ICONINDEX',
'forecast3Wid' => '^EXTENDEDWEATHERINFO^DAY3^ICONINDEX',
'forecast4Wid' => '^EXTENDEDWEATHERINFO^DAY4^ICONINDEX',
'forecast5Wid' => '^EXTENDEDWEATHERINFO^DAY5^ICONINDEX',
'forecast6Wid' => '^EXTENDEDWEATHERINFO^DAY6^ICONINDEX',
'forecast7Wid' => '^EXTENDEDWEATHERINFO^DAY7^ICONINDEX');
function getParams()
{
return array(
'location' => array(
'type' => 'string',
'name' => _("ZIP Code"),
'default' => ''
),
'expire' => array(
'type' => 'string',
'name' => _("Reload Period"),
'default' => '900'
),
'metric' => array(
'type' => 'enum',
'name' => _("Units"),
'default' => 0,
'values' => array(
0 => _("Standard"),
1 => _("Metric")
)
)
);
}
/**
* The title to go in this block.
*
* @return string The title text.
*/
function _title()
{
return _("Current Weather for zip ") . $this->_params['location'];
}
/**
* The content to go in this block.
*
* @return string The content
*/
function _content()
{
// Get the location and refresh info from the paramaters
$location = $this->_params['location'];
$expire = $this->_params['expire'];
$html = '<table border="0" cellspacing="0" cellpadding="0"><tr><td class="text" valign="top">';
/* Get weekday names and weather info into the .po files. */
array(_("Monday"), _("Tuesday"), _("Wednesday"), _("Thursday"), _("Friday"), _("Saturday"), _("Sunday"));
array(_("windy"), _("thunder storms"), _("snow"), _("rain"),
_("heavy rain"), _("sleet"), _("light flurries"),
_("flurries"), _("snow and wind"), _("dust"), _("fog"),
_("haze"), _("smoke"), _("cold"), _("cloudy"), _("mostly cloudy"),
_("partly cloudy"), _("sunny"), _("mostly sunny"), _("hot"));
// Get the weather info
$weather = $this->_getWeather('http://www.ejse.com/WeatherService/Service.asmx/GetExtendedWeatherInfo?zipCode=' . $location,
$expire);
$html .= '<table border="0" cellspacing="0" cellpadding="1"><tr><td class="control"><b>';
$sep = '';
if (!empty($weather['conditions']['city'])) {
$html .= $weather['conditions']['city'];
$sep = ', ';
}
if (!empty($weather['conditions']['country']) && ($weather['conditions']['country'] != 'USA')) {
$html .= $sep . $weather['conditions']['country'];
} elseif (!empty($weather['conditions']['state'])) {
$html .= $sep . $weather['conditions']['state'];
}
$html .= '</b>';
if (!empty($weather['conditions']['date'])) {
$html = sprintf(_("%s at %s"), $html, strftime('%X', strtotime($weather['conditions']['date'])));
}
$html .= '</td></tr><tr><td>';
if (isset($weather['conditions']['temp'])) {
$html .= sprintf(_("The temperature is <b>%s</b>, "),
$this->_params['metric'] ?
round((($weather['conditions']['temp'] - 32) * 5) / 9) . '°C' :
$weather['conditions']['temp'] . '°F');
}
if (isset($weather['conditions']['humidity'])) {
$html .= sprintf(_("humidity is at %s"), $weather['conditions']['humidity'] . '% ');
}
if (isset($weather['conditions']['windDir'])) {
if ($weather['conditions']['windDir'] == 'CALM') {
$html .= _("and there is no wind measured. ");
} elseif (!empty($weather['conditions']['windDir'])) {
if (isset($weather['conditions']['windStrength'])) {
$html .= sprintf(_("and the wind is %s at %s. "),
$weather['conditions']['windDir'],
$this->_params['metric'] ?
round($weather['conditions']['windStrength'] * 1609 / 1000) . _(" km/h") :
$weather['conditions']['windStrength'] . _("mph"));
} else {
$html .= sprintf(_("and the wind is %s. "), $weather['conditions']['windDir']);
}
}
}
if (isset($weather['conditions']['realtemp'])) {
$html .= sprintf(_("It feels like %s. "),
$this->_params['metric'] ?
round((($weather['conditions']['realtemp'] - 32) * 5) / 9) . '°C' :
$weather['conditions']['realtemp'] . '°F');
}
if (isset($weather['conditions']['barometer'])) {
$html .= sprintf(_("Barometer shows %s. "),
$this->_params['metric'] ?
round($weather['conditions']['barometer'] * 3386 / 100) . ' hP' :
$weather['conditions']['barometer']);
}
$visibility = '';
if (isset($weather['conditions']['visibility'])) {
$visibility = sprintf(_("Visibility is %s"),
$weather['conditions']['visibility'] == 999 ?
_("infinite") :
($this->_params['metric'] ?
round(1609 * $weather['conditions']['visibility'] / 1000) . _(" km") :
$weather['conditions']['visibility'] . _(" miles")));
}
$html .= $visibility;
if (isset($weather['conditions']['uv'])) {
if ($weather['conditions']['uv'] <= 2.5) {
$uv = sprintf(_("minimal %s"), $weather['conditions']['uv']);
} elseif ($weather['conditions']['uv'] <= 4.5) {
$uv = sprintf(_("low %s"), $weather['conditions']['uv']);
} elseif ($weather['conditions']['uv'] <= 6.5) {
$uv = sprintf(_("moderate %s"), $weather['conditions']['uv']);
} elseif ($weather['conditions']['uv'] <= 9.5) {
$uv = sprintf(_("high %s. Use sunscreen"), $weather['conditions']['uv']);
} else {
$uv = sprintf(_("very high %s. Stay indoors"), $weather['conditions']['uv']);
}
$html .= sprintf(empty($visibility) ? _("The UV index is a %s. ") : _(" and the UV index is a %s. "), $uv);
} elseif (!empty($visibility)) {
$html .= '. ';
}
$html .= '</td></tr>';
if (isset($weather['forecast']) && (count($weather['forecast']) > 1)) {
$html .= '<tr><td class="control"><b>' . sprintf(_("%d-Day Forecast"), count($weather['forecast'])) . '</b></td></tr><tr><td><table border="0" align="center"><tr>';
foreach ($weather['forecast'] as $day => $forecast) {
$html .= '<td align="center" width="75"><b>' . _($day) . '</b><br /><span style="color:red">';
if ($this->_params['metric']) {
$html .= round((($forecast['high'] - 32) * 5) / 9);
} else {
$html .= $forecast['high'];
}
$html .= '°</span>/<span style="color:blue">';
if ($this->_params['metric']) {
$html .= round((($forecast['low'] - 32) * 5) / 9);
} else {
$html .= $forecast['low'];
}
$html .= '°</span><br />';
if (isset($forecast['conditions'])) {
$html .= Horde::img('weather/' . $forecast['wid'] . '.gif', gettext(String::lower($forecast['conditions'])), 'align="middle"', '/horde3/jonah/graphics');
}
$html .= '</td>';
}
$html .= '</tr></table></td></tr>';
}
$html .= '</table>';
$html .= '</td></tr></table>';
return $html;
}
function _getWeather($location, $expire)
{
global $conf;
require_once HORDE_LIBS . 'Horde/Cache.php';
$cache = &Horde_Cache::singleton($conf['cache']['driver'], Horde::getDriverConfig('cache', $conf['cache']['driver']));
if (is_a($cache, 'PEAR_Error')) {
Horde::fatal($cache, __FILE__, __LINE__);
}
$weather = @unserialize($cache->getData('Block.ejse_weather.' . $location, 'false;', $expire));
if (!$weather) {
$this->_updateCache($location, $this->_parseUrl($location));
$weather = @unserialize($cache->getData('Block.ejse_weather.' . $location, "Horde_Block_weather::_updateCache('$location');", 1));
}
return $weather;
}
function _updateCache($location, $data = null)
{
static $weather;
if (is_null($data)) {
return isset($weather[$location]) ?
(is_a($weather[$location], 'PEAR_Error') ? '' : $weather[$location])
: null;
}
$weather[$location] = $data;
Horde::logMessage($weather[$location], __FILE__, __LINE__, PEAR_LOG_ERR);
}
function _parseUrl($url)
{
$this->_initialize();
if (!$this->_parser) {
return PEAR::raiseError(_("Could not find XML parser handle."));
}
$data = $this->_readUrl($url);
if (is_a($data, 'PEAR_Error')) {
Horde::logMessage($data, __FILE__, __LINE__, PEAR_LOG_ERR);
return '';
}
$data = preg_replace(array('|<br />|', '|<b>.*</b>|'), '', $data);
if (!xml_parse($this->_parser, $data)) {
return PEAR::raiseError(sprintf(_("XML error: %s at line %d"), xml_error_string(xml_get_error_code($this->_parser)), xml_get_current_line_number($this->_parser)));
}
xml_parser_free($this->_parser);
return serialize(array('conditions' => $this->_conditions,
'forecast' => $this->_forecast));
}
function _initialize()
{
$this->_conditions = array();
$this->_forecast = array();
$this->_currentDay = '';
$this->_currentTag = '';
// Create the XML parser.
$this->_parser = xml_parser_create();
xml_set_object($this->_parser, $this);
xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, true);
xml_set_element_handler($this->_parser, '_start', '_end');
xml_set_character_data_handler($this->_parser, '_data');
xml_set_default_handler($this->_parser, '_default');
xml_set_processing_instruction_handler($this->_parser, '');
xml_set_external_entity_ref_handler($this->_parser, '');
}
function _readUrl($url)
{
$options['method'] = 'GET';
$options['timeout'] = 5;
$options['allowRedirects'] = true;
require_once 'HTTP/Request.php';
$http = &new HTTP_Request($url, $options);
@$http->sendRequest();
if ($http->getResponseCode() != 200) {
return PEAR::raiseError(sprintf(_("Could not open %s."), $url));
}
return $http->getResponseBody();
}
/**
* Start collecting data about a new element.
*/
function _start($parser, $name, $attribs)
{
$this->_currentTag .= "^$name";
}
/**
* Handle the ends of XML elements - wrap up whatever we've been
* putting together and store it for safekeeping.
*/
function _end($parser, $name)
{
$currentPosition = strrpos($this->_currentTag, '^');
$this->_currentTag = substr($this->_currentTag, 0, $currentPosition);
}
/**
* The handler for character data encountered in the XML file.
*/
function _data($parser, $data)
{
$data = trim($data);
switch ($this->_currentTag) {
case $this->_keys['cityKey']:
list($this->_conditions['city'], $this->_conditions['state']) = split(',', $data, 2);
break;
case $this->_keys['regionKey']:
$this->_conditions['region'] = $data;
break;
case $this->_keys['dateKey']:
list($time, $ampm) = preg_split('/^\S+, \S+ \S+, \S+, at ([0-9:]+) (\S+) .*/', $data, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
$this->_conditions['date'] = "$time $ampm";
break;
case $this->_keys['tempKey']:
$this->_conditions['temp'] = round($data);
break;
case $this->_keys['windDirKey']:
list($this->_conditions['windDir'], $this->_conditions['windStrength']) = split(' at ', $data, 2);
$this->_conditions['windStrength'] = substr($this->_conditions['windStrength'], 0, -3);
break;
case $this->_keys['barometerKey']:
$this->_conditions['barometer'] = substr($data,0,5);
$this->_conditions['barometer'] = $data;
break;
case $this->_keys['humidityKey']:
$this->_conditions['humidity'] = round($data);
break;
case $this->_keys['realtempKey']:
$this->_conditions['realtemp'] = round($data);
break;
case $this->_keys['uvKey']:
$this->_conditions['uv'] = round($data);
break;
case $this->_keys['visibilityKey']:
$this->_conditions['visibility'] = $data;
break;
case $this->_keys['conditionsKey']:
$this->_conditions['conditions'] = $data;
break;
case $this->_keys['forecastDay1Key']:
case $this->_keys['forecastDay2Key']:
case $this->_keys['forecastDay3Key']:
case $this->_keys['forecastDay4Key']:
case $this->_keys['forecastDay5Key']:
case $this->_keys['forecastDay6Key']:
case $this->_keys['forecastDay7Key']:
$this->_currentDay = $data;
$this->_forecast[$this->_currentDay] = array();
break;
case $this->_keys['forecastHigh1Key']:
case $this->_keys['forecastHigh2Key']:
case $this->_keys['forecastHigh3Key']:
case $this->_keys['forecastHigh4Key']:
case $this->_keys['forecastHigh5Key']:
case $this->_keys['forecastHigh6Key']:
case $this->_keys['forecastHigh7Key']:
$this->_forecast[$this->_currentDay]['high'] = round($data);
break;
case $this->_keys['forecastLow1Key']:
case $this->_keys['forecastLow2Key']:
case $this->_keys['forecastLow3Key']:
case $this->_keys['forecastLow4Key']:
case $this->_keys['forecastLow5Key']:
case $this->_keys['forecastLow6Key']:
case $this->_keys['forecastLow7Key']:
$this->_forecast[$this->_currentDay]['low'] = round($data);
break;
case $this->_keys['forecastConditions1Key']:
case $this->_keys['forecastConditions2Key']:
case $this->_keys['forecastConditions3Key']:
case $this->_keys['forecastConditions4Key']:
case $this->_keys['forecastConditions5Key']:
case $this->_keys['forecastConditions6Key']:
case $this->_keys['forecastConditions7Key']:
$this->_forecast[$this->_currentDay]['conditions'] = $data;
break;
case $this->_keys['forecast1Wid']:
case $this->_keys['forecast2Wid']:
case $this->_keys['forecast3Wid']:
case $this->_keys['forecast4Wid']:
case $this->_keys['forecast5Wid']:
case $this->_keys['forecast6Wid']:
case $this->_keys['forecast7Wid']:
switch ($data) {
case 23:
case 24:
$data = 1;
break;
case 4:
case 17:
case 35:
case 37:
case 38:
$data = 3;
break;
case 9:
case 11:
$data = 6;
break;
case 39:
case 40:
$data = 18;
break;
case 8:
case 10:
$data = 7;
break;
case 16:
case 41:
case 42:
$data = 15;
break;
case 28:
$data = 27;
break;
case 30:
case 44:
$data = 29;
break;
case 32:
$data = 31;
break;
case 34:
$data = 33;
break;
}
$this->_forecast[$this->_currentDay]['wid'] = $data;
break;
}
}
/**
* Handles things that we don't recognize. A no-op.
*/
function _default($parser, $data)
{
}
}