1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
13:
14: 15: 16: 17: 18: 19: 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: 30: 31: 32:
33: protected $_language = 'en';
34:
35: 36: 37: 38: 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:
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: 82: 83: 84: 85: 86: 87: 88: 89: 90:
91: public function __construct(array $params = array())
92: {
93:
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: 107: 108: 109:
110: public function getCurrentConditions($location)
111: {
112: $this->_getCommonElements(rawurlencode($location));
113: return $this->_current;
114: }
115:
116: 117: 118: 119: 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: 132: 133: 134: 135: 136: 137: 138: 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: 163: 164: 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: 178: 179: 180: 181: 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: 198: 199: 200: 201: 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: 211: 212: 213: 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:
258:
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: 269: 270: 271: 272: 273:
274: protected function _parseStation($station)
275: {
276:
277:
278:
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: 296: 297: 298: 299: 300:
301: protected function _parseForecast($forecast)
302: {
303: return new Horde_Service_Weather_Forecast_WeatherUnderground(
304: (array)$forecast, $this);
305: }
306:
307: 308: 309: 310: 311: 312: 313:
314: protected function _parseAstronomy($astronomy)
315: {
316:
317:
318:
319: return (array)$astronomy;
320: }
321:
322: 323: 324: 325: 326: 327: 328:
329: protected function _parseCurrent($current)
330: {
331:
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: }