PageRenderTime 49ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/pear/Services/Weather/Common.php

https://github.com/yrchen/OPMS
PHP | 1138 lines | 626 code | 100 blank | 412 comment | 184 complexity | 462d1df3c8d921fb9e5ea52addbb048e MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.0
  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
  3. /**
  4. * PEAR::Services_Weather_Common
  5. *
  6. * PHP versions 4 and 5
  7. *
  8. * <LICENSE>
  9. * Copyright (c) 2005, Alexander Wirtz
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or without
  13. * modification, are permitted provided that the following conditions
  14. * are met:
  15. * o Redistributions of source code must retain the above copyright notice,
  16. * this list of conditions and the following disclaimer.
  17. * o Redistributions in binary form must reproduce the above copyright notice,
  18. * this list of conditions and the following disclaimer in the documentation
  19. * and/or other materials provided with the distribution.
  20. * o Neither the name of the software nor the names of its contributors
  21. * may be used to endorse or promote products derived from this software
  22. * without specific prior written permission.
  23. *
  24. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  25. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  26. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  27. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  28. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  29. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  30. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  31. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  32. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  33. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  34. * POSSIBILITY OF SUCH DAMAGE.
  35. * </LICENSE>
  36. *
  37. * @category Web Services
  38. * @package Services_Weather
  39. * @author Alexander Wirtz <alex@pc4p.net>
  40. * @copyright 2005 Alexander Wirtz
  41. * @license http://www.opensource.org/licenses/bsd-license.php BSD License
  42. * @version CVS: $Id: Common.php,v 1.51 2005/11/13 22:27:32 eru Exp $
  43. * @link http://pear.php.net/package/Services_Weather
  44. * @filesource
  45. */
  46. require_once "Services/Weather.php";
  47. // {{{ constants
  48. // {{{ natural constants and measures
  49. define("SERVICES_WEATHER_RADIUS_EARTH", 6378.15);
  50. // }}}
  51. // {{{ default values for the sun-functions
  52. define("SERVICES_WEATHER_SUNFUNCS_DEFAULT_LATITUDE", 31.7667);
  53. define("SERVICES_WEATHER_SUNFUNCS_DEFAULT_LONGITUDE", 35.2333);
  54. define("SERVICES_WEATHER_SUNFUNCS_SUNRISE_ZENITH", 90.83);
  55. define("SERVICES_WEATHER_SUNFUNCS_SUNSET_ZENITH", 90.83);
  56. // }}}
  57. // }}}
  58. // {{{ class Services_Weather_Common
  59. /**
  60. * Parent class for weather-services. Defines common functions for unit
  61. * conversions, checks for cache enabling and does other miscellaneous
  62. * things.
  63. *
  64. * @category Web Services
  65. * @package Services_Weather
  66. * @author Alexander Wirtz <alex@pc4p.net>
  67. * @copyright 2005 Alexander Wirtz
  68. * @license http://www.opensource.org/licenses/bsd-license.php BSD License
  69. * @version Release: 1.4.0
  70. * @link http://pear.php.net/package/Services_Weather
  71. */
  72. class Services_Weather_Common {
  73. // {{{ properties
  74. /**
  75. * Format of the units provided (standard/metric/custom)
  76. *
  77. * @var string $_unitsFormat
  78. * @access private
  79. */
  80. var $_unitsFormat = "s";
  81. /**
  82. * Custom format of the units
  83. *
  84. * @var array $_customUnitsFormat
  85. * @access private
  86. */
  87. var $_customUnitsFormat = array(
  88. "temp" => "f",
  89. "vis" => "sm",
  90. "height" => "ft",
  91. "wind" => "mph",
  92. "pres" => "in",
  93. "rain" => "in"
  94. );
  95. /**
  96. * Options for HTTP requests
  97. *
  98. * @var array $_httpOptions
  99. * @access private
  100. */
  101. var $_httpOptions = array();
  102. /**
  103. * Format of the used dates
  104. *
  105. * @var string $_dateFormat
  106. * @access private
  107. */
  108. var $_dateFormat = "m/d/y";
  109. /**
  110. * Format of the used times
  111. *
  112. * @var string $_timeFormat
  113. * @access private
  114. */
  115. var $_timeFormat = "G:i A";
  116. /**
  117. * Object containing the location-data
  118. *
  119. * @var object stdClass $_location
  120. * @access private
  121. */
  122. var $_location;
  123. /**
  124. * Object containing the weather-data
  125. *
  126. * @var object stdClass $_weather
  127. * @access private
  128. */
  129. var $_weather;
  130. /**
  131. * Object containing the forecast-data
  132. *
  133. * @var object stdClass $_forecast
  134. * @access private
  135. */
  136. var $_forecast;
  137. /**
  138. * Cache, containing the data-objects
  139. *
  140. * @var object Cache $_cache
  141. * @access private
  142. */
  143. var $_cache;
  144. /**
  145. * Provides check for Cache
  146. *
  147. * @var bool $_cacheEnabled
  148. * @access private
  149. */
  150. var $_cacheEnabled = false;
  151. // }}}
  152. // {{{ constructor
  153. /**
  154. * Constructor
  155. *
  156. * @param array $options
  157. * @param mixed $error
  158. * @throws PEAR_Error
  159. * @access private
  160. */
  161. function Services_Weather_Common($options, &$error)
  162. {
  163. // Set some constants for the case when PHP4 is used, as the
  164. // date_sunset/sunrise functions are not implemented there
  165. if (!defined("SUNFUNCS_RET_TIMESTAMP")) {
  166. define("SUNFUNCS_RET_TIMESTAMP", 0);
  167. define("SUNFUNCS_RET_STRING", 1);
  168. define("SUNFUNCS_RET_DOUBLE", 2);
  169. }
  170. // Set options accordingly
  171. if (isset($options["cacheType"])) {
  172. if (isset($options["cacheOptions"])) {
  173. $status = $this->setCache($options["cacheType"], $options["cacheOptions"]);
  174. } else {
  175. $status = $this->setCache($options["cacheType"]);
  176. }
  177. if (Services_Weather::isError($status)) {
  178. $error = $status;
  179. return;
  180. }
  181. }
  182. if (isset($options["unitsFormat"])) {
  183. if (isset($options["customUnitsFormat"])) {
  184. $this->setUnitsFormat($options["unitsFormat"], $options["customUnitsFormat"]);
  185. } else {
  186. $this->setUnitsFormat($options["unitsFormat"]);
  187. }
  188. }
  189. if (isset($options["httpTimeout"])) {
  190. $this->setHttpTimeout($options["httpTimeout"]);
  191. } else {
  192. $this->setHttpTimeout(60);
  193. }
  194. if (isset($options["httpProxy"])) {
  195. $status = $this->setHttpProxy($options["httpProxy"]);
  196. if (Services_Weather::isError($status)) {
  197. $error = $status;
  198. return;
  199. }
  200. }
  201. if (isset($options["dateFormat"])) {
  202. $this->setDateTimeFormat($options["dateFormat"], "");
  203. }
  204. if (isset($options["timeFormat"])) {
  205. $this->setDateTimeFormat("", $options["timeFormat"]);
  206. }
  207. }
  208. // }}}
  209. // {{{ setCache()
  210. /**
  211. * Enables caching the data, usage strongly recommended
  212. *
  213. * Requires Cache to be installed
  214. *
  215. * @param string $cacheType
  216. * @param array $cacheOptions
  217. * @return PEAR_Error|bool
  218. * @throws PEAR_Error::SERVICES_WEATHER_ERROR_CACHE_INIT_FAILED
  219. * @access public
  220. */
  221. function setCache($cacheType = "file", $cacheOptions = array())
  222. {
  223. // The error handling in Cache is a bit crummy (read: not existent)
  224. // so we have to do that on our own...
  225. @include_once "Cache.php";
  226. @$cache = new Cache($cacheType, $cacheOptions);
  227. if (is_object($cache) && (strtolower(get_class($cache)) == "cache" || is_subclass_of($cache, "cache"))) {
  228. $this->_cache = $cache;
  229. $this->_cacheEnabled = true;
  230. } else {
  231. $this->_cache = null;
  232. $this->_cacheEnabled = false;
  233. return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_CACHE_INIT_FAILED, __FILE__, __LINE__);
  234. }
  235. return true;
  236. }
  237. // }}}
  238. // {{{ setUnitsFormat()
  239. /**
  240. * Changes the representation of the units (standard/metric)
  241. *
  242. * @param string $unitsFormat
  243. * @param array $customUnitsFormat
  244. * @access public
  245. */
  246. function setUnitsFormat($unitsFormat, $customUnitsFormat = array())
  247. {
  248. static $acceptedFormats;
  249. if (!isset($acceptedFormats)) {
  250. $acceptedFormats = array(
  251. "temp" => array("c", "f"),
  252. "vis" => array("m", "km", "ft", "sm"),
  253. "height" => array("m", "ft"),
  254. "wind" => array("mph", "kmh", "kt", "mps", "fps", "bft"),
  255. "pres" => array("in", "hpa", "mb", "mm", "atm"),
  256. "rain" => array("in", "mm")
  257. );
  258. }
  259. if (strlen($unitsFormat) && in_array(strtolower($unitsFormat{0}), array("c", "m", "s"))) {
  260. $this->_unitsFormat = strtolower($unitsFormat{0});
  261. if ($this->_unitsFormat == "c" && is_array($customUnitsFormat)) {
  262. foreach ($customUnitsFormat as $key => $value) {
  263. if (array_key_exists($key, $acceptedFormats) && in_array($value, $acceptedFormats[$key])) {
  264. $this->_customUnitsFormat[$key] = $value;
  265. }
  266. }
  267. } elseif ($this->_unitsFormat == "c") {
  268. $this->_unitsFormat = "s";
  269. }
  270. }
  271. }
  272. // }}}
  273. // {{{ setHttpOption()
  274. /**
  275. * Sets an option for usage in HTTP_Request objects
  276. *
  277. * @param string $varName
  278. * @param mixed $varValue
  279. * @access public
  280. */
  281. function setHttpOption($varName, $varValue)
  282. {
  283. if (is_string($varName) && $varName != "" && !empty($varValue)) {
  284. $this->_httpOptions[$varName] = $varValue;
  285. }
  286. }
  287. // }}}
  288. // {{{ setHttpTimeout()
  289. /**
  290. * Sets the timeout in seconds for HTTP requests
  291. *
  292. * @param int $httpTimeout
  293. * @access public
  294. */
  295. function setHttpTimeout($httpTimeout)
  296. {
  297. if (is_int($httpTimeout)) {
  298. $this->_httpOptions["timeout"] = $httpTimeout;
  299. }
  300. }
  301. // }}}
  302. // {{{ setHttpProxy()
  303. /**
  304. * Sets the proxy for HTTP requests
  305. *
  306. * @param string $httpProxy
  307. * @access public
  308. */
  309. function setHttpProxy($httpProxy)
  310. {
  311. if (($proxy = parse_url($httpProxy)) !== false && $proxy["scheme"] == "http") {
  312. if (isset($proxy["user"]) && $proxy["user"] != "") {
  313. $this->_httpOptions["proxy_user"] = $proxy["user"];
  314. }
  315. if (isset($proxy["pass"]) && $proxy["pass"] != "") {
  316. $this->_httpOptions["proxy_pass"] = $proxy["pass"];
  317. }
  318. if (isset($proxy["host"]) && $proxy["host"] != "") {
  319. $this->_httpOptions["proxy_host"] = $proxy["host"];
  320. }
  321. if (isset($proxy["port"]) && $proxy["port"] != "") {
  322. $this->_httpOptions["proxy_port"] = $proxy["port"];
  323. }
  324. return true;
  325. } else {
  326. return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_HTTP_PROXY_INVALID, __FILE__, __LINE__);
  327. }
  328. }
  329. // }}}
  330. // {{{ getUnitsFormat()
  331. /**
  332. * Returns the selected units format
  333. *
  334. * @param string $unitsFormat
  335. * @return array
  336. * @access public
  337. */
  338. function getUnitsFormat($unitsFormat = "")
  339. {
  340. // This is cheap'o stuff
  341. if (strlen($unitsFormat) && in_array(strtolower($unitsFormat{0}), array("c", "m", "s"))) {
  342. $unitsFormat = strtolower($unitsFormat{0});
  343. } else {
  344. $unitsFormat = $this->_unitsFormat;
  345. }
  346. $c = $this->_customUnitsFormat;
  347. $m = array(
  348. "temp" => "c",
  349. "vis" => "km",
  350. "height" => "m",
  351. "wind" => "kmh",
  352. "pres" => "mb",
  353. "rain" => "mm"
  354. );
  355. $s = array(
  356. "temp" => "f",
  357. "vis" => "sm",
  358. "height" => "ft",
  359. "wind" => "mph",
  360. "pres" => "in",
  361. "rain" => "in"
  362. );
  363. return ${$unitsFormat};
  364. }
  365. // }}}
  366. // {{{ setDateTimeFormat()
  367. /**
  368. * Changes the representation of time and dates (see http://www.php.net/date)
  369. *
  370. * @param string $dateFormat
  371. * @param string $timeFormat
  372. * @access public
  373. */
  374. function setDateTimeFormat($dateFormat = "", $timeFormat = "")
  375. {
  376. if (strlen($dateFormat)) {
  377. $this->_dateFormat = $dateFormat;
  378. }
  379. if (strlen($timeFormat)) {
  380. $this->_timeFormat = $timeFormat;
  381. }
  382. }
  383. // }}}
  384. // {{{ convertTemperature()
  385. /**
  386. * Convert temperature between f and c
  387. *
  388. * @param float $temperature
  389. * @param string $from
  390. * @param string $to
  391. * @return float
  392. * @access public
  393. */
  394. function convertTemperature($temperature, $from, $to)
  395. {
  396. $from = strtolower($from{0});
  397. $to = strtolower($to{0});
  398. $result = array(
  399. "f" => array(
  400. "f" => $temperature, "c" => ($temperature - 32) / 1.8
  401. ),
  402. "c" => array(
  403. "f" => 1.8 * $temperature + 32, "c" => $temperature
  404. )
  405. );
  406. return round($result[$from][$to], 2);
  407. }
  408. // }}}
  409. // {{{ convertSpeed()
  410. /**
  411. * Convert speed between mph, kmh, kt, mps, fps and bft
  412. *
  413. * Function will return "false" when trying to convert from
  414. * Beaufort, as it is a scale and not a true measurement
  415. *
  416. * @param float $speed
  417. * @param string $from
  418. * @param string $to
  419. * @return float|int|bool
  420. * @access public
  421. * @link http://www.spc.noaa.gov/faq/tornado/beaufort.html
  422. */
  423. function convertSpeed($speed, $from, $to)
  424. {
  425. $from = strtolower($from);
  426. $to = strtolower($to);
  427. static $factor;
  428. static $beaufort;
  429. if (!isset($factor)) {
  430. $factor = array(
  431. "mph" => array(
  432. "mph" => 1, "kmh" => 1.609344, "kt" => 0.8689762, "mps" => 0.44704, "fps" => 1.4666667
  433. ),
  434. "kmh" => array(
  435. "mph" => 0.6213712, "kmh" => 1, "kt" => 0.5399568, "mps" => 0.2777778, "fps" => 0.9113444
  436. ),
  437. "kt" => array(
  438. "mph" => 1.1507794, "kmh" => 1.852, "kt" => 1, "mps" => 0.5144444, "fps" => 1.6878099
  439. ),
  440. "mps" => array(
  441. "mph" => 2.2369363, "kmh" => 3.6, "kt" => 1.9438445, "mps" => 1, "fps" => 3.2808399
  442. ),
  443. "fps" => array(
  444. "mph" => 0.6818182, "kmh" => 1.09728, "kt" => 0.5924838, "mps" => 0.3048, "fps" => 1
  445. )
  446. );
  447. // Beaufort scale, measurements are in knots
  448. $beaufort = array(
  449. 1, 3, 6, 10,
  450. 16, 21, 27, 33,
  451. 40, 47, 55, 63
  452. );
  453. }
  454. if ($from == "bft") {
  455. return false;
  456. } elseif ($to == "bft") {
  457. $speed = round($speed * $factor[$from]["kt"], 0);
  458. for ($i = 0; $i < sizeof($beaufort); $i++) {
  459. if ($speed <= $beaufort[$i]) {
  460. return $i;
  461. }
  462. }
  463. return sizeof($beaufort);
  464. } else {
  465. return round($speed * $factor[$from][$to], 2);
  466. }
  467. }
  468. // }}}
  469. // {{{ convertPressure()
  470. /**
  471. * Convert pressure between in, hpa, mb, mm and atm
  472. *
  473. * @param float $pressure
  474. * @param string $from
  475. * @param string $to
  476. * @return float
  477. * @access public
  478. */
  479. function convertPressure($pressure, $from, $to)
  480. {
  481. $from = strtolower($from);
  482. $to = strtolower($to);
  483. static $factor;
  484. if (!isset($factor)) {
  485. $factor = array(
  486. "in" => array(
  487. "in" => 1, "hpa" => 33.863887, "mb" => 33.863887, "mm" => 25.4, "atm" => 0.0334213
  488. ),
  489. "hpa" => array(
  490. "in" => 0.02953, "hpa" => 1, "mb" => 1, "mm" => 0.7500616, "atm" => 0.0009869
  491. ),
  492. "mb" => array(
  493. "in" => 0.02953, "hpa" => 1, "mb" => 1, "mm" => 0.7500616, "atm" => 0.0009869
  494. ),
  495. "mm" => array(
  496. "in" => 0.0393701, "hpa" => 1.3332239, "mb" => 1.3332239, "mm" => 1, "atm" => 0.0013158
  497. ),
  498. "atm" => array(
  499. "in" => 29,921258, "hpa" => 1013.2501, "mb" => 1013.2501, "mm" => 759.999952, "atm" => 1
  500. )
  501. );
  502. }
  503. return round($pressure * $factor[$from][$to], 2);
  504. }
  505. // }}}
  506. // {{{ convertDistance()
  507. /**
  508. * Convert distance between km, ft and sm
  509. *
  510. * @param float $distance
  511. * @param string $from
  512. * @param string $to
  513. * @return float
  514. * @access public
  515. */
  516. function convertDistance($distance, $from, $to)
  517. {
  518. $to = strtolower($to);
  519. $from = strtolower($from);
  520. static $factor;
  521. if (!isset($factor)) {
  522. $factor = array(
  523. "m" => array(
  524. "m" => 1, "km" => 1000, "ft" => 3.280839895, "sm" => 0.0006213699
  525. ),
  526. "km" => array(
  527. "m" => 0.001, "km" => 1, "ft" => 3280.839895, "sm" => 0.6213699
  528. ),
  529. "ft" => array(
  530. "m" => 0.3048, "km" => 0.0003048, "ft" => 1, "sm" => 0.0001894
  531. ),
  532. "sm" => array(
  533. "m" => 0.0016093472, "km" => 1.6093472, "ft" => 5280.0106, "sm" => 1
  534. )
  535. );
  536. }
  537. return round($distance * $factor[$from][$to], 2);
  538. }
  539. // }}}
  540. // {{{ calculateWindChill()
  541. /**
  542. * Calculate windchill from temperature and windspeed (enhanced formula)
  543. *
  544. * Temperature has to be entered in deg F, speed in mph!
  545. *
  546. * @param float $temperature
  547. * @param float $speed
  548. * @return float
  549. * @access public
  550. * @link http://www.nws.noaa.gov/om/windchill/
  551. */
  552. function calculateWindChill($temperature, $speed)
  553. {
  554. return round(35.74 + 0.6215 * $temperature - 35.75 * pow($speed, 0.16) + 0.4275 * $temperature * pow($speed, 0.16));
  555. }
  556. // }}}
  557. // {{{ calculateHumidity()
  558. /**
  559. * Calculate humidity from temperature and dewpoint
  560. * This is only an approximation, there is no exact formula, this
  561. * one here is called Magnus-Formula
  562. *
  563. * Temperature and dewpoint have to be entered in deg C!
  564. *
  565. * @param float $temperature
  566. * @param float $dewPoint
  567. * @return float
  568. * @access public
  569. * @link http://www.faqs.org/faqs/meteorology/temp-dewpoint/
  570. */
  571. function calculateHumidity($temperature, $dewPoint)
  572. {
  573. // First calculate saturation steam pressure for both temperatures
  574. if ($temperature >= 0) {
  575. $a = 7.5;
  576. $b = 237.3;
  577. } else {
  578. $a = 7.6;
  579. $b = 240.7;
  580. }
  581. $tempSSP = 6.1078 * pow(10, ($a * $temperature) / ($b + $temperature));
  582. if ($dewPoint >= 0) {
  583. $a = 7.5;
  584. $b = 237.3;
  585. } else {
  586. $a = 7.6;
  587. $b = 240.7;
  588. }
  589. $dewSSP = 6.1078 * pow(10, ($a * $dewPoint) / ($b + $dewPoint));
  590. return round(100 * $dewSSP / $tempSSP, 1);
  591. }
  592. // }}}
  593. // {{{ calculateDewPoint()
  594. /**
  595. * Calculate dewpoint from temperature and humidity
  596. * This is only an approximation, there is no exact formula, this
  597. * one here is called Magnus-Formula
  598. *
  599. * Temperature has to be entered in deg C!
  600. *
  601. * @param float $temperature
  602. * @param float $humidity
  603. * @return float
  604. * @access public
  605. * @link http://www.faqs.org/faqs/meteorology/temp-dewpoint/
  606. */
  607. function calculateDewPoint($temperature, $humidity)
  608. {
  609. if ($temperature >= 0) {
  610. $a = 7.5;
  611. $b = 237.3;
  612. } else {
  613. $a = 7.6;
  614. $b = 240.7;
  615. }
  616. // First calculate saturation steam pressure for temperature
  617. $SSP = 6.1078 * pow(10, ($a * $temperature) / ($b + $temperature));
  618. // Steam pressure
  619. $SP = $humidity / 100 * $SSP;
  620. $v = log($SP / 6.1078, 10);
  621. return round($b * $v / ($a - $v), 1);
  622. }
  623. // }}}
  624. // {{{ polar2cartesian()
  625. /**
  626. * Convert polar coordinates to cartesian coordinates
  627. *
  628. * @param float $latitude
  629. * @param float $longitude
  630. * @return array
  631. * @access public
  632. */
  633. function polar2cartesian($latitude, $longitude)
  634. {
  635. $theta = deg2rad($latitude);
  636. $phi = deg2rad($longitude);
  637. $x = SERVICES_WEATHER_RADIUS_EARTH * cos($phi) * cos($theta);
  638. $y = SERVICES_WEATHER_RADIUS_EARTH * sin($phi) * cos($theta);
  639. $z = SERVICES_WEATHER_RADIUS_EARTH * sin($theta);
  640. return array($x, $y, $z);
  641. }
  642. // }}}
  643. // {{{ calculateSunRiseSet()
  644. /**
  645. * Calculates sunrise and sunset for a location
  646. *
  647. * The sun position algorithm taken from the 'US Naval Observatory's
  648. * Almanac for Computers', implemented by Ken Bloom <kekabloom[at]ucdavis[dot]edu>
  649. * for the zmanim project, converted to C by Moshe Doron <mosdoron[at]netvision[dot]net[dot]il>
  650. * and finally taken from the PHP5 sources and converted to native PHP as a wrapper.
  651. *
  652. * The date has to be entered as a timestamp!
  653. *
  654. * @param int $date
  655. * @param int $retformat
  656. * @param float $latitude
  657. * @param float $longitude
  658. * @param float $zenith
  659. * @param float $gmt_offset
  660. * @param bool $sunrise
  661. * @return PEAR_Error|mixed
  662. * @throws PEAR_Error::SERVICES_WEATHER_ERROR_SUNFUNCS_DATE_INVALID
  663. * @throws PEAR_Error::SERVICES_WEATHER_ERROR_SUNFUNCS_RETFORM_INVALID
  664. * @throws PEAR_Error::SERVICES_WEATHER_ERROR_UNKNOWN_ERROR
  665. * @access public
  666. */
  667. function calculateSunRiseSet($date, $retformat = null, $latitude = null, $longitude = null, $zenith = null, $gmt_offset = null, $sunrise = true)
  668. {
  669. // Date must be timestamp for now
  670. if (!is_int($date)) {
  671. return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_SUNFUNCS_DATE_INVALID, __FILE__, __LINE__);
  672. }
  673. // Check for proper return format
  674. if ($retformat === null) {
  675. $retformat = SUNFUNCS_RET_STRING;
  676. } elseif (!in_array($retformat, array(SUNFUNCS_RET_TIMESTAMP, SUNFUNCS_RET_STRING, SUNFUNCS_RET_DOUBLE)) ) {
  677. return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_SUNFUNCS_RETFORM_INVALID, __FILE__, __LINE__);
  678. }
  679. // Set default values for coordinates
  680. if ($latitude === null) {
  681. $latitude = SUNFUNCS_DEFAULT_LATITUDE;
  682. } else {
  683. $latitude = (float) $latitude;
  684. }
  685. if ($longitude === null) {
  686. $longitude = SUNFUNCS_DEFAULT_LONGITUDE;
  687. } else {
  688. $longitude = (float) $longitude;
  689. }
  690. if ($zenith === null) {
  691. if($sunrise) {
  692. $zenith = SUNFUNCS_SUNRISE_ZENITH;
  693. } else {
  694. $zenith = SUNFUNCS_SUNSET_ZENITH;
  695. }
  696. } else {
  697. $zenith = (float) $zenith;
  698. }
  699. // Default value for GMT offset
  700. if ($gmt_offset === null) {
  701. $gmt_offset = date("Z", $date) / 3600;
  702. } else {
  703. $gmt_offset = (float) $gmt_offset;
  704. }
  705. // If we have PHP5, then act as wrapper for the appropriate functions
  706. if ($sunrise && function_exists("date_sunrise")) {
  707. return date_sunrise($date, $retformat, $latitude, $longitude, $zenith, $gmt_offset);
  708. }
  709. if (!$sunrise && function_exists("date_sunset")) {
  710. return date_sunset($date, $retformat, $latitude, $longitude, $zenith, $gmt_offset);
  711. }
  712. // Apparently we have PHP4, so calculate the neccessary steps in native PHP
  713. // Step 1: First calculate the day of the year
  714. $N = date("z", $date) + 1;
  715. // Step 2: Convert the longitude to hour value and calculate an approximate time
  716. $lngHour = $longitude / 15;
  717. // Use 18 for sunset instead of 6
  718. if ($sunrise) {
  719. // Sunrise
  720. $t = $N + ((6 - $lngHour) / 24);
  721. } else {
  722. // Sunset
  723. $t = $N + ((18 - $lngHour) / 24);
  724. }
  725. // Step 3: Calculate the sun's mean anomaly
  726. $M = (0.9856 * $t) - 3.289;
  727. // Step 4: Calculate the sun's true longitude
  728. $L = $M + (1.916 * sin(deg2rad($M))) + (0.020 * sin(deg2rad(2 * $M))) + 282.634;
  729. while ($L < 0) {
  730. $Lx = $L + 360;
  731. assert($Lx != $L); // askingtheguru: really needed?
  732. $L = $Lx;
  733. }
  734. while ($L >= 360) {
  735. $Lx = $L - 360;
  736. assert($Lx != $L); // askingtheguru: really needed?
  737. $L = $Lx;
  738. }
  739. // Step 5a: Calculate the sun's right ascension
  740. $RA = rad2deg(atan(0.91764 * tan(deg2rad($L))));
  741. while ($RA < 0) {
  742. $RAx = $RA + 360;
  743. assert($RAx != $RA); // askingtheguru: really needed?
  744. $RA = $RAx;
  745. }
  746. while ($RA >= 360) {
  747. $RAx = $RA - 360;
  748. assert($RAx != $RA); // askingtheguru: really needed?
  749. $RA = $RAx;
  750. }
  751. // Step 5b: Right ascension value needs to be in the same quadrant as L
  752. $Lquadrant = floor($L / 90) * 90;
  753. $RAquadrant = floor($RA / 90) * 90;
  754. $RA = $RA + ($Lquadrant - $RAquadrant);
  755. // Step 5c: Right ascension value needs to be converted into hours
  756. $RA /= 15;
  757. // Step 6: Calculate the sun's declination
  758. $sinDec = 0.39782 * sin(deg2rad($L));
  759. $cosDec = cos(asin($sinDec));
  760. // Step 7a: Calculate the sun's local hour angle
  761. $cosH = (cos(deg2rad($zenith)) - ($sinDec * sin(deg2rad($latitude)))) / ($cosDec * cos(deg2rad($latitude)));
  762. // XXX: What's the use of this block.. ?
  763. // if (sunrise && cosH > 1 || !sunrise && cosH < -1) {
  764. // throw doesnthappen();
  765. // }
  766. // Step 7b: Finish calculating H and convert into hours
  767. if ($sunrise) {
  768. // Sunrise
  769. $H = 360 - rad2deg(acos($cosH));
  770. } else {
  771. // Sunset
  772. $H = rad2deg(acos($cosH));
  773. }
  774. $H = $H / 15;
  775. // Step 8: Calculate local mean time
  776. $T = $H + $RA - (0.06571 * $t) - 6.622;
  777. // Step 9: Convert to UTC
  778. $UT = $T - $lngHour;
  779. while ($UT < 0) {
  780. $UTx = $UT + 24;
  781. assert($UTx != $UT); // askingtheguru: really needed?
  782. $UT = $UTx;
  783. }
  784. while ($UT >= 24) {
  785. $UTx = $UT - 24;
  786. assert($UTx != $UT); // askingtheguru: really needed?
  787. $UT = $UTx;
  788. }
  789. $UT = $UT + $gmt_offset;
  790. // Now bring the result into the chosen format and return
  791. switch ($retformat) {
  792. case SUNFUNCS_RET_TIMESTAMP:
  793. return floor($date - ($date % (24 * 3600))) + floor(60 * $UT);
  794. case SUNFUNCS_RET_STRING:
  795. $N = floor($UT);
  796. return sprintf("%02d:%02d", $N, floor(60 * ($UT - $N)));
  797. case SUNFUNCS_RET_DOUBLE:
  798. return $UT;
  799. default:
  800. return Services_Weather::raiseError(SERVICES_WEATHER_ERROR_UNKNOWN_ERROR, __FILE__, __LINE__);
  801. }
  802. }
  803. // }}}
  804. // {{{ getWeatherIcon()
  805. /**
  806. * Gets a number corresponding to a weather icon.
  807. *
  808. * These numbers just happen to correspond with the icons that you get with
  809. * the weather.com SDK, but open versions of them have been created. Input
  810. * must be in standard units. For the icons that include day/night, we use
  811. * the present time and the provided lat/lon to determine if the sun is up.
  812. * A complete set of icon descriptions can be found here:
  813. * http://sranshaft.wincustomize.com/Articles.aspx?AID=60165&u=0
  814. *
  815. * There are a number of icon sets here:
  816. * http://www.desktopsidebar.com/forums/index.php?showtopic=2441&st=0
  817. * http://www.desktopsidebar.com/forums/index.php?showtopic=819
  818. *
  819. * @param string $condition The condition.
  820. * @param array $clouds The clouds at various levels.
  821. * @param float $wind Wind speed in mph.
  822. * @param float $temperature Temperature in deg F.
  823. * @param float $latitude Point latitude.
  824. * @param float $longitude Point longitude.
  825. * @author Seth Price <seth@pricepages.org>
  826. * @access public
  827. */
  828. function getWeatherIcon($condition, $clouds = array(), $wind = 5, $temperature = 70, $latitude = -360, $longitude = -360)
  829. {
  830. // Search for matches that don't use the time of day
  831. $hail = (bool) stristr($condition, "hail");
  832. $dust = (bool) stristr($condition, "dust") || (bool) stristr($condition, "sand");
  833. $smoke = (bool) stristr($condition, "smoke") || (bool) stristr($condition, "volcanic ash");
  834. // Slightly more complex matches that might or might not use the time of day
  835. $near = (bool) stristr($condition, "vicinity") || (bool) stristr($condition, "recent");
  836. $light = (bool) stristr($condition, "light");
  837. $heavy = (bool) stristr($condition, "heavy");
  838. $ice = (bool) stristr($condition, "ice") || (bool) stristr($condition, "pellets");
  839. $rain = (bool) stristr($condition, "rain");
  840. $snow = (bool) stristr($condition, "snow");
  841. $fog = (bool) stristr($condition, "fog") || (bool) stristr($condition, "spray") || (bool) stristr($condition, "mist");
  842. $haze = (bool) stristr($condition, "haze");
  843. $ts = (bool) stristr($condition, "thunderstorm");
  844. $freezing = (bool) stristr($condition, "freezing");
  845. $wind = (bool) stristr($condition, "squall") || $wind > 25;
  846. $nsw = (bool) stristr($condition, "no significant weather");
  847. $hot = $temperature > 95;
  848. $frigid = $temperature < 5;
  849. if ($hail) {
  850. return 6; // Hail
  851. }
  852. if ($dust) {
  853. return 19; // Dust
  854. }
  855. if ($smoke) {
  856. return 22; // Smoke
  857. }
  858. // Get some of the dangerous conditions fist
  859. if ($rain && $snow && ($ice || $freezing)) {
  860. return 7; // Icy/Clouds Rain-Snow
  861. }
  862. if (($ts || $rain) && ($ice || $freezing)) {
  863. return 10; // Icy/Rain
  864. }
  865. if (($fog || $haze) && ($ice || $freezing)) {
  866. return 8; // Icy/Haze Rain
  867. }
  868. if ($rain && $snow) {
  869. return 5; // Cloudy/Snow-Rain Mix
  870. }
  871. if ($fog && $rain) {
  872. return 9; // Haze/Rain
  873. }
  874. if ($wind && $rain) {
  875. return 1; // Wind/Rain
  876. }
  877. if ($wind && $snow) {
  878. return 43; // Windy/Snow
  879. }
  880. if ($snow && $light) {
  881. return 13; // Flurries
  882. }
  883. if ($light && $rain) {
  884. return 11; // Light Rain
  885. }
  886. // Get the maximum coverage of the clouds at any height. For most
  887. // people, overcast at 1000ft is the same as overcast at 10000ft.
  888. //
  889. // 0 == clear, 1 == hazey, 2 == partly cloudy, 3 == mostly cloudy, 4 == overcast
  890. $coverage = 0;
  891. foreach ($clouds as $layer) {
  892. if ($coverage < 1 && stristr($layer["amount"], "few")) {
  893. $coverage = 1;
  894. } elseif ($coverage < 2 && stristr($layer["amount"], "scattered")) {
  895. $coverage = 2;
  896. } elseif ($coverage < 3 && (stristr($layer["amount"], "broken") || stristr($layer["amount"], "cumulus"))) {
  897. $coverage = 3;
  898. } elseif ($coverage < 4 && stristr($layer["amount"], "overcast")) {
  899. $coverage = 4;
  900. }
  901. }
  902. // Check if it is day or not. 0 is night, 2 is day, and 1 is unknown
  903. // or twilight (~(+|-)1 hour of sunrise/sunset). Note that twilight isn't
  904. // always accurate because of issues wrapping around the 24hr clock. Oh well...
  905. if ($latitude < 90 && $latitude > -90 && $longitude < 180 && $longitude > -180) {
  906. // Calculate sunrise/sunset and current time in GMT
  907. $sunrise = $this->calculateSunRiseSet(gmmktime(), SUNFUNCS_RET_TIMESTAMP, $latitude, $longitude, SERVICES_WEATHER_SUNFUNCS_SUNRISE_ZENITH, 0, true);
  908. $sunset = $this->calculateSunRiseSet(gmmktime(), SUNFUNCS_RET_TIMESTAMP, $latitude, $longitude, SERVICES_WEATHER_SUNFUNCS_SUNRISE_ZENITH, 0, false);
  909. $timeOfDay = gmmktime();
  910. // Now that we have the sunrise/sunset times and the current time,
  911. // we need to figure out if it is day, night, or twilight. Wrapping
  912. // these times around the 24hr clock is a pain.
  913. if ($sunrise < $sunset) {
  914. if ($timeOfDay > ($sunrise + 3600) && $timeOfDay < ($sunset - 3600)) {
  915. $isDay = 2;
  916. } elseif ($timeOfDay > ($sunrise - 3600) && $timeOfDay < ($sunset + 3600)) {
  917. $isDay = 1;
  918. } else {
  919. $isDay = 0;
  920. }
  921. } else {
  922. if ($timeOfDay < ($sunrise - 3600) && $timeOfDay > ($sunset + 3600)) {
  923. $isDay = 0;
  924. } elseif ($timeOfDay < ($sunrise + 3600) && $timeOfDay > ($sunset - 3600)) {
  925. $isDay = 1;
  926. } else {
  927. $isDay = 2;
  928. }
  929. }
  930. } else {
  931. // Default to twilight because it tends to have neutral icons.
  932. $isDay = 1;
  933. }
  934. // General precipitation
  935. if ($ts && $near) {
  936. switch ($isDay) {
  937. case 0:
  938. case 1:
  939. return 38; // Lightning
  940. case 2:
  941. return 37; // Lightning/Day
  942. }
  943. }
  944. if ($ts) {
  945. switch ($isDay) {
  946. case 0:
  947. return 47; // Thunderstorm/Night
  948. case 1:
  949. case 2:
  950. return 0; // Rain/Lightning
  951. }
  952. }
  953. if ($snow) {
  954. switch ($isDay) {
  955. case 0:
  956. return 46; // Snow/Night
  957. case 1:
  958. case 2:
  959. return 41; // Snow
  960. }
  961. }
  962. if ($rain) {
  963. switch ($isDay) {
  964. case 0:
  965. return 45; // Rain/Night
  966. case 1:
  967. return 40; // Rain
  968. case 2:
  969. return 39; // Rain/Day
  970. }
  971. }
  972. // Cloud conditions near the ground
  973. if ($fog) {
  974. return 20; // Fog
  975. }
  976. if ($haze) {
  977. return 21; // Haze
  978. }
  979. // Cloud conditions
  980. if ($coverage == 4) {
  981. return 26; // Mostly Cloudy
  982. }
  983. if ($coverage == 3) {
  984. switch ($isDay) {
  985. case 0:
  986. return 27; // Mostly Cloudy/Night
  987. case 1:
  988. return 26; // Mostly Cloudy
  989. case 2:
  990. return 28; // Mostly Cloudy/Day
  991. }
  992. }
  993. if ($coverage == 2) {
  994. switch ($isDay) {
  995. case 0:
  996. return 29; // Partly Cloudy/Night
  997. case 1:
  998. return 26; // Mostly Cloudy
  999. case 2:
  1000. return 30; // Partly Cloudy/Day
  1001. }
  1002. }
  1003. if ($coverage == 1) {
  1004. switch ($isDay) {
  1005. case 0:
  1006. case 1:
  1007. return 33; // Hazy/Night
  1008. case 2:
  1009. return 34; // Hazy/Day
  1010. }
  1011. }
  1012. // Catch-alls
  1013. if ($wind) {
  1014. return 23; // Wind
  1015. }
  1016. if ($hot) {
  1017. return 36; // Hot!
  1018. }
  1019. if ($frigid) {
  1020. return 25; // Frigid
  1021. }
  1022. if ($nsw) {
  1023. switch ($isDay) {
  1024. case 0:
  1025. case 1:
  1026. // Use night for twilight because the moon is generally
  1027. // out then, so it will match with most icon sets.
  1028. return 31; // Clear Night
  1029. case 2:
  1030. return 32; // Clear Day
  1031. }
  1032. }
  1033. return "na";
  1034. }
  1035. // }}}
  1036. }
  1037. // }}}
  1038. ?>