Overview

Packages

  • Service
    • Weather

Classes

  • Horde_Service_Weather
  • Horde_Service_Weather_Base
  • Horde_Service_Weather_Current_Base
  • Horde_Service_Weather_Current_Google
  • Horde_Service_Weather_Current_WeatherUnderground
  • Horde_Service_Weather_Current_Wwo
  • Horde_Service_Weather_Exception
  • Horde_Service_Weather_Exception_InvalidProperty
  • Horde_Service_Weather_Forecast_Base
  • Horde_Service_Weather_Forecast_Google
  • Horde_Service_Weather_Forecast_WeatherUnderground
  • Horde_Service_Weather_Forecast_Wwo
  • Horde_Service_Weather_Google
  • Horde_Service_Weather_Period_Base
  • Horde_Service_Weather_Period_Google
  • Horde_Service_Weather_Period_WeatherUnderground
  • Horde_Service_Weather_Period_Wwo
  • Horde_Service_Weather_Station
  • Horde_Service_Weather_Translation
  • Horde_Service_Weather_WeatherUnderground
  • Horde_Service_Weather_Wwo
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * This file contains the Horde_Service_Weather class for communicating with
  4:  * the World Weather Online API.
  5:  *
  6:  * Copyright 2011-2012 Horde LLC (http://www.horde.org/)
  7:  *
  8:  * @author   Michael J Rubinsky <mrubinsk@horde.org>
  9:  * @license  http://www.horde.org/licenses/bsd BSD
 10:  * @category Horde
 11:  * @package  Service_Weather
 12:  */
 13: 
 14: /**
 15:  * Horde_Service_Weather_Wwo
 16:  *
 17:  * @author   Michael J Rubinsky <mrubinsk@horde.org>
 18:  * @category Horde
 19:  * @package  Service_Weather
 20:  */
 21: class Horde_Service_Weather_Wwo extends Horde_Service_Weather_Base
 22:  {
 23: 
 24:     const API_URL = 'http://free.worldweatheronline.com/feed/weather.ashx';
 25:     const SEARCH_URL = 'http://www.worldweatheronline.com/feed/search.ashx';
 26: 
 27:     public $title = 'World Weather Online';
 28:     public $link = 'http://worldweatheronline.com';
 29: 
 30:     protected $_key;
 31: 
 32: 
 33:     /**
 34:      * Icon map for wunderground. Not some are returned as
 35:      * "sky" conditions and some as "condition" icons. Public
 36:      * so it can be overridded in client code if desired.
 37:      */
 38:     public $iconMap = array(
 39:         'wsymbol_0001_sunny' => '32.png',
 40:         'wsymbol_0002_sunny_intervals' => '30.png',
 41:         'wsymbol_0003_white_cloud' => '26.png',
 42:         'wsymbol_0004_black_low_cloud' => '26.png',
 43:         'wsymbol_0006_mist' => '34.png',
 44:         'wsymbol_0007_fog' => '20.png',
 45:         'wsymbol_0008_clear_sky_night' => '33.png',
 46:         'wsymbol_0009_light_rain_showers' => '11.png',
 47:         'wsymbol_0010_heavy_rain_showers' => '12.png',
 48:         'wsymbol_0011_light_snow_showers' => '14.png',
 49:         'wsymbol_0012_heavy_snow_showers' => '16.png',
 50:         'wsymbol_0013_sleet_showers' => '7.png',
 51:         'wsymbol_0016_thundery_showers' => '0.png',
 52:         'wsymbol_0017_cloudy_with_light_rain' => '11.png',
 53:         'wsymbol_0018_cloudy_with_heavy_rain' => '12.png',
 54:         'wsymbol_0019_cloudy_with_light_snow' => '13.png',
 55:         'wsymbol_0020_cloudy_with_heavy_snow' => '16.png',
 56:         'wsymbol_0021_cloudy_with_sleet' => '8.png',
 57:         'wsymbol_0024_thunderstorms' => '0.png',
 58:         'wsymbol_0025_light_rain_showers_night' => '40.png',
 59:         'wsymbol_0026_heavy_rain_showers_night' => '30.png',
 60:         'wsymbol_0027_light_snow_showers_night' => '41.png',
 61:         'wsymbol_0028_heavy_snow_showers_night' => '42.png',
 62:         'wsymbol_0029_sleet_showers_night' => '7.png',
 63:         'wsymbol_0032_thundery_showers_night' => '47.png',
 64:         'wsymbol_0033_cloudy_with_light_rain_night' => '45.png',
 65:         'wsymbol_0034_cloudy_with_heavy_rain_night' => '45.png',
 66:         'wsymbol_0035_cloudy_with_light_snow_night' => '46.png',
 67:         'wsymbol_0036_cloudy_with_heavy_snow_night' => '46.png',
 68:         'wsymbol_0037_cloudy_with_sleet_night' => '8.png',
 69:         'wsymbol_0040_thunderstorms_night' => '47.png'
 70:     );
 71: 
 72:     /**
 73:      * Constructor
 74:      *
 75:      * @param array $params                                  Parameters.
 76:      *<pre>
 77:      *  'http_client'  - Required http client object
 78:      *  'apikey'       - Required API key for wunderground.
 79:      *</pre>
 80:      *
 81:      * @return Horde_Service_Weather_Base
 82:      */
 83:     public function __construct(array $params = array())
 84:     {
 85:         // Check required api key parameters here...
 86:         if (empty($params['apikey'])) {
 87:             throw new InvalidArgumentException('Missing required API Key parameter.');
 88:         }
 89:         $this->_key = $params['apikey'];
 90:         unset($params['apikey']);
 91:         parent::__construct($params);
 92:     }
 93: 
 94:     /**
 95:      * Obtain the current observations.
 96:      *
 97:      * @return Horde_Service_Weather_Current
 98:      */
 99:     public function getCurrentConditions($location)
100:     {
101:         $this->_getCommonElements($location);
102:         return $this->_current;
103:     }
104: 
105:     /**
106:      * Obtain the forecast for the current location.
107:      *
108:      * @see Horde_Service_Weather_Base#getForecast
109:      */
110:     public function getForecast(
111:         $location,
112:         $length = Horde_Service_Weather::FORECAST_3DAY,
113:         $type = Horde_Service_Weather::FORECAST_TYPE_STANDARD)
114:     {
115:         $this->_getCommonElements($location);
116:         return $this->_forecast;
117:     }
118: 
119:     /**
120:      * Search for a valid location code.
121:      *
122:      * @param  string $location  A location search string like e.g., Boston,MA
123:      * @param  integer $type     The type of search being performed.
124:      *
125:      * @return string  The search location suitable to use directly in a
126:      *                 weather request.
127:      */
128:     public function searchLocations($location, $type = Horde_Service_Weather::SEARCHTYPE_STANDARD)
129:     {
130:         switch ($type) {
131:         case Horde_Service_Weather::SEARCHTYPE_STANDARD:
132:         case Horde_Service_Weather::SEARCHTYPE_IP;
133:             return $this->_parseSearchLocations($this->_searchLocations($location));
134:         }
135:     }
136: 
137: 
138:     public function autocompleteLocation($search)
139:     {
140:         $url = new Horde_Url(self::SEARCH_URL);
141:         $url->add(array(
142:             'query' => $search,
143:             'format' => 'json',
144:             'num_of_results' => 25));
145: 
146:         return $this->_parseAutocomplete($this->_makeRequest($url));
147:     }
148: 
149:     /**
150:      * Get array of supported forecast lengths.
151:      *
152:      * @return array The array of supported lengths.
153:      */
154:      public function getSupportedForecastLengths()
155:      {
156:          return array(
157:             3 => Horde_Service_Weather::FORECAST_3DAY,
158:             5 => Horde_Service_Weather::FORECAST_5DAY
159:          );
160:      }
161: 
162:     /**
163:      * Weather Underground allows requesting multiple features per request,
164:      * and only counts it as a single request against your API key. So we trade
165:      * a bit of request time/traffic for a smaller number of requests to obtain
166:      * information for e.g., a typical weather portal display.
167:      */
168:     protected function _getCommonElements($location, $length = Horde_Service_Weather::FORECAST_5DAY)
169:     {
170:         if (!empty($this->_current) && $location == $this->_lastLocation
171:             && $this->_lastLength == $length) {
172:             return;
173:         }
174: 
175:         $this->_lastLength = $length;
176:         $this->_lastLocation = $location;
177: 
178:         $url = new Horde_Url(self::API_URL);
179:         // Not sure why, but Wwo chokes if we urlencode the location?
180:         $url->add(array(
181:             'q' => $location,
182:             'num_of_days' => $length,
183:             'includeLocation' => 'yes',
184:             'localObsTime' => 'yes'));
185: 
186:         $results = $this->_makeRequest($url);
187:         $station = $this->_parseStation($results->data->nearest_area[0]);
188: 
189:         // Current conditions
190:         $this->_current = $this->_parseCurrent($results->data->current_condition);
191: 
192:         // Sunrise/Sunset
193:         $date = $this->_current->time;
194:         $station->sunset = new Horde_Date(
195:             date_sunset(
196:                 $date->timestamp(),
197:                 SUNFUNCS_RET_TIMESTAMP,
198:                 $station->lat,
199:                 $station->lon)
200:         );
201:         $station->sunrise = new Horde_Date(
202:             date_sunrise(
203:                 $date->timestamp(),
204:                 SUNFUNCS_RET_TIMESTAMP,
205:                 $station->lat,
206:                 $station->lon)
207:         );
208:         $station->time = (string)$date;
209:         $this->_station = $station;
210:         $this->_forecast = $this->_parseForecast($results->data->weather);
211:     }
212: 
213:     /**
214:      * Parses the JSON response for a location request into a station object.
215:      *
216:      * @param  StdClass $station  The response from a Location request.
217:      *
218:      * @return Horde_Service_Weather_Station
219:      */
220:     protected function _parseStation($station)
221:     {
222:         $properties = array(
223:             // @TODO: can we parse cith/state from results?
224:             'name' => $station->areaName[0]->value . ', ' . $station->region[0]->value,
225:             'city' => $station->areaName[0]->value,
226:             'state' => $station->region[0]->value,
227:             'country' => $station->country[0]->value,
228:             'country_name' => '',
229:             'tz' => '', // Not provided, can we assume it's the location's local?
230:             'lat' => $station->latitude,
231:             'lon' => $station->longitude,
232:             'zip' => '',
233:             'code' => $station->latitude . ',' . $station->longitude
234:         );
235: 
236:         return new Horde_Service_Weather_Station($properties);
237:     }
238: 
239:     /**
240:      * Parses the forecast data.
241:      *
242:      * @param stdClass $forecast The result of the forecast request.
243:      *
244:      * @return Horde_Service_Weather_Forecast_Wwo  The forecast.
245:      */
246:     protected function _parseForecast($forecast)
247:     {
248:         $forecast = new Horde_Service_Weather_Forecast_Wwo($forecast, $this);
249:         return $forecast;
250:     }
251: 
252:     /**
253:      * Parse the current_conditions response.
254:      *
255:      * @param  stdClass $current  The current_condition request response object
256:      *
257:      * @return Horde_Service_Weather_Current
258:      */
259:     protected function _parseCurrent($current)
260:     {
261:         // The Current object takes care of the parsing/mapping.
262:         $current = new Horde_Service_Weather_Current_Wwo($current[0], $this);
263:         return $current;
264:     }
265: 
266:     protected function _parseAutocomplete($results)
267:     {
268:         $return = array();
269:         if (!empty($results->search_api->result)) {
270:             foreach($results->search_api->result as $result) {
271:                 if (!empty($result->region[0]->value)) {
272:                     $new = new stdClass();
273:                     $new->name = $result->areaName[0]->value . ', ' . $result->region[0]->value;
274:                     $new->code = $result->latitude . ',' . $result->longitude;
275:                     $return[] = $new;
276:                 }
277:             }
278:         }
279: 
280:         return $return;
281:     }
282: 
283:     /**
284:      * Execute a location search.
285:      *
286:      * @param  string $location The location text to search.
287:      *
288:      * @return string  The location code result(s).
289:      */
290:     protected function _searchLocations($location)
291:     {
292:         $url = new Horde_Url(self::SEARCH_URL);
293:         $url = $url->add(array(
294:             'timezone' => 'yes',
295:             'query' => $location,
296:             'num_of_results' => 10));
297: 
298:         return $this->_makeRequest($url);
299:     }
300: 
301:     protected function _parseSearchLocations($response)
302:     {
303:         if (!empty($response->error)) {
304:             throw new Horde_Service_Weather_Exception($response->error->msg);
305:         }
306: 
307:         // Wwo's location search is pretty useless. It *always* returns multiple
308:         // matches, even if you pass an explicit identifier. We need to ignore
309:         // these, and hope for the best.
310:         if (!empty($response->search_api->result)) {
311:             $results = array();
312:             return $this->_parseStation($response->search_api->result[0]);
313:         }
314: 
315:         return array();
316:     }
317: 
318:     /**
319:      * Make the remote API call.
320:      *
321:      * @param Horde_Url $url  The endpoint.
322:      *
323:      * @return mixed  The unserialized results form the remote API call.
324:      * @throws Horde_Service_Weather_Exception
325:      */
326:     protected function _makeRequest(Horde_Url $url)
327:     {
328:         $url->add(
329:             array(
330:                 'format' => 'json',
331:                 'key' => $this->_key)
332:         )->setRaw(true);
333: 
334:         $cachekey = md5('hordeweather' . $url);
335:         if ((!empty($this->_cache) && !$results = $this->_cache->get($cachekey, $this->_cache_lifetime)) ||
336:             empty($this->_cache)) {
337:             $response = $this->_http->get($url);
338:             if (!$response->code == '200') {
339:                 Horde::logMessage($response->getBody());
340:                 throw new Horde_Service_Weather_Exception($response->code);
341:             }
342:             $results = $response->getBody();
343:             if (!empty($this->_cache)) {
344:                $this->_cache->set($cachekey, $results);
345:             }
346:         }
347:         $results = Horde_Serialize::unserialize($results, Horde_Serialize::JSON);
348:         if (!($results instanceof StdClass)) {
349:             throw new Horde_Service_Weather_Exception('Error, unable to decode response.');
350:         }
351: 
352:         return $results;
353:     }
354: 
355:  }
API documentation generated by ApiGen