{ Hi! I'm Mike }
I'm a core developer with The Horde Project and a founding parter of Horde LLC - the company behind the world's most flexible groupware platform. This is my personal blog full of random thoughts about development and life in general.
August 13, 2016

METAR/TAF support in Horde_Service_Weather

Back in the Horde 3 days, to power the weather portal block, Horde used PEAR_Services_Weather to obtain weather.com's weather data. When Weather.com discontinued its free weather API, sometime in the Horde 4 release cycle, we created Horde_Service_Weather to interface with a host of other weather APIs.

However, for those users that used METAR and TAF weather data - using the "METAR" portal block - you were still stuck using the older PEAR library since Horde did not have support for this type of data. With the release of PHP 7 this has become even more problematic since the PEAR_Services_Weather library does not support PHP 7.

Enter Horde_Service_Weather_Metar, the latest addition to the Horde_Service_Weather library. It adds support for decoding METAR and TAF data from either a remote web server or a local file. While it is not a 100% drop-in replacement for the PEAR library, it is close. For the end user, this is pretty much all you need to know - your METAR weather data will still be available (and the portal block will look prettier). If you are developer and are interested in the specifics, read on.

Two APIs

As part of the Horde_Service_Weather library, it supports the same API as the other weather drivers. However, METAR and TAF data are not really the same type of weather data as a traditional 3 or 5 day forecast. These are designed for aviation purposes. For example, whereas in the other weather drivers each "period" represents one day, in the METAR driver, the forecast periods (or even the number of periods) are not pre-defined. Each period is only a few hours - with the entire forecast usually covering only 24 hours. TAF data also contains data not typically provided in a typical consumer weather forecast - such as the type/amount/height of each cloud layer. Given this, we provide an additional method to obtain the more detailed data.

// Note that below we use the global $injector to obtain object instances.
// If not using the $injector, substitute your own instances in place of the $injector call.
$params = array(
    'http_client' => $injector->createInstance('Horde_Core_Factory_HttpClient')->create(),
    'cache' => $injector->getInstance('Horde_Cache'),
    'cache_lifetime' => 3600,
    'db' => $injector->getInstance('Horde_Db_Adapter')
);
$weather = new Horde_Service_Weather_Metar($params);

// METAR (Current conditions)
$current = $weather->getCurrentConditions('KPHL');
$data = $current->getRawData();

// Current TAF
$forecast = $weather->getForecast('KPHL');
$data = $forecast->getRawData();

The Horde_Service_Weather_Current_Metar::getRawData() method returns all the parsed METAR properties. We use the same key names as the PEAR_Services_Weather library, so this data can be used directly in place of the PEAR library's data.

Likewise, the Horde_Service_Weather_Forecast_Taf::getRawData() method returns the same data structure as the PEAR_Services_Weather_Metar::getForecast() method.

As mentioned, the "normal" API is still supported and you are free to use it. However, the data available is limited. For example, you can still iterate over the forecast periods, but the information available in each period is still limited to the properties of the Horde_Service_Weather_Period_Base object. Also note that TAF periods only contain weather information that is different from the main forecast section so period objects may not contain all expected typical information at all. This is why it's best to use the getRaw() methods as described above for METAR/TAF data.

// Note that below we use the global $injector to obtain object instances.
// If not using the $injector, substitute your own instances in place of the $injector call.
$params = array(
    'http_client' => $injector->createInstance('Horde_Core_Factory_HttpClient')->create(),
    'cache' => $injector->getInstance('Horde_Cache'),
    'cache_lifetime' => 3600,
    'db' => $injector->getInstance('Horde_Db_Adapter')
);
$weather = new Horde_Service_Weather_Metar($params);

// Current TAF
$forecast = $weather->getForecast('KPHL');
foreach ($forecast as $period) {
   $humidity = $period->humidity;
   // etc...
}

Local or Remote

Horde_Service_Weather_Metar supports obtaining the data from a remote http service or from a local file for maximum flexibility. By default, it tries the NOAA links. If you want to use a different service, you can provide the path in the constructor's parameters.

$params = array(
    'http_client' => $injector->createInstance('Horde_Core_Factory_HttpClient')->create(),
    'cache' => $injector->getInstance('Horde_Cache'),
    'cache_lifetime' => 3600,
    'db' => $injector->getInstance('Horde_Db_Adapter'),
   'metar_path' => 'http://example.com/metar',
   'taf_path' => 'http://example.com/taf'
);
$weather = new Horde_Service_Weather_Metar($params);

Or for those that may already sync weather data, or have their own weather observation systems, you can point to a local file:

$params = array(
    'http_client' => $injector->createInstance('Horde_Core_Factory_HttpClient')->create(),
    'cache' => $injector->getInstance('Horde_Cache'),
    'cache_lifetime' => 3600,
    'db' => $injector->getInstance('Horde_Db_Adapter'),
   'metar_path' => '/var/weather/metar',
   'taf_path' => '/var/weather/taf'
);
$weather = new Horde_Service_Weather_Metar($params);

You can even mix and match the two - if you have a local METAR file but not a local TAF file for example.

Location Database

The PEAR library also had a script that would build a local database of airport weather reporting locations. This was also ported to Horde_Service_Weather as a migration script. Running the migration in the normal way automatically downloads the latest airport datafile and builds the necessary tables. See the horde-db-migrate tool for more information on running migrations.

Use of the database isn't mandatory and if the 'db' parameter is not passed to the constructor it will not be used. However, the database is required for searching location names and performing autocompletion of location names. For example, the following code will search the location database for any locations (either the location name of the ICAO airport code) beginning with $location.

$weather =  new Horde_Service_Weather_Metar(array(
    'cache' => $injector->getInstance('Horde_Cache'),
    'cache_lifetime' => $conf['weather']['params']['lifetime'],
    'http_client' => $injector->createInstance('Horde_Core_Factory_HttpClient')->create(),
    'db' => $injector->getInstance('Horde_Db_Adapter'))
 );

 $locations = $weather->autocompleteLocation($location);

Portal Block

We have also improved Horde's METAR weather portal block by tweaking the layout a bit and by adding the ability dynamically change the location using an ajax autocompleter - just like the exiting weather block.

Why keep both weather blocks in Horde? As mentioned, METAR and TAF data are specialized data designed for aviation use. It doesn't fit neatly into a traditional weather forecast layout. It contains more detailed information and shorter forecast periods. Providing a specific block for this data allows the full data set to be represented without overly complicating the "normal" weather portal display.