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 weather underground service.
  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_WeatherUnderground.
 16:  *
 17:  * @author   Michael J Rubinsky <mrubinsk@horde.org>
 18:  * @category Horde
 19:  * @package  Service_Weather
 20:  */
 21: class Horde_Service_Weather_WeatherUnderground extends Horde_Service_Weather_Base
 22:  {
 23: 
 24:     const API_URL = 'http://api.wunderground.com';
 25: 
 26:     public $logo = 'weather/wundergroundlogo.png';
 27: 
 28:     /**
 29:      * Language to request strings from Google in.
 30:      *
 31:      * @var string
 32:      */
 33:     protected $_language = 'en';
 34: 
 35:     /**
 36:      * Icon map for wunderground. Not some are returned as
 37:      * "sky" conditions and some as "condition" icons. Public
 38:      * so it can be overridded in client code if desired.
 39:      */
 40:     public $iconMap = array(
 41:         'chanceflurries' => '15.png',
 42:         'chancerain' => '11.png',
 43:         'chancesleet' => '8.png',
 44:         'chancesnow' => '14.png',
 45:         'chancetstorms' => '3.png',
 46:         'clear' => '32.png',
 47:         'cloudy' => '26.png',
 48:         'flurries' => '14.png',
 49:         'fog' => '20.png',
 50:         'hazy' => '21.png',
 51:         'mostlycloudy' => '28.png',
 52:         'mostlysunny' => '34.png',
 53:         'partlycloudy' => '30.png',
 54:         'partlysunny' => '30.png',
 55:         'sleet' => '10.png',
 56:         'rain' => '12.png',
 57:         'snow' => '16.png',
 58:         'sunny' => '32.png',
 59:         'tstorms' => '3.png',
 60: 
 61:         // Nighttime
 62:         'nt_chanceflurries' => '46.png',
 63:         'nt_chancerain' => '45.png',
 64:         'nt_chancesleet' => '10.png',
 65:         'nt_chancesnow' => '46.png',
 66:         'nt_chancetstorms' => '45.png',
 67:         'nt_clear' => '31.png',
 68:         'nt_cloudy' => '26.png',
 69:         'nt_flurries' => '46.png',
 70:         'nt_fog' => '20.png',
 71:         'nt_hazy' => '21.png',
 72:         'nt_mostlycloudy' => '45.png',
 73:         'nt_partlycloudy' => '29.png',
 74:         'nt_sleet' => '10.png',
 75:         'nt_rain' => '45.png',
 76:         'nt_snow' => '46.png',
 77:         'nt_tstorms' => '47.png'
 78:     );
 79: 
 80:     /**
 81:      * Constructor
 82:      *
 83:      * @param array $params                                  Parameters.
 84:      *<pre>
 85:      *  'http_client'  - Required http client object
 86:      *  'apikey'       - Required API key for wunderground.
 87:      *</pre>
 88:      *
 89:      * @return Horde_Service_Weather_Base
 90:      */
 91:     public function __construct(array $params = array())
 92:     {
 93:         // Check required api key parameters here...
 94:         if (empty($params['apikey'])) {
 95:             throw new InvalidArgumentException('Missing required API Key parameter.');
 96:         }
 97:         if (!empty($params['language'])) {
 98:             $this->_language = $params['language'];
 99:         }
100:         $this->_apiKey = $params['apikey'];
101:         unset($params['apikey']);
102:         parent::__construct($params);
103:     }
104: 
105:     /**
106:      * Obtain the current observations.
107:      *
108:      * @return Horde_Service_Weather_Current
109:      */
110:     public function getCurrentConditions($location)
111:     {
112:         $this->_getCommonElements(rawurlencode($location));
113:         return $this->_current;
114:     }
115: 
116:     /**
117:      * Obtain the forecast for the current location.
118:      *
119:      * @see Horde_Service_Weather_Base#getForecast
120:      */
121:     public function getForecast(
122:         $location,
123:         $length = Horde_Service_Weather::FORECAST_3DAY,
124:         $type = Horde_Service_Weather::FORECAST_TYPE_STANDARD)
125:     {
126:         $this->_getCommonElements(rawurlencode($location), $length);
127:         return $this->_forecast;
128:     }
129: 
130:     /**
131:      * Search for a valid location code.
132:      *
133:      * @param  string $location  A location search string like e.g., Boston,MA
134:      * @param  integer $type     The type of search being performed.
135:      *
136:      * @return string  The search location suitable to use directly in a
137:      *                 weather request.
138:      * @throws Horde_Service_Weather_Exception
139:      */
140:     public function searchLocations($location, $type = Horde_Service_Weather::SEARCHTYPE_STANDARD)
141:     {
142:         switch ($type) {
143:         case Horde_Service_Weather::SEARCHTYPE_STANDARD:
144:         case Horde_Service_Weather::SEARCHTYPE_ZIP:
145:         case Horde_Service_Weather::SEARCHTYPE_CITYSTATE:
146:             return $this->_parseSearchLocations($this->_searchLocations(rawurlencode($location)));
147: 
148:         case Horde_Service_Weather::SEARCHTYPE_IP:
149:             return $this->_parseSearchLocations($this->_getLocationByIp(rawurlencode($location)));
150:         }
151:     }
152: 
153:     public function autocompleteLocation($search)
154:     {
155:         $url = new Horde_Url('http://autocomplete.wunderground.com/aq');
156:         $url->add(array('query' => $search, 'format' => 'JSON'));
157: 
158:         return $this->_parseAutocomplete($this->_makeRequest($url));
159:     }
160: 
161:     /**
162:      * Get array of supported forecast lengths.
163:      *
164:      * @return array The array of supported lengths.
165:      */
166:      public function getSupportedForecastLengths()
167:      {
168:          return array(
169:             3 => Horde_Service_Weather::FORECAST_3DAY,
170:             5 => Horde_Service_Weather::FORECAST_5DAY,
171:             7 => Horde_Service_Weather::FORECAST_7DAY,
172:             10 => Horde_Service_Weather::FORECAST_10DAY
173:          );
174:      }
175: 
176:     /**
177:      * Perform an IP location search.
178:      *
179:      * @param  string $ip  The IP address to use.
180:      *
181:      * @return string  The location code.
182:      */
183:     protected function _getLocationByIp($ip)
184:     {
185:         if ($this->_ipIsUnique($ip)) {
186:             return $this->_makeRequest(
187:                 self::API_URL . '/api/' . $this->_apiKey
188:                     . '/geolookup/q/autoip.json?geo_ip=' . $ip);
189:         } else {
190:             return $this->_makeRequest(
191:                 self::API_URL . '/api/' . $this->_apiKey
192:                     . '/geolookup/q/autoip.json');
193:         }
194:     }
195: 
196:     /**
197:      * Execute a location search.
198:      *
199:      * @param  string $location The location text to search.
200:      *
201:      * @return string  The location code result(s).
202:      */
203:     protected function _searchLocations($location)
204:     {
205:         return $this->_makeRequest(self::API_URL . '/api/' . $this->_apiKey
206:             . '/geolookup/q/' . $location . '.json');
207:     }
208: 
209:     /**
210:      * Weather Underground allows requesting multiple features per request,
211:      * and only counts it as a single request against your API key. So we trade
212:      * a bit of request time/traffic for a smaller number of requests to obtain
213:      * information for e.g., a typical weather portal display.
214:      */
215:     protected function _getCommonElements($location, $length = Horde_Service_Weather::FORECAST_10DAY)
216:     {
217:         if (!empty($this->_current) && $location == $this->_lastLocation
218:             && $this->_lastLength >= $length) {
219: 
220:             if ($this->_lastLength > $length) {
221:                 $this->_forecast->limitLength($length);
222:             }
223: 
224:             return;
225:         }
226: 
227:         $this->_lastLength = $length;
228:         $this->_lastLocation = $location;
229: 
230:         switch ($length) {
231:         case Horde_Service_Weather::FORECAST_3DAY:
232:             $l = 'forecast';
233:             break;
234:         case Horde_Service_Weather::FORECAST_5DAY:
235:         case Horde_Service_Weather::FORECAST_7DAY:
236:             $l = 'forecast7day';
237:             break;
238:         case Horde_Service_Weather::FORECAST_10DAY:
239:             $l = 'forecast10day';
240:             break;
241:         }
242:         $url = self::API_URL . '/api/' . $this->_apiKey
243:             . '/geolookup/conditions/' . $l . '/astronomy/q/' . $location . '.json';
244:         $results = $this->_makeRequest($url, $this->_cache_lifetime);
245:         $station = $this->_parseStation($results->location);
246:         $this->_current = $this->_parseCurrent($results->current_observation);
247:         $astronomy = $results->moon_phase;
248:         $date = clone $this->_current->time;
249:         $date->hour = $astronomy->sunrise->hour;
250:         $date->min = $astronomy->sunrise->minute;
251:         $date->sec = 0;
252: 
253:         $station->sunrise = $date;
254:         $station->sunset = clone $date;
255:         $station->sunset->hour = $astronomy->sunset->hour;
256:         $station->sunset->min = $astronomy->sunset->minute;
257:         // Station information doesn't include any type of name string, so
258:         // get it from the currentConditions request.
259:         $station->name = $results->current_observation->display_location->full;
260:         $this->_station = $station;
261:         $this->_forecast = $this->_parseForecast($results->forecast);
262:         $this->_forecast->limitLength($length);
263:         $this->link = $results->current_observation->image->link;
264:         $this->title = $results->current_observation->image->title;
265:     }
266: 
267:     /**
268:      * Parses the JSON response for a location request into a station object.
269:      *
270:      * @param  StdClass $station  The response from a Location request.
271:      *
272:      * @return Horde_Service_Weather_Station
273:      */
274:     protected function _parseStation($station)
275:     {
276:         // @TODO: Create a subclass of Station for wunderground, parse the
277:         //  "close stations" and "pws" properties - allow for things like
278:         //  displaying other, nearby weather station conditions etc...
279:         $properties = array(
280:             'city' => $station->city,
281:             'state' => $station->state,
282:             'country' => $station->country_iso3166,
283:             'country_name' => $station->country_name,
284:             'tz' => $station->tz_long,
285:             'lat' => $station->lat,
286:             'lon' => $station->lon,
287:             'zip' => $station->zip,
288:             'code' => str_replace('/q/', '', $station->l)
289:         );
290: 
291:         return new Horde_Service_Weather_Station($properties);
292:     }
293: 
294:     /**
295:      * Parses the forecast data.
296:      *
297:      * @param stdClass $forecast The result of the forecast request.
298:      *
299:      * @return Horde_Service_Weather_Forecast_WeatherUnderground  The forecast.
300:      */
301:     protected function _parseForecast($forecast)
302:     {
303:         return new Horde_Service_Weather_Forecast_WeatherUnderground(
304:             (array)$forecast, $this);
305:     }
306: 
307:     /**
308:      * Parses astronomy information. Returned as an array since this will be
309:      * added to the station information.
310:      *
311:      * @param  {[type]} $astronomy [description]
312:      * @return {[type]}
313:      */
314:     protected function _parseAstronomy($astronomy)
315:     {
316:         // For now, just cast to array and pass back, we need to normalize
317:         // at least the moon data. (Given in percent illumindated and age -
318:         // need to parse that into phases.)
319:         return (array)$astronomy;
320:     }
321: 
322:     /**
323:      * Parse the current_conditions response.
324:      *
325:      * @param  stdClass $current  The current_condition request response object
326:      *
327:      * @return Horde_Service_Weather_Current
328:      */
329:     protected function _parseCurrent($current)
330:     {
331:         // The Current object takes care of the parsing/mapping.
332:         return new Horde_Service_Weather_Current_WeatherUnderground((array)$current, $this);
333:     }
334: 
335:     protected function _parseSearchLocations($response)
336:     {
337:         if (!empty($response->response->error)) {
338:             throw new Horde_Service_Weather_Exception($response->response->error->description);
339:         }
340:         if (!empty($response->response->results)) {
341:             $results = array();
342:             foreach ($response->response->results as $location) {
343:                 $results[] = $this->_parseStation($location);
344:             }
345:             return $results;
346:         } else {
347:             return $this->_parseStation($response->location);
348:         }
349:     }
350: 
351:     protected function _parseAutocomplete($results)
352:     {
353:         $return = array();
354:         foreach($results->RESULTS as $result) {
355:             $new = new stdClass();
356:             $new->name = $result->name;
357:             $new->code = $result->l;
358:             $return[] = $new;
359:         }
360: 
361:         return $return;
362:     }
363: 
364:     protected function _makeRequest($url, $lifetime = 86400)
365:     {
366:         $cachekey = md5('hordeweather' . $url);
367:         if ((!empty($this->_cache) && !$results = $this->_cache->get($cachekey, $lifetime)) ||
368:             empty($this->_cache)) {
369:             $url = new Horde_Url($url);
370:             $response = $this->_http->get($url);
371:             if (!$response->code == '200') {
372:                 Horde::logMessage($response->getBody());
373:                 throw new Horde_Service_Weather_Exception($response->code);
374:             }
375:             $results = $response->getBody();
376:             if (!empty($this->_cache)) {
377:                $this->_cache->set($cachekey, $results);
378:             }
379:         }
380:         $results = Horde_Serialize::unserialize($results, Horde_Serialize::JSON);
381:         if (!($results instanceof StdClass)) {
382:             throw new Horde_Service_Weather_Exception('Error, unable to decode response.');
383:         }
384: 
385:         return $results;
386:     }
387: 
388:  }
API documentation generated by ApiGen