Weatherdotcom.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP version 4 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2004 The PHP Group |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.0 of the PHP license, |
  9. // | that is bundled with this package in the file LICENSE, and is |
  10. // | available through the world-wide-web at |
  11. // | http://www.php.net/license/2_02.txt. |
  12. // | If you did not receive a copy of the PHP license and are unable to |
  13. // | obtain it through the world-wide-web, please send a note to |
  14. // | license@php.net so we can mail you a copy immediately. |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Alexander Wirtz <alex@pc4p.net> |
  17. // +----------------------------------------------------------------------+
  18. //
  19. // $Id: Weatherdotcom.php,v 1.29 2004/01/24 17:56:35 eru Exp $
  20. require_once "Services/Weather/Common.php";
  21. // {{{ class Services_Weather_Weatherdotcom
  22. /**
  23. * PEAR::Services_Weather_Weatherdotcom
  24. *
  25. * This class acts as an interface to the xml service of weather.com. It searches for given
  26. * locations and retrieves current weather data as well as forecast for up to 10 days.
  27. *
  28. * For using the weather.com xml-service please visit
  29. * http://www.weather.com/services/xmloap.html
  30. * and follow the link to sign up, it's free! You will receive an email where to download
  31. * the SDK with the needed images and guidelines how to publish live data from weather.com.
  32. * Unfortunately the guidelines are a bit harsh, that's why there's no actual data-representation
  33. * in this class, just the raw data.
  34. * Also weather.com demands active caching, so I'd strongly recommend enabling the caching
  35. * implemented in this class. It obeys to the times as written down in the guidelines.
  36. *
  37. * For a working example, please take a look at
  38. * docs/Services_Weather/examples/weather.com-basic.php
  39. *
  40. * @author Alexander Wirtz <alex@pc4p.net>
  41. * @link http://www.weather.com/services/xmloap.html
  42. * @example docs/Services_Weather/examples/weather.com-basic.php
  43. * @package Services_Weather
  44. * @license http://www.php.net/license/2_02.txt
  45. * @version 1.2
  46. */
  47. class Services_Weather_Weatherdotcom extends Services_Weather_Common {
  48. // {{{ properties
  49. /**
  50. * Partner-ID at weather.com
  51. *
  52. * @var string $_partnerID
  53. * @access private
  54. */
  55. var $_partnerID = "";
  56. /**
  57. * License key at weather.com
  58. *
  59. * @var string $_licenseKey
  60. * @access private
  61. */
  62. var $_licenseKey = "";
  63. /**
  64. * XML_Unserializer, used for processing the xml
  65. *
  66. * @var object XML_Unserializer $_unserializer
  67. * @access private
  68. */
  69. var $_unserializer;
  70. // }}}
  71. // {{{ constructor
  72. /**
  73. * Constructor
  74. *
  75. * Requires XML_Serializer to be installed
  76. *
  77. * @param array $options
  78. * @param mixed $error
  79. * @throws PEAR_Error
  80. * @see Science_Weather::Science_Weather
  81. * @access private
  82. */
  83. function Services_Weather_Weatherdotcom($options, &$error)
  84. {
  85. $perror = null;
  86. $this->Services_Weather_Common($options, $perror);
  87. if (Services_Weather::isError($perror)) {
  88. $error = $perror;
  89. return;
  90. }
  91. // Set options accordingly
  92. if (isset($options["partnerID"])) {
  93. $this->setAccountData($options["partnerID"]);
  94. }
  95. if (isset($options["licenseKey"])) {
  96. $this->setAccountData("", $options["licenseKey"]);
  97. }
  98. include_once "XML/Unserializer.php";
  99. $unserializer = &new XML_Unserializer(array("complexType" => "object", "keyAttribute" => "type"));
  100. if (Services_Weather::isError($unserializer)) {
  101. $error = $unserializer;
  102. return;
  103. } else {
  104. $this->_unserializer = $unserializer;
  105. }
  106. }
  107. // }}}
  108. // {{{ setAccountData()
  109. /**
  110. * Sets the neccessary account-information for weather.com, you'll receive them after registering for the XML-stream
  111. *
  112. * @param string $partnerID
  113. * @param string $licenseKey
  114. * @access public
  115. */
  116. function setAccountData($partnerID, $licenseKey)
  117. {
  118. if (strlen($partnerID) && ctype_digit($partnerID)) {
  119. $this->_partnerID = $partnerID;
  120. }
  121. if (strlen($licenseKey) && ctype_alnum($licenseKey)) {
  122. $this->_licenseKey = $licenseKey;
  123. }
  124. }
  125. // }}}
  126. // {{{ _checkLocationID()
  127. /**
  128. * Checks the id for valid values and thus prevents silly requests to weather.com server
  129. *
  130. * @param string $id
  131. * @return PEAR_Error|bool
  132. * @throws PEAR_Error::SERVICES_WEATHER_ERROR_NO_LOCATION
  133. * @throws PEAR_Error::SERVICES_WEATHER_ERROR_INVALID_LOCATION
  134. * @access private
  135. */
  136. function _checkLocationID($id)
  137. {
  138. if (!strlen($id)) {
  139. return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_NO_LOCATION);
  140. } elseif (!ctype_alnum($id) || (strlen($id) > 8)) {
  141. return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_INVALID_LOCATION);
  142. }
  143. return true;
  144. }
  145. // }}}
  146. // {{{ _parseWeatherData()
  147. /**
  148. * Parses the data returned by the provided URL and caches it
  149. *
  150. * @param string $id
  151. * @param string $url
  152. * @return PEAR_Error|bool
  153. * @throws PEAR_Error::SERVICES_WEATHER_ERROR_WRONG_SERVER_DATA
  154. * @throws PEAR_Error
  155. * @access private
  156. */
  157. function _parseWeatherData($id, $url)
  158. {
  159. // Get data from URL and unserialize
  160. $status = $this->_unserializer->unserialize($url, true);
  161. if (Services_Weather::isError($status)) {
  162. return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_WRONG_SERVER_DATA);
  163. } else {
  164. $root = $this->_unserializer->getRootName();
  165. $data = $this->_unserializer->getUnserializedData();
  166. if (Services_Weather::isError($root)) {
  167. // Something wrong here...
  168. return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_WRONG_SERVER_DATA);
  169. } elseif ($root == "error") {
  170. // We got an error back from weather.com
  171. $errno = key(get_object_vars($data));
  172. return Services_Weather::raiseError($errno);
  173. } else {
  174. // Valid data, lets get started
  175. // Loop through the different sub-parts of the data fro processing
  176. foreach (get_object_vars($data) as $key => $val) {
  177. switch ($key) {
  178. case "head":
  179. continue 2;
  180. break;
  181. case "loc":
  182. $varname = "location";
  183. break;
  184. case "cc":
  185. $varname = "weather";
  186. break;
  187. case "dayf":
  188. $varname = "forecast";
  189. break;
  190. }
  191. // Save data in object
  192. $this->{"_".$varname} = $val;
  193. if ($this->_cacheEnabled) {
  194. // ...and cache if possible
  195. $expire = constant("SERVICES_WEATHER_EXPIRES_".strtoupper($varname));
  196. $this->_cache->extSave($id, $val, "", $expire, $varname);
  197. }
  198. }
  199. }
  200. }
  201. return true;
  202. }
  203. // }}}
  204. // {{{ searchLocation()
  205. /**
  206. * Searches IDs for given location, returns array of possible locations or single ID
  207. *
  208. * @param string $location
  209. * @param bool $useFirst If set, first ID of result-array is returned
  210. * @return PEAR_Error|array|string
  211. * @throws PEAR_Error::SERVICES_WEATHER_ERROR_WRONG_SERVER_DATA
  212. * @throws PEAR_Error::SERVICES_WEATHER_ERROR_UNKNOWN_LOCATION
  213. * @access public
  214. */
  215. function searchLocation($location, $useFirst = false)
  216. {
  217. // Get search data from server and unserialize
  218. $searchURL = "http://xoap.weather.com/search/search?where=".urlencode(trim($location));
  219. $status = $this->_unserializer->unserialize($searchURL, true, array("overrideOptions" => true, "complexType" => "array", "keyAttribute" => "id"));
  220. if (Services_Weather::isError($status)) {
  221. return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_WRONG_SERVER_DATA);
  222. } else {
  223. $search = $this->_unserializer->getUnserializedData();
  224. if (Services_Weather::isError($search)) {
  225. return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_WRONG_SERVER_DATA);
  226. } elseif (!is_array($search) || !sizeof($search)) {
  227. return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_UNKNOWN_LOCATION);
  228. } else {
  229. if (!$useFirst && (sizeof($search) > 1)) {
  230. $searchReturn = $search;
  231. } elseif ($useFirst || (sizeof($search) == 1)) {
  232. $searchReturn = key($search);
  233. }
  234. }
  235. }
  236. return $searchReturn;
  237. }
  238. // }}}
  239. // {{{ searchLocationByCountry()
  240. /**
  241. * Returns only false, as weather.com offers no country listing via
  242. * its XML services
  243. *
  244. * @param string $country
  245. * @return bool
  246. * @access public
  247. * @deprecated
  248. */
  249. function searchLocationByCountry($country = "")
  250. {
  251. return false;
  252. }
  253. // }}}
  254. // {{{ getUnits()
  255. /**
  256. * Returns the units for the current query
  257. *
  258. * @param string $id
  259. * @param string $unitsFormat
  260. * @return array
  261. * @deprecated
  262. * @access public
  263. */
  264. function getUnits($id = null, $unitsFormat = "")
  265. {
  266. return $this->getUnitsFormat($unitsFormat);
  267. }
  268. // }}}
  269. // {{{ getLocation()
  270. /**
  271. * Returns the data for the location belonging to the ID
  272. *
  273. * @param string $id
  274. * @return PEAR_Error|array
  275. * @throws PEAR_Error
  276. * @access public
  277. */
  278. function getLocation($id = "")
  279. {
  280. $status = $this->_checkLocationID($id);
  281. if (Services_Weather::isError($status)) {
  282. return $status;
  283. }
  284. $locationReturn = array();
  285. $locationURL = "http://xoap.weather.com/weather/local/".$id."?prod=xoap&par=".$this->_partnerID."&key=".$this->_licenseKey."&unit=s";
  286. if ($this->_cacheEnabled && ($location = $this->_cache->get($id, "location"))) {
  287. // Get data from cache
  288. $this->_location = $location;
  289. $locationReturn["cache"] = "HIT";
  290. } else {
  291. // Same as in the function above...
  292. $status = $this->_parseWeatherData($id, $locationURL);
  293. if (Services_Weather::isError($status)) {
  294. return $status;
  295. }
  296. $locationReturn["cache"] = "MISS";
  297. }
  298. $locationReturn["name"] = $this->_location->dnam;
  299. $locationReturn["time"] = date($this->_timeFormat, strtotime($this->_location->tm));
  300. $locationReturn["latitude"] = $this->_location->lat;
  301. $locationReturn["longitude"] = $this->_location->lon;
  302. $locationReturn["sunrise"] = date($this->_timeFormat, strtotime($this->_location->sunr));
  303. $locationReturn["sunset"] = date($this->_timeFormat, strtotime($this->_location->suns));
  304. $locationReturn["timezone"] = $this->_location->zone;
  305. return $locationReturn;
  306. }
  307. // }}}
  308. // {{{ getWeather()
  309. /**
  310. * Returns the weather-data for the supplied location
  311. *
  312. * @param string $id
  313. * @param string $unitsFormat
  314. * @return PEAR_Error|array
  315. * @throws PEAR_Error
  316. * @access public
  317. */
  318. function getWeather($id = "", $unitsFormat = "")
  319. {
  320. $status = $this->_checkLocationID($id);
  321. if (Services_Weather::isError($status)) {
  322. return $status;
  323. }
  324. // Get other data
  325. $units = $this->getUnitsFormat($unitsFormat);
  326. $weatherReturn = array();
  327. $weatherURL = "http://xoap.weather.com/weather/local/".$id."?cc=*&prod=xoap&par=".$this->_partnerID."&key=".$this->_licenseKey."&unit=s";
  328. if ($this->_cacheEnabled && ($weather = $this->_cache->get($id, "weather"))) {
  329. // Same procedure...
  330. $this->_weather = $weather;
  331. $weatherReturn["cache"] = "HIT";
  332. } else {
  333. // ...as last function
  334. $status = $this->_parseWeatherData($id, $weatherURL);
  335. if (Services_Weather::isError($status)) {
  336. return $status;
  337. }
  338. $weatherReturn["cache"] = "MISS";
  339. }
  340. $update = str_replace("Local Time", "", $this->_weather->lsup);
  341. $weatherReturn["update"] = gmdate(trim($this->_dateFormat." ".$this->_timeFormat), strtotime($update));
  342. $weatherReturn["station"] = $this->_weather->obst;
  343. $weatherReturn["temperature"] = $this->convertTemperature($this->_weather->tmp, "f", $units["temp"]);
  344. $weatherReturn["feltTemperature"] = $this->convertTemperature($this->_weather->flik, "f", $units["temp"]);
  345. $weatherReturn["condition"] = $this->_weather->t;
  346. $weatherReturn["conditionIcon"] = $this->_weather->icon;
  347. $weatherReturn["pressure"] = $this->convertPressure($this->_weather->bar->r, "in", $units["pres"]);
  348. $weatherReturn["pressureTrend"] = $this->_weather->bar->d;
  349. $weatherReturn["wind"] = $this->convertSpeed($this->_weather->wind->s, "mph", $units["wind"]);
  350. $weatherReturn["windDegrees"] = $this->_weather->wind->d;
  351. $weatherReturn["windDirection"] = $this->_weather->wind->t;
  352. $weatherReturn["humidity"] = $this->_weather->hmid;
  353. if (is_numeric($this->_weather->vis)) {
  354. $weatherReturn["visibility"] = $this->convertDistance($this->_weather->vis, "sm", $units["vis"]);
  355. } else {
  356. $weatherReturn["visibility"] = $this->_weather->vis;
  357. }
  358. $weatherReturn["uvIndex"] = $this->_weather->uv->i;
  359. $weatherReturn["uvText"] = $this->_weather->uv->t;
  360. $weatherReturn["dewPoint"] = $this->convertTemperature($this->_weather->dewp, "f", $units["temp"]);
  361. return $weatherReturn;
  362. }
  363. // }}}
  364. // {{{ getForecast()
  365. /**
  366. * Get the forecast for the next days
  367. *
  368. * @param string $id
  369. * @param int $days Values between 1 and 10
  370. * @param string $unitsFormat
  371. * @return PEAR_Error|array
  372. * @throws PEAR_Error
  373. * @access public
  374. */
  375. function getForecast($id = "", $days = 2, $unitsFormat = "")
  376. {
  377. $status = $this->_checkLocationID($id);
  378. if (Services_Weather::isError($status)) {
  379. return $status;
  380. }
  381. if (!in_array($days, range(1, 10))) {
  382. $days = 2;
  383. }
  384. // Get other data
  385. $units = $this->getUnitsFormat($unitsFormat);
  386. $forecastReturn = array();
  387. $forecastURL = "http://xoap.weather.com/weather/local/".$id."?dayf=10&prod=xoap&par=".$this->_partnerID."&key=".$this->_licenseKey."&unit=s";
  388. if ($this->_cacheEnabled && ($forecast = $this->_cache->get($id, "forecast"))) {
  389. // Encore...
  390. $this->_forecast = $forecast;
  391. $forecastReturn["cache"] = "HIT";
  392. } else {
  393. // ...
  394. $status = $this->_parseWeatherData($id, $forecastURL, $days);
  395. if (Services_Weather::isError($status)) {
  396. return $status;
  397. }
  398. $forecastReturn["cache"] = "MISS";
  399. }
  400. $update = implode(" ", array_slice(explode(" ", $this->_forecast->lsup ), 0, 3));
  401. $forecastReturn["update"] = date($this->_dateFormat." ".$this->_timeFormat, strtotime($update));
  402. $forecastReturn["days"] = array();
  403. for ($i = 0; $i < $days; $i++) {
  404. $day = array(
  405. "tempertureHigh" => $this->convertTemperature($this->_forecast->day[$i]->hi, "f", $units["temp"]),
  406. "temperatureLow" => $this->convertTemperature($this->_forecast->day[$i]->low, "f", $units["temp"]),
  407. "sunrise" => date($this->_timeFormat, strtotime($this->_forecast->day[$i]->sunr)),
  408. "sunset" => date($this->_timeFormat, strtotime($this->_forecast->day[$i]->suns)),
  409. "day" => array(
  410. "condition" => $this->_forecast->day[$i]->part[0]->t,
  411. "conditionIcon" => $this->_forecast->day[$i]->part[0]->icon,
  412. "wind" => $this->convertSpeed($this->_forecast->day[$i]->part[0]->wind->s, "mph", $units["wind"]),
  413. "windGust" => $this->convertSpeed($this->_forecast->day[$i]->part[0]->wind->gust, "mph", $units["wind"]),
  414. "windDegrees" => $this->_forecast->day[$i]->part[0]->wind->d,
  415. "windDirection" => $this->_forecast->day[$i]->part[0]->wind->t,
  416. "precipitation" => $this->_forecast->day[$i]->part[0]->ppcp,
  417. "humidity" => $this->_forecast->day[$i]->part[0]->hmid
  418. ),
  419. "night" => array (
  420. "condition" => $this->_forecast->day[$i]->part[1]->t,
  421. "conditionIcon" => $this->_forecast->day[$i]->part[1]->icon,
  422. "wind" => $this->convertSpeed($this->_forecast->day[$i]->part[1]->wind->s, "mph", $units["wind"]),
  423. "windGust" => $this->convertSpeed($this->_forecast->day[$i]->part[1]->wind->gust, "mph", $units["wind"]),
  424. "windDegrees" => $this->_forecast->day[$i]->part[1]->wind->d,
  425. "windDirection" => $this->_forecast->day[$i]->part[1]->wind->t,
  426. "precipitation" => $this->_forecast->day[$i]->part[1]->ppcp,
  427. "humidity" => $this->_forecast->day[$i]->part[1]->hmid
  428. )
  429. );
  430. $forecastReturn["days"][] = $day;
  431. }
  432. return $forecastReturn;
  433. }
  434. // }}}
  435. }
  436. // }}}
  437. ?>