├── .gitignore ├── LICENSE ├── README.md ├── example.php └── lib ├── Forecast.php ├── ForecastAlert.php ├── ForecastDataPoint.php ├── ForecastException.php ├── ForecastFlags.php └── ForecastResponse.php /.gitignore: -------------------------------------------------------------------------------- 1 | database-setup 2 | forecast-wrapper.php 3 | docs/ 4 | *.log 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Charlie Gorichanaz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ForecastTools ## 2 | 3 | Wrapper for [Forecast.io](http://forecast.io/) [API](https://developer.forecast.io/) that supports many simultaneous API calls, substantially reducing wait time for any applications needing to look up weather conditions en masse. 4 | 5 | *Note: If you will never want to make more than one request at a time or cache the results (coming later), you might want to try [Guilherm Uhelski](https://github.com/guhelski)’s simpler [forecast-php](https://github.com/guhelski/forecast-php) project.* 6 | 7 | ### About the Forecast API 8 | 9 | > The easiest, most advanced, weather API on the web
10 | > The same API that powers [Forecast.io](http://forecast.io/) and [Dark Sky for iOS](http://darkskyapp.com/) can provide accurate short and long­ term weather predictions to your business, application or crazy idea. 11 | 12 | 13 | ## Power of Multi cURL 14 | 15 | The included `example.php` was used to demonstrate various combinations of numbers of total requests and numbers of simultaneous threads. This table shows the results in seconds, where each value is an average of three trials. 16 | 17 | Total Requests 18 | Threads 10 100 250 500 19 | 1 3.25 31.39 75.19 155.73 20 | 5 1.00 9.49 23.02 43.05 21 | 10 0.65 5.36 14.61 27.04 22 | 25 0.49 5.02 9.90 18.20 23 | 50 0.59 2.93 6.97 14.74 24 | 100 0.50 2.35 7.23 12.14 25 | 26 | This shows more than a tenfold speed increase using ForecastTools with 100 threads over using the single execution available in other projects. *Note: The Dark Sky Company recommends 10 threads due to server load concerns, so that is the current default. Please use discretion in changing this value.* 27 | 28 | 29 | ## Requirements 30 | 31 | ### PHP configuration 32 | 33 | To handle multiple simultaneous API calls, this wrapper requires [cURL](http://www.php.net/manual/en/intro.curl.php) and relies on [libcurl-multi](http://curl.haxx.se/libcurl/c/libcurl-multi.html). If you have cURL installed, you likely have what you need. 34 | 35 | For single API calls, this wrapper uses cURL if available and falls back on the PHP function [file_get_contents](http://php.net/manual/en/function.file-get-contents.php), which itself requires `allow_url_fopen On` in your php.ini file, which enables [URL-aware fopen wrappers](http://www.php.net/manual/en/filesystem.configuration.php#ini.allow-url-fopen). 36 | 37 | ### API key 38 | 39 | You need an API key, which is available after signing up for [Forecast for Developers](https://developer.forecast.io/). 40 | 41 | 42 | ## Installation 43 | 44 | #### FTP program 45 | 46 | 1. Download [ForecastTools](https://github.com/CNG/ForecastTools/archive/master.zip) 47 | 1. Extract the ZIP file 48 | 1. Rename `ForecastTools-master` to `ForecastTools` 49 | 1. Upload `ForecastTools` to your web server using an FTP program 50 | 51 | #### Console (advanced) 52 | 53 | Here is how you could copy the files to your server if your web root is `/var/www/html`: 54 | 55 | $ cd /var/www/html 56 | $ wget -O ForecastTools.zip https://github.com/CNG/ForecastTools/archive/master.zip 57 | $ unzip ForecastTools.zip 58 | $ rm -f ForecastTools.zip 59 | $ mv ForecastTools-master ForecastTools 60 | 61 | 62 | ## Structure 63 | 64 | This project is a series of classes that implement the various branches of the API response. The PHP files in `lib` are thoroughly documented to the point you can rely on them and not need to refer to the official [API documentation](https://developer.forecast.io/docs/v2). 65 | 66 | Here is the basic structure: 67 | 68 | `Forecast` objects make requests to the API and can return `ForecastResponse` objects that wrap the JSON response. `ForecastResponse` objects can return `ForecastDataPoint` objects for accessing weather conditions, as well as `ForecastAlert` and `ForecastFlags` objects for accessing metadata. 69 | 70 | 71 | ## Usage 72 | 73 | ### Current conditions, single request 74 | 75 | Here is how you could get the current temperature given some GPS coordinates: 76 | 77 | getData($latitude, $longitude); 88 | $currently = $response->getCurrently(); 89 | $time = date("H:i:s", $currently->getTime()); 90 | $temp = number_format($currently->getTemperature(), 1); 91 | echo "Temperature in the Castro at $time: $temp℉
\n"; 92 | 93 | ?> 94 | 95 | You can disregard the getter methods of the `ForecastDataPoint`, `ForecastAlerts` and `ForecastFlags` classes and deal directly with the API response. Continuing from above: 96 | 97 | getRawData(); 100 | var_dump($data); 101 | 102 | ?> 103 | 104 | ### Historical conditions, many requests 105 | 106 | Here is how you could get the temperature at the current time for every month between now and 75 years ago: 107 | 108 | $latitude, 123 | 'longitude' => $longitude, 124 | 'time' => strtotime("-$i months"), 125 | ); 126 | } 127 | 128 | // Make requests to the API 129 | $responses = $forecast->getData($requests); 130 | 131 | foreach ($responses as $response) { 132 | if ($currently = $response->getCurrently()) { 133 | $time = date("Y-m-d H:i:s", $currently->getTime()); 134 | $temp = $currently->getTemperature() 135 | ? number_format($currently->getTemperature(), 2) . '℉' 136 | : "unknown"; 137 | echo "$time: $temp
\n"; 138 | } 139 | } 140 | 141 | ?> 142 | 143 | 144 | ## Documentation 145 | 146 | The PHP files in `lib` are thoroughly documented to the point you can rely on them and not need to refer to the official [API documentation](https://developer.forecast.io/docs/v2). 147 | 148 | There is also documentation generated by [phpDocumentor](http://phpdoc.org/) available [on my website](http://votecharlie.com/projects/ForecastTools/docs/index.html), or you can generate your own from source with the command: 149 | 150 | phpdoc -d /path/to/ForecastTools/lib -t docs --template abstract 151 | 152 | In general, anything can return false if the data is not available or if there is an error, so code defensively. 153 | 154 | ### Forecast 155 | 156 | Instantiate: 157 | 158 | $forecast = new Forecast('YOUR_API_KEY_HERE'); 159 | 160 | Single request: 161 | 162 | $response = $forecast->getData(float, float[, int]); 163 | 164 | Multiple simultaneous requests: 165 | 166 | $requests = array( 167 | array('latitude' => float, 'longitude' => float, 'time' => int), 168 | array('latitude' => float, 'longitude' => float, 'time' => int), 169 | array('latitude' => float, 'longitude' => float, 'time' => int), 170 | ); 171 | $responses = $forecast->getData($requests); 172 | 173 | ### ForecastResponse 174 | 175 | A ForecastResponse object is used to access the various data blocks returned from Forecast.io for a given request. In general, to determine the weather at a given point in time, one should examine the highest-precision data block defined (minutely, hourly, and daily respectively), taking any data available from from it and falling back to the next-highest precision data block for any properties that are missing for the point in time desired. 176 | 177 | It is returned by `Forecast->getData()` 178 | 179 | Single request: 180 | 181 | $response->getRawData(); 182 | 183 | Multiple requests: 184 | 185 | foreach ($responses as $response) { 186 | $response->getRawData(); 187 | } 188 | 189 | #### Other methods 190 | 191 | Properties of the location of time requested: 192 | 193 | - `getAlerts()` returns array of `ForecastAlert` objects 194 | - `getFlags()` returns `ForecastFlags` object 195 | - `getCurrently()` returns array of `ForecastDataPoint` objects 196 | - `getLatitude()` 197 | - `getLongitude()` 198 | - `getOffset()` 199 | - `getTimezone()` 200 | 201 | Forecast or historical conditions around location and time requested: 202 | 203 | - `getMinutely()` returns array of `ForecastDataPoint` objects 204 | - `getHourly()` returns array of `ForecastDataPoint` objects 205 | - `getDaily()` returns array of `ForecastDataPoint` objects 206 | 207 | The previous three methods take an optional zero based integer argument to return a specific `ForecastDataPoint` object in the set. Can be used in conjunction with `getCount(string)`. 208 | 209 | - `getCount($type)` returns number of ForecastDataPoint objects that exist within specified block. $type can be 'minutely','hourly' or 'daily'. 210 | 211 | #### ForecastDataPoint 212 | 213 | A ForecastDataPoint object represents the various weather phenomena occurring at a specific instant of time, and has many varied methods. All of these methods (except time) are optional, and will only be set if we have that type of information for that location and time. Please note that minutely data points are always aligned to the nearest minute boundary, hourly points to the top of the hour, and daily points to midnight of that day. Data points in the daily data block (see below) are special: instead of representing the weather phenomena at a given instant of time, they are an aggregate point representing the weather phenomena that will occur over the entire day. For precipitation fields, this aggregate is a maximum; for other fields, it is an average. The following are not implemented as get functions due to lack of documentation: All of the data oriented methods may have an associated error value defined, representing our system’s confidence in its prediction. Such properties represent standard deviations of the value of their associated property; small error values therefore represent a strong confidence, while large error values represent a weak confidence. These properties are omitted where the confidence is not precisely known (though generally considered to be adequate). 214 | 215 | - `getTime()` returns the UNIX time (that is, seconds since midnight GMT on 1 Jan 1970) at which this data point occurs. 216 | - `getSummary()` returns a human-readable text summary of this data point. 217 | - `getIcon()` returns a machine-readable text summary of this data point, suitable for selecting an icon for display. If defined, this property will have one of the following values: clear-day, clear-night, rain, snow, sleet, wind, fog, cloudy, partly-cloudy-day, or partly-cloudy-night. (Developers should ensure that a sensible default is defined, as additional values, such as hail, thunderstorm, or tornado, may be defined in the future.) 218 | - `getSunriseTime()` returns (only defined on daily data points) the UNIX time (that is, seconds since midnight GMT on 1 Jan 1970) of sunrise and sunset on the given day. (If no sunrise or sunset will occur on the given day, then the appropriate fields will be undefined. This can occur during summer and winter in very high or low latitudes.) 219 | - `getSunsetTime()` returns (only defined on daily data points) the UNIX time (that is, seconds since midnight GMT on 1 Jan 1970) of sunrise and sunset on the given day. (If no sunrise or sunset will occur on the given day, then the appropriate fields will be undefined. This can occur during summer and winter in very high or low latitudes.) 220 | - `getPrecipIntensity()` returns a numerical value representing the average expected intensity (in inches of liquid water per hour) of precipitation occurring at the given time conditional on probability (that is, assuming any precipitation occurs at all). A very rough guide is that a value of 0 in./hr. corresponds to no precipitation, 0.002 in./hr. corresponds to very light precipitation, 0.017 in./hr. corresponds to light precipitation, 0.1 in./hr. corresponds to moderate precipitation, and 0.4 in./hr. corresponds to heavy precipitation. 221 | - `getPrecipIntensityMax()` returns (only defined on daily data points) numerical values representing the maximumum expected intensity of precipitation (and the UNIX time at which it occurs) on the given day in inches of liquid water per hour. 222 | - `getPrecipIntensityMaxTime()` returns (only defined on daily data points) numerical values representing the maximumum expected intensity of precipitation (and the UNIX time at which it occurs) on the given day in inches of liquid water per hour. 223 | - `getPrecipProbability()` returns a numerical value between 0 and 1 (inclusive) representing the probability of precipitation occuring at the given time. 224 | - `getPrecipType()` returns a string representing the type of precipitation occurring at the given time. If defined, this method will have one of the following values: rain, snow, sleet (which applies to each of freezing rain, ice pellets, and “wintery mix”), or hail. (If getPrecipIntensity() returns 0, then this method should return false.) 225 | - `getPrecipAccumulation()` returns (only defined on daily data points) the amount of snowfall accumulation expected to occur on the given day. (If no accumulation is expected, this method should return false.) 226 | - `getTemperature()` returns (not defined on daily data points) a numerical value representing the temperature at the given time in degrees Fahrenheit. 227 | - `getTemperatureMin()` returns (only defined on daily data points) numerical value representing the minimum temperatures on the given day in degrees Fahrenheit. 228 | - `getTemperatureMinTime()` returns (only defined on daily data points) numerical values representing the minimum temperatures (and the UNIX times at which they occur) on the given day in degrees Fahrenheit. 229 | - `getTemperatureMax()` returns (only defined on daily data points) numerical values representing the maximumum temperatures (and the UNIX times at which they occur) on the given day in degrees Fahrenheit. 230 | - `getTemperatureMaxTime()` returns (only defined on daily data points) numerical values representing the maximumum temperatures (and the UNIX times at which they occur) on the given day in degrees Fahrenheit. 231 | - `getApparentTemperature()` returns (not defined on daily data points) a numerical value representing the apparent (or “feels like”) temperature at the given time in degrees Fahrenheit. 232 | - `getApparentTemperatureMin()` returns (only defined on daily data points) numerical value representing the minimum apparent temperatures on the given day in degrees Fahrenheit. 233 | - `getApparentTemperatureMinTime()` returns (only defined on daily data points) numerical values representing the minimum apparent temperatures (and the UNIX times at which they occur) on the given day in degrees Fahrenheit. 234 | - `getApparentTemperatureMax()` returns (only defined on daily data points) numerical values representing the maximumum apparent temperatures (and the UNIX times at which they occur) on the given day in degrees Fahrenheit. 235 | - `getApparentTemperatureMaxTime()` returns (only defined on daily data points) numerical values representing the maximumum apparent temperatures (and the UNIX times at which they occur) on the given day in degrees Fahrenheit. 236 | - `getDewPoint()` returns a numerical value representing the dew point at the given time in degrees Fahrenheit. 237 | - `getWindSpeed()` returns a numerical value representing the wind speed in miles per hour. 238 | - `getWindBearing()` returns a numerical value representing the direction that the wind is coming from in degrees, with true north at 0° and progressing clockwise. (If getWindSpeed is zero, then this value will not be defined.) 239 | - `getCloudCover()` returns a numerical value between 0 and 1 (inclusive) representing the percentage of sky occluded by clouds. A value of 0 corresponds to clear sky, 0.4 to scattered clouds, 0.75 to broken cloud cover, and 1 to completely overcast skies. 240 | - `getHumidity()` returns a numerical value between 0 and 1 (inclusive) representing the relative humidity. 241 | - `getPressure()` returns a numerical value representing the sea-level air pressure in millibars. 242 | - `getVisibility()` returns a numerical value representing the average visibility in miles, capped at 10 miles. 243 | - `getOzone()` returns a numerical value representing the columnar density of total atmospheric ozone at the given time in Dobson units. 244 | 245 | #### ForecastAlert 246 | 247 | An alert object represents a severe weather warning issued for the requested location by a governmental authority (for a list of which authorities we currently support, please see data sources at https://developer.forecast.io/docs/v2 248 | 249 | - `getDescription()` returns detailed text description of the alert from appropriate weather service. 250 | - `getExpires()` returns the UNIX time (that is, seconds since midnight GMT on 1 Jan 1970) at which the alert will cease to be valid. 251 | - `getTitle()` returns a short text summary of the alert. 252 | - `getURI()` returns the HTTP(S) URI that contains detailed information about the alert. 253 | 254 | #### ForecastFlags 255 | 256 | The flags object contains various metadata information related to the request. 257 | 258 | - `getDarkskyUnavailable()` The presence of this property indicates that the Dark Sky data source supports the given location, but a temporary error (such as a radar station being down for maintenace) has made the data unavailable. 259 | - `getDarkskyStations()` returns an array of IDs for each radar station utilized in servicing this request. 260 | - `getDatapointStations()` returns an array of IDs for each DataPoint station utilized in servicing this request. 261 | - `getISDStations()` returns an array of IDs for each ISD station utilized in servicing this request. 262 | - `getLAMPStations()` returns an array of IDs for each LAMP station utilized in servicing this request. 263 | - `getMETARStations()` returns an array of IDs for each METAR station utilized in servicing this request. 264 | - `getMetnoLicense()` The presence of this property indicates that data from api.met.no was utilized in order to facilitate this request (as per their license agreement). 265 | - `getSources()` returns an array of IDs for each data source utilized in servicing this request. 266 | - `getUnits()` The presence of this property indicates which units were used for the data in this request. 267 | 268 | 269 | ## Notes 270 | 271 | I have run into some inconsistent errors when testing many (1000+) requests over and over (10+ times), such as cURL coming back with an error like: 272 | 273 | Unknown SSL protocol error in connection to api.forecast.io:443 274 | 275 | The errors do not happen when doing fewer requests in a short period, so I attributed it to a service limitation. This needs to be explored more if you intend to use ForecastTools to do massive numbers of requests. 276 | 277 | 278 | ## Changelog 279 | 280 | - Version 1.0: 7 October 2013 281 | - First release 282 | 283 | 284 | ## Improvements to come 285 | 286 | I wrote another layer for use in my own app, [Weatherbit](http://votecharlie.com/weatherbit/), that caches requests to a MySQL database to prevent redundant queries to the API. This is especially useful for applications that are likely to make repeated requests for the same information, such as a Weatherbit user checking his 30 day chart every day. I plan to abstract this a bit and include with this wrapper eventually. If you want to see what I did sooner, feel free to send me a message. -------------------------------------------------------------------------------- /example.php: -------------------------------------------------------------------------------- 1 | results[0]->geometry->location->lat, 28 | $json->results[0]->geometry->location->lng 29 | ); 30 | } 31 | 32 | ?> 33 | 34 | 35 | ForecastTools example 36 | 43 | 44 | 45 | 46 | $latitude, 55 | 'longitude' => $longitude, 56 | 'time' => strtotime("-$i months"), 57 | ); 58 | } 59 | 60 | $requests = array(1, 10, 100); 61 | $threads = array(1, 5, 10, 25, 50, 100); 62 | $trials = 2; 63 | 64 | $index = 0; // count how many trials we did 65 | echo "Of " . count($requests) * count($threads) * $trials . " trials, running:"; 66 | 67 | $results = array(); // $results['num_requests']['num_threads']['trial'] 68 | for ($i = 0; $i < count($requests); $i++) { 69 | for ($j = 0; $j < count($threads); $j++) { 70 | for ($k = 0; $k < $trials; $k++) { 71 | $index++; 72 | echo " $index"; 73 | $sample = array_slice($this_day_in_history, 0, $requests[$i]); 74 | $start = microtime(true); 75 | $forecast = new Forecast($api_key, $threads[$j]); 76 | $responses = $forecast->getData($sample); 77 | $end = microtime(true); 78 | $results[$i][$j][$k] = $end - $start; // duration 79 | echo ";"; 80 | sleep(20); 81 | } 82 | } 83 | } 84 | 85 | echo "
\n"; 86 | ?> 87 | 88 | 91 | 92 |

Request processing time in seconds

93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | '.$requests[$j].""; 106 | } 107 | ?> 108 | 109 | 112 | 113 | 114 | '.($k+1).""; 117 | for ($i = 0; $i < count($requests); $i++) { 118 | echo '"; 119 | } 120 | ?> 121 | 122 | 123 | 126 | 127 | '.sprintf('%04.6f', $average).""; 131 | } 132 | ?> 133 | 134 | 137 |
Requests
ThreadsTrial
'.sprintf('%04.6f', $results[$i][$j][$k])."
Ave
138 | 139 |

Temperature history

140 | getCurrently(); 144 | $time = date("Y-m-d H:i:s", $currently->getTime()); 145 | $temp = number_format($currently->getTemperature(), 2); 146 | echo "$time: $temp℉
\n"; 147 | } 148 | } 149 | ?> 150 | 151 | 152 | -------------------------------------------------------------------------------- /lib/Forecast.php: -------------------------------------------------------------------------------- 1 | 11 | *
  • Current conditions
  • 12 | *
  • Minute by minute forecasts out to 1 hour where available
  • 13 | *
  • Hour by hour forecasts out to 48 hours
  • 14 | *
  • Day by day forecasts out to 7 days
  • 15 | * 16 | * 17 | * You can get the current forecast or query for a specific time, up to 60 years 18 | * in the past up to 10 years in the future. 19 | * 20 | * This wrapper handles many simultaneous requests with cURL or single requests 21 | * without cURL. 22 | * 23 | * @package ForecastTools 24 | * @author Charlie Gorichanaz 25 | * @license http://opensource.org/licenses/MIT The MIT License 26 | * @version 1.0 27 | * @link http://github.com/CNG/ForecastTools 28 | * @example ../example.php 29 | */ 30 | class Forecast 31 | { 32 | 33 | private $_api_key; 34 | private $_threads; // multi cURL simultaneous requests 35 | const API_URL = 'https://api.forecast.io/forecast/'; 36 | 37 | /** 38 | * Create Forecast object 39 | * 40 | * @param string $api_key API key obtained from 41 | * Forecast.io 42 | * @param integer $threads Number of requests to process simultaneously. 43 | * Default is 10. 44 | */ 45 | public function __construct($api_key, $threads = 10) 46 | { 47 | $this->_api_key = $api_key; 48 | $this->_threads = $threads; 49 | } 50 | 51 | /** 52 | * Make requests to API 53 | * 54 | * @param array $requests_data Requests to process. Each element is 55 | *
     56 |    * array('
     57 |    *   latitude' => float,
     58 |    *   'longitude' => float,
     59 |    *   [
     60 |    *     'time' => int,
     61 |    *     'units' => string,
     62 |    *     'exclude' => string,
     63 |    *     'extend' => string,
     64 |    *     'callback' => string
     65 |    *   ]
     66 |    * )
     67 |    * 
    68 | * 69 | * @return array JSON decoded responses or false values for 70 | * each request element 71 | */ 72 | private function _request($requests_data) 73 | { 74 | 75 | $request_urls = array(); // final URLs to process 76 | $responses = array(); // raw responses from API 77 | $nice_responses = array(); // json_decoded values or false on errors 78 | 79 | // convert arrays of search parameters to request URLs 80 | foreach ($requests_data as $request_data) { 81 | 82 | // required attributes 83 | $latitude = $request_data['latitude']; 84 | $longitude = $request_data['longitude']; 85 | // optional attributes 86 | $time = empty($request_data['time']) ? null : $request_data['time']; 87 | $options = array(); 88 | if (!empty($request_data['units'])) { 89 | $options['units'] = $request_data['units']; 90 | } 91 | if (!empty($request_data['exclude'])) { 92 | $options['exclude'] = $request_data['exclude']; 93 | } 94 | if (!empty($request_data['extend'])) { 95 | $options['extend'] = $request_data['extend']; 96 | } 97 | if (!empty($request_data['callback'])) { 98 | $options['callback'] = $request_data['callback']; 99 | } 100 | $options = http_build_query($options); 101 | 102 | $request_url = self::API_URL 103 | . $this->_api_key 104 | . "/$latitude,$longitude" 105 | . ($time ? ",$time" : '') 106 | . ($options ? "?$options" : ''); 107 | 108 | $request_urls[] = $request_url; 109 | 110 | } 111 | 112 | // select method for making requests, preferring multi cURL 113 | if (function_exists('curl_multi_select') && $this->_threads > 1) { 114 | $responses = $this->_processWithMultiCurl($request_urls); 115 | } elseif (function_exists('curl_version')) { 116 | $responses = $this->_processWithSingleCurl($request_urls); 117 | } else { 118 | $responses = $this->_processWithFileGetContents($request_urls); 119 | } 120 | 121 | // JSON decode responses and log any problems with them 122 | // (but do not choke on problems, just return false in those cases) 123 | foreach ($responses as $response) { 124 | 125 | if (empty($response)) { 126 | // decided to just let developers check for false instead of forcing 127 | // everyone to implement full exception handling 128 | $err = 'At least one of the API responses was empty.'; 129 | trigger_error(__FILE__ . ':L' . __LINE__ . ": $err\n"); 130 | $nice_responses[] = false; 131 | } else { 132 | $decoded = json_decode($response); 133 | if ($decoded === null) { 134 | $err = 'Cannot decode one of the API responses.'; 135 | trigger_error(__FILE__ . ':L' . __LINE__ . ": $err\n"); 136 | $nice_responses[] = false; 137 | } else { 138 | $nice_responses[] = $decoded; 139 | } 140 | } 141 | 142 | } 143 | 144 | return $nice_responses; 145 | 146 | } 147 | 148 | /** 149 | * Make requests using multi cURL 150 | * 151 | * @param array $request_urls Simple array of URLs to run through Multi cURL 152 | * 153 | * @return array Simple array of responses from URL queries where array keys 154 | * correspond to input array keys. 155 | */ 156 | private function _processWithMultiCurl($request_urls) 157 | { 158 | 159 | $threads = $this->_threads; // requests to handle simultaneously 160 | $responses = array(); 161 | $iterations = floor(count($request_urls) / $threads); 162 | if (count($request_urls) % $threads) { 163 | $iterations++; 164 | } 165 | 166 | for ($j = 0; $j < $iterations; $j++) { 167 | 168 | $request_urls_slice = array_slice($request_urls, $j * $threads, $threads); 169 | $responses_part = array(); 170 | 171 | $mh = curl_multi_init(); 172 | foreach ($request_urls_slice as $i => $url) { 173 | $ch[$i] = curl_init($url); 174 | curl_setopt($ch[$i], CURLOPT_RETURNTRANSFER, 1); 175 | curl_multi_add_handle($mh, $ch[$i]); 176 | } 177 | 178 | // Following block replaces the commented out block below 179 | // Old code worked w/ Ubuntu 12.04/Apache2.2, but not on Ubuntu 14.04 with Apache 2.4 and PHP-FPM 180 | // Don't have time for more specifics at moment, but if you experience issues, try swapping out the blocks 181 | 182 | $running = null; 183 | do { 184 | $execReturnValue = curl_multi_exec($mh, $running); 185 | } while($running > 0); 186 | 187 | /* 188 | do { 189 | $execReturnValue = curl_multi_exec($mh, $runningHandles); 190 | } while ($execReturnValue == CURLM_CALL_MULTI_PERFORM); 191 | while ($runningHandles && $execReturnValue == CURLM_OK) { 192 | $numberReady = curl_multi_select($mh); 193 | if ($numberReady != -1) { 194 | do { 195 | $execReturnValue = curl_multi_exec($mh, $runningHandles); 196 | } while ($execReturnValue == CURLM_CALL_MULTI_PERFORM); 197 | } 198 | } 199 | */ 200 | 201 | if ($execReturnValue != CURLM_OK) { 202 | $err = "Multi cURL read error $execReturnValue"; 203 | trigger_error(__FILE__ . ':L' . __LINE__ . ": $err\n"); 204 | } 205 | 206 | foreach ($request_urls_slice as $i => $url) { 207 | 208 | $curlError = curl_error($ch[$i]); 209 | if ($curlError == "") { 210 | $responses_part[$i] = curl_multi_getcontent($ch[$i]); 211 | } else { 212 | $responses_part[$i] = false; 213 | $err = "Multi cURL error on handle $i: $curlError"; 214 | trigger_error(__FILE__ . ':L' . __LINE__ . ": $err\n"); 215 | } 216 | curl_multi_remove_handle($mh, $ch[$i]); 217 | curl_close($ch[$i]); 218 | 219 | } 220 | curl_multi_close($mh); 221 | 222 | $responses = array_merge($responses, $responses_part); 223 | 224 | } 225 | 226 | return $responses; 227 | 228 | } 229 | 230 | /** 231 | * Make requests using regular cURL (not multi) 232 | * 233 | * @param array $request_urls Simple array of URLs to run through cURL 234 | * 235 | * @return array Simple array of responses from URL queries where array keys 236 | * correspond to input array keys. 237 | */ 238 | private function _processWithSingleCurl($request_urls) 239 | { 240 | 241 | $responses = array(); 242 | 243 | foreach ($request_urls as $request_url) { 244 | 245 | $ch1 = curl_init(); 246 | curl_setopt($ch1, CURLOPT_URL, $request_url); 247 | curl_setopt($ch1, CURLOPT_HEADER, 0); 248 | curl_setopt($ch1, CURLOPT_RETURNTRANSFER, 1); 249 | $response = curl_exec($ch1); 250 | $responses[] = $response; 251 | if ($response === false) { 252 | $curlError = curl_error($ch1); 253 | $err = "cURL error: $curlError"; 254 | trigger_error(__FILE__ . ':L' . __LINE__ . ": $err\n"); 255 | } 256 | curl_close($ch1); 257 | 258 | } 259 | 260 | return $responses; 261 | 262 | } 263 | 264 | /** 265 | * Make requests using file_get_contents (not cURL) 266 | * 267 | * @param array $request_urls Simple array of URLs to run through 268 | * file_get_contents() 269 | * 270 | * @return array Simple array of responses from URL queries where array keys 271 | * correspond to input array keys. 272 | */ 273 | private function _processWithFileGetContents($request_urls) 274 | { 275 | 276 | $responses = array(); 277 | 278 | foreach ($request_urls as $request_url) { 279 | 280 | /** 281 | * Use Buffer to cache API-requests if initialized 282 | * (if not, just get the latest data) 283 | * 284 | * More info: http://git.io/FoO2Qw 285 | */ 286 | 287 | if(class_exists('Buffer')) { 288 | $cache = new Buffer(); 289 | $response = $cache->data($request_url); 290 | } else { 291 | $response = file_get_contents($request_url); 292 | } 293 | 294 | $responses[] = $response; 295 | if ($response === false) { 296 | trigger_error(__FILE__ . ':L' . __LINE__ . ": Error on file_get_contents($request_url)\n"); 297 | } 298 | 299 | } 300 | 301 | return $responses; 302 | 303 | } 304 | 305 | 306 | /** 307 | * Retrieve ForecastResponse objects for each set of location and time. 308 | * 309 | * You can call the function with either the values for one API request or an 310 | * array or arrays, with one subarray for each API request. Therefore making 311 | * one API request can be done by passing in latitude, longitude, etc., or by 312 | * passing an array containing one array. Making multiple simultaneous API 313 | * requests must be done by passing an array of arrays. The return value will 314 | * typically be either an array of ForecastResponse objects or a single such 315 | * object. If you pass in an array, you will get an array back. 316 | * 317 | * For each request, either a ForecastResponse object or false will be 318 | * returned, so you must check for false. This can indicate the data is not 319 | * available for that request. 320 | * 321 | * If invalid parameters are passed, this can throw a ForecastException. If 322 | * other errors occur, such as a problem making the request or data not being 323 | * available, the response will generally just be false. Some errors are 324 | * logged with trigger_error to the same location as PHP warnings and notices. 325 | * You must therefore write code in a way that will handle false values. You 326 | * probably do not need to handle the ForecastException unless your production 327 | * code might result in variable parameters or formats. 328 | * 329 | * @param mixed $args1 Pass either of the following: 330 | *
      331 | *
    1. 332 | * One parameter that is an array of one or more associative arrays like 333 | *
      334 |    *       array(
      335 |    *         'latitude'  => float,
      336 |    *         'longitude' => float,
      337 |    *         'time'      => int,
      338 |    *         'units'     => string,
      339 |    *         'exclude'   => string,
      340 |    *         'extend'    => string,
      341 |    *         'callback'  => string,
      342 |    *       )
      343 |    *     
      344 | * with only the latitiude and longitude required 345 | *
    2. 346 | *
    3. 347 | * Two to seven parameters in this order: latitude float, longitude float, 348 | * time int, units string, exclude string, extend string, callback string 349 | *
    4. 350 | * 351 | * 352 | * @return array|ForecastResponse|bool If array passed in, returns array of 353 | * ForecastIOConditions objects or false vales. Otherwise returns a single 354 | * ForecastResponse object or false value. 355 | * @throws ForecastException If invalid parameters used 356 | */ 357 | public function getData($args1) 358 | { 359 | /* 360 | This implementation is a little messy since I am allowing parameters to 361 | be passed individually or within arrays. Ideally I would require arrays of 362 | arrays with named keys, but I want to maintain compatibility with existing 363 | Forecast.io PHP implementations. 364 | */ 365 | $requests; // will hold array of arrays of lat/long/time/options 366 | $return_array = true; // if params not passed as array, don't return array 367 | if (func_num_args() == 1 && is_array(func_get_arg(0))) { 368 | $requests = func_get_arg(0); 369 | } elseif (func_num_args() > 1 && is_numeric(func_get_arg(0)) 370 | && is_numeric(func_get_arg(1)) 371 | ) { 372 | $return_array = false; 373 | $requests = array( 374 | array('latitude' => func_get_arg(0), 'longitude' => func_get_arg(1)) 375 | ); 376 | if (func_num_args() > 2 && is_int(func_get_arg(2))) { 377 | $requests[0]['time'] = func_get_arg(2); 378 | } 379 | if (func_num_args() > 3 && is_string(func_get_arg(3))) { 380 | $requests[0]['units'] = func_get_arg(3); 381 | } 382 | if (func_num_args() > 4 && is_string(func_get_arg(4))) { 383 | $requests[0]['exclude'] = func_get_arg(4); 384 | } 385 | if (func_num_args() > 5 && is_string(func_get_arg(5))) { 386 | $requests[0]['extend'] = func_get_arg(5); 387 | } 388 | if (func_num_args() > 6 && is_string(func_get_arg(6))) { 389 | $requests[0]['callback'] = func_get_arg(6); 390 | } 391 | } else { 392 | include_once 'ForecastException.php'; 393 | throw new ForecastException(__FUNCTION__ . " called with invalid parameters."); 394 | } 395 | 396 | $json_results = $this->_request($requests); 397 | 398 | // Wrap JSON responses in ForecastResponse objects or leave as false and 399 | // log the error to the error log 400 | $conditions = array(); 401 | include_once 'ForecastResponse.php'; 402 | foreach ($json_results as $result) { 403 | if ($result !== false) { 404 | $conditions[] = new ForecastResponse($result); 405 | } else { 406 | $conditions[] = false; 407 | trigger_error(__FILE__ . ':L' . __LINE__ . ": Failed to retrieve conditions.\n"); 408 | } 409 | } 410 | 411 | // if request included a single API call and was not wrapped in an array, 412 | // return the ForecastResponse or false alone, otherwise return array 413 | if ($return_array) { 414 | return $conditions; 415 | } else { 416 | return $conditions[0]; 417 | } 418 | 419 | } 420 | 421 | } 422 | -------------------------------------------------------------------------------- /lib/ForecastAlert.php: -------------------------------------------------------------------------------- 1 | 16 | * @license http://opensource.org/licenses/MIT The MIT License 17 | * @version 1.0 18 | * @link http://github.com/CNG/ForecastTools 19 | * @example ../example.php 20 | */ 21 | class ForecastAlert 22 | { 23 | 24 | private $_alert; 25 | 26 | /** 27 | * Create ForecastAlert object 28 | * 29 | * @param object $alert JSON decoded alert from API response 30 | */ 31 | public function __construct($alert) 32 | { 33 | $this->_alert = $alert; 34 | } 35 | 36 | /** 37 | * A short text summary of the alert. 38 | * 39 | * @return string|bool alert “title” data or false if none 40 | */ 41 | public function getTitle() 42 | { 43 | $field = 'title'; 44 | return empty($this->_alert->$field) ? false : $this->_alert->$field; 45 | } 46 | 47 | /** 48 | * The UNIX time (that is, seconds since midnight GMT on 1 Jan 1970) at which 49 | * the alert will cease to be valid. 50 | * 51 | * @return int|bool alert “expires” data or false if none 52 | */ 53 | public function getExpires() 54 | { 55 | $field = 'expires'; 56 | return empty($this->_alert->$field) ? false : $this->_alert->$field; 57 | } 58 | 59 | /** 60 | * A detailed text description of the alert from appropriate weather service. 61 | * 62 | * @return string|bool alert “description” data or false if none 63 | */ 64 | public function getDescription() 65 | { 66 | $field = 'description'; 67 | return empty($this->_alert->$field) ? false : $this->_alert->$field; 68 | } 69 | 70 | /** 71 | * An HTTP(S) URI that contains detailed information about the alert. 72 | * 73 | * @return string|bool alert “URI” data or false if none 74 | */ 75 | public function getURI() 76 | { 77 | $field = 'uri'; 78 | return empty($this->_alert->$field) ? false : $this->_alert->$field; 79 | } 80 | 81 | 82 | } 83 | -------------------------------------------------------------------------------- /lib/ForecastDataPoint.php: -------------------------------------------------------------------------------- 1 | 32 | * @license http://opensource.org/licenses/MIT The MIT License 33 | * @version 1.0 34 | * @link http://github.com/CNG/ForecastTools 35 | * @example ../example.php 36 | */ 37 | class ForecastDataPoint 38 | { 39 | 40 | private $_point_data; 41 | 42 | /** 43 | * Create ForecastDataPoint object 44 | * 45 | * @param object $point_data JSON decoded data point from API response 46 | */ 47 | public function __construct($point_data) 48 | { 49 | $this->_point_data = $point_data; 50 | } 51 | 52 | /** 53 | * The UNIX time (that is, seconds since midnight GMT on 1 Jan 1970) at which 54 | * this data point occurs. 55 | * 56 | * @return int|bool The UNIX time at which this data point occurs. 57 | */ 58 | public function getTime() 59 | { 60 | $field = 'time'; 61 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 62 | } 63 | 64 | /** 65 | * A human-readable text summary of this data point. 66 | * 67 | * @return string|bool A human-readable text summary of this data point. 68 | */ 69 | public function getSummary() 70 | { 71 | $field = 'summary'; 72 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 73 | } 74 | 75 | /** 76 | * A machine-readable text summary of this data point, suitable for selecting 77 | * an icon for display. If defined, this property will have one of the 78 | * following values: clear-day, clear-night, rain, snow, sleet, wind, fog, 79 | * cloudy, partly-cloudy-day, or partly-cloudy-night. (Developers should ensure 80 | * that a sensible default is defined, as additional values, such as hail, 81 | * thunderstorm, or tornado, may be defined in the future.) 82 | * 83 | * @return string|bool A machine-readable text summary of this data point 84 | */ 85 | public function getIcon() 86 | { 87 | $field = 'icon'; 88 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 89 | } 90 | 91 | /** 92 | * (only defined on daily data points): The UNIX time (that is, seconds since 93 | * midnight GMT on 1 Jan 1970) of sunrise and sunset on the given day. (If no 94 | * sunrise or sunset will occur on the given day, then the appropriate fields 95 | * will be undefined. This can occur during summer and winter in very high or 96 | * low latitudes.) 97 | * 98 | * @return int|bool sunriseTime 99 | */ 100 | public function getSunriseTime() 101 | { 102 | $field = 'sunriseTime'; 103 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 104 | } 105 | 106 | /** 107 | * (only defined on daily data points): The UNIX time (that is, seconds since 108 | * midnight GMT on 1 Jan 1970) of sunrise and sunset on the given day. (If no 109 | * sunrise or sunset will occur on the given day, then the appropriate fields 110 | * will be undefined. This can occur during summer and winter in very high or 111 | * low latitudes.) 112 | * 113 | * @return int|bool sunsetTime 114 | */ 115 | public function getSunsetTime() 116 | { 117 | $field = 'sunsetTime'; 118 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 119 | } 120 | 121 | /** 122 | * A numerical value representing the average expected intensity (in inches of 123 | * liquid water per hour) of precipitation occurring at the given time 124 | * conditional on probability (that is, assuming any precipitation occurs at 125 | * all). A very rough guide is that a value of 0 in./hr. corresponds to no 126 | * precipitation, 0.002 in./hr. corresponds to very light precipitation, 0.017 127 | * in./hr. corresponds to light precipitation, 0.1 in./hr. corresponds to 128 | * moderate precipitation, and 0.4 in./hr. corresponds to heavy precipitation. 129 | * 130 | * @return float|bool precipIntensity 131 | */ 132 | public function getPrecipIntensity() 133 | { 134 | $field = 'precipIntensity'; 135 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 136 | } 137 | 138 | /** 139 | * (only defined on daily data points): numerical values representing the 140 | * maximumum expected intensity of precipitation (and the UNIX time at which 141 | * it occurs) on the given day in inches of liquid water per hour. 142 | * 143 | * @return float|bool precipIntensityMax 144 | */ 145 | public function getPrecipIntensityMax() 146 | { 147 | $field = 'precipIntensityMax'; 148 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 149 | } 150 | 151 | /** 152 | * (only defined on daily data points): numerical values representing the 153 | * maximumum expected intensity of precipitation (and the UNIX time at which 154 | * it occurs) on the given day in inches of liquid water per hour. 155 | * 156 | * @return int|bool precipIntensityMaxTime 157 | */ 158 | public function getPrecipIntensityMaxTime() 159 | { 160 | $field = 'precipIntensityMaxTime'; 161 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 162 | } 163 | 164 | /** 165 | * A numerical value between 0 and 1 (inclusive) representing the probability 166 | * of precipitation occuring at the given time. 167 | * 168 | * @return float|bool precipProbability 169 | */ 170 | public function getPrecipProbability() 171 | { 172 | $field = 'precipProbability'; 173 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 174 | } 175 | 176 | /** 177 | * A string representing the type of precipitation occurring at the given time. 178 | * If defined, this method will have one of the following values: rain, snow, 179 | * sleet (which applies to each of freezing rain, ice pellets, and “wintery 180 | * mix”), or hail. (If getPrecipIntensity() returns 0, then this method should 181 | * return false.) 182 | * 183 | * @return string|bool precipType 184 | */ 185 | public function getPrecipType() 186 | { 187 | $field = 'precipType'; 188 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 189 | } 190 | 191 | /** 192 | * (only defined on daily data points): the amount of snowfall accumulation 193 | * expected to occur on the given day. (If no accumulation is expected, this 194 | * method should return false.) 195 | * 196 | * @return float|bool precipAccumulation 197 | */ 198 | public function getPrecipAccumulation() 199 | { 200 | $field = 'precipAccumulation'; 201 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 202 | } 203 | 204 | /** 205 | * (not defined on daily data points): A numerical value representing the 206 | * temperature at the given time in degrees Fahrenheit. 207 | * 208 | * @return float|bool temperature 209 | */ 210 | public function getTemperature() 211 | { 212 | $field = 'temperature'; 213 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 214 | } 215 | 216 | /** 217 | * (only defined on daily data points): numerical value representing the 218 | * minimum temperatures on the given day in degrees Fahrenheit. 219 | * 220 | * @return float|bool temperatureMin 221 | */ 222 | public function getTemperatureMin() 223 | { 224 | $field = 'temperatureMin'; 225 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 226 | } 227 | 228 | /** 229 | * (only defined on daily data points): numerical values representing the 230 | * minimum temperatures (and the UNIX times at which they 231 | * occur) on the given day in degrees Fahrenheit. 232 | * 233 | * @return int|bool temperatureMinTime 234 | */ 235 | public function getTemperatureMinTime() 236 | { 237 | $field = 'temperatureMinTime'; 238 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 239 | } 240 | 241 | /** 242 | * (only defined on daily data points): numerical values representing the 243 | * maximumum temperatures (and the UNIX times at which they 244 | * occur) on the given day in degrees Fahrenheit. 245 | * 246 | * @return float|bool temperatureMax 247 | */ 248 | public function getTemperatureMax() 249 | { 250 | $field = 'temperatureMax'; 251 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 252 | } 253 | 254 | /** 255 | * (only defined on daily data points): numerical values representing the 256 | * maximumum temperatures (and the UNIX times at which they 257 | * occur) on the given day in degrees Fahrenheit. 258 | * 259 | * @return int|bool temperatureMaxTime 260 | */ 261 | public function getTemperatureMaxTime() 262 | { 263 | $field = 'temperatureMaxTime'; 264 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 265 | } 266 | 267 | /** 268 | * (not defined on daily data points): A numerical value representing the 269 | * apparent (or “feels like”) temperature at the given time in degrees 270 | * Fahrenheit. 271 | * 272 | * @return string|bool apparentTemperature 273 | */ 274 | public function getApparentTemperature() 275 | { 276 | $field = 'apparentTemperature'; 277 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 278 | } 279 | 280 | /** 281 | * (only defined on daily data points): numerical value representing the 282 | * minimum apparent temperatures on the given day in degrees Fahrenheit. 283 | * 284 | * @return float|bool apparentTemperatureMin 285 | */ 286 | public function getApparentTemperatureMin() 287 | { 288 | $field = 'apparentTemperatureMin'; 289 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 290 | } 291 | 292 | /** 293 | * (only defined on daily data points): numerical values representing the 294 | * minimum apparent temperatures (and the UNIX times at which they 295 | * occur) on the given day in degrees Fahrenheit. 296 | * 297 | * @return int|bool apparentTemperatureMinTime 298 | */ 299 | public function getApparentTemperatureMinTime() 300 | { 301 | $field = 'apparentTemperatureMinTime'; 302 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 303 | } 304 | 305 | /** 306 | * (only defined on daily data points): numerical values representing the 307 | * maximumum apparent temperatures (and the UNIX times at which they 308 | * occur) on the given day in degrees Fahrenheit. 309 | * 310 | * @return float|bool apparentTemperatureMax 311 | */ 312 | public function getApparentTemperatureMax() 313 | { 314 | $field = 'apparentTemperatureMax'; 315 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 316 | } 317 | 318 | /** 319 | * (only defined on daily data points): numerical values representing the 320 | * maximumum apparent temperatures (and the UNIX times at which they 321 | * occur) on the given day in degrees Fahrenheit. 322 | * 323 | * @return int|bool apparentTemperatureMaxTime 324 | */ 325 | public function getApparentTemperatureMaxTime() 326 | { 327 | $field = 'apparentTemperatureMaxTime'; 328 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 329 | } 330 | 331 | /** 332 | * A numerical value representing the dew point at the given time in degrees 333 | * Fahrenheit. 334 | * 335 | * @return float|bool dewPoint 336 | */ 337 | public function getDewPoint() 338 | { 339 | $field = 'dewPoint'; 340 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 341 | } 342 | 343 | /** 344 | * A numerical value representing the wind speed in miles per hour. 345 | * 346 | * @return float|bool windSpeed 347 | */ 348 | public function getWindSpeed() 349 | { 350 | $field = 'windSpeed'; 351 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 352 | } 353 | 354 | /** 355 | * A numerical value representing the direction that the wind is coming from 356 | * in degrees, with true north at 0° and progressing clockwise. (If 357 | * getWindSpeed is zero, then this value will not be defined.) 358 | * 359 | * @return string|bool windBearing 360 | */ 361 | public function getWindBearing() 362 | { 363 | $field = 'windBearing'; 364 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 365 | } 366 | 367 | /** 368 | * A numerical value between 0 and 1 (inclusive) representing the percentage 369 | * of sky occluded by clouds. A value of 0 corresponds to clear sky, 0.4 to 370 | * scattered clouds, 0.75 to broken cloud cover, and 1 to completely overcast 371 | * skies. 372 | * 373 | * @return float|bool cloudCover 374 | */ 375 | public function getCloudCover() 376 | { 377 | $field = 'cloudCover'; 378 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 379 | } 380 | 381 | /** 382 | * A numerical value between 0 and 1 (inclusive) representing the relative 383 | * humidity. 384 | * 385 | * @return float|bool humidity 386 | */ 387 | public function getHumidity() 388 | { 389 | $field = 'humidity'; 390 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 391 | } 392 | 393 | /** 394 | * A numerical value representing the sea-level air pressure in millibars. 395 | * 396 | * @return float|bool pressure 397 | */ 398 | public function getPressure() 399 | { 400 | $field = 'pressure'; 401 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 402 | } 403 | 404 | /** 405 | * A numerical value representing the average visibility in miles, capped 406 | * at 10 miles. 407 | * 408 | * @return float|bool visibility 409 | */ 410 | public function getVisibility() 411 | { 412 | $field = 'visibility'; 413 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 414 | } 415 | 416 | /** 417 | * A numerical value representing the columnar density of total atmospheric 418 | * ozone at the given time in Dobson units. 419 | * 420 | * @return float|bool ozone 421 | */ 422 | public function getOzone() 423 | { 424 | $field = 'ozone'; 425 | return empty($this->_point_data->$field) ? false : $this->_point_data->$field; 426 | } 427 | 428 | } 429 | -------------------------------------------------------------------------------- /lib/ForecastException.php: -------------------------------------------------------------------------------- 1 | 14 | * @license http://opensource.org/licenses/MIT The MIT License 15 | * @version 1.0 16 | * @link http://github.com/CNG/ForecastTools 17 | * @example ../example.php 18 | */ 19 | class ForecastException extends Exception 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /lib/ForecastFlags.php: -------------------------------------------------------------------------------- 1 | 13 | * @license http://opensource.org/licenses/MIT The MIT License 14 | * @version 1.0 15 | * @link http://github.com/CNG/ForecastTools 16 | * @example ../example.php 17 | */ 18 | class ForecastFlags 19 | { 20 | 21 | private $_flags; 22 | 23 | /** 24 | * Create ForecastFlags object 25 | * 26 | * @param object $flags JSON decoded flags from API response 27 | */ 28 | public function __construct($flags) 29 | { 30 | $this->_flags = $flags; 31 | } 32 | 33 | /** 34 | * The presence of this property indicates that the Dark Sky data source 35 | * supports the given location, but a temporary error (such as a radar station 36 | * being down for maintenace) has made the data unavailable. 37 | * 38 | * @return bool true if flags object has “darksky-unavailable” property or 39 | * false if not 40 | */ 41 | public function getDarkskyUnavailable() 42 | { 43 | $field = 'darksky-unavailable'; 44 | return property_exists($this->_flags->$field); 45 | } 46 | 47 | /** 48 | * This property contains an array of IDs for each radar station utilized in 49 | * servicing this request. 50 | * 51 | * @return string|bool flags object “darksky-stations” data or false if none 52 | */ 53 | public function getDarkskyStations() 54 | { 55 | $field = 'darksky-stations'; 56 | return empty($this->_flags->$field) ? false : $this->_flags->$field; 57 | } 58 | 59 | /** 60 | * This property contains an array of IDs for each DataPoint station utilized 61 | * in servicing this request. 62 | * 63 | * @return string|bool flags object “datapoint-stations” data or false if none 64 | */ 65 | public function getDatapointStations() 66 | { 67 | $field = 'datapoint-stations'; 68 | return empty($this->_flags->$field) ? false : $this->_flags->$field; 69 | } 70 | 71 | /** 72 | * This property contains an array of IDs for each ISD station utilized in 73 | * servicing this request. 74 | * 75 | * @return string|bool flags object “isd-stations” data or false if none 76 | */ 77 | public function getISDStations() 78 | { 79 | $field = 'isd-stations'; 80 | return empty($this->_flags->$field) ? false : $this->_flags->$field; 81 | } 82 | 83 | /** 84 | * This property contains an array of IDs for each LAMP station utilized in 85 | * servicing this request. 86 | * 87 | * @return string|bool flags object “lamp-stations” data or false if none 88 | */ 89 | public function getLAMPStations() 90 | { 91 | $field = 'lamp-stations'; 92 | return empty($this->_flags->$field) ? false : $this->_flags->$field; 93 | } 94 | 95 | /** 96 | * This property contains an array of IDs for each METAR station utilized in 97 | * servicing this request. 98 | * 99 | * @return string|bool flags object “metar-stations” data or false if none 100 | */ 101 | public function getMETARStations() 102 | { 103 | $field = 'metar-stations'; 104 | return empty($this->_flags->$field) ? false : $this->_flags->$field; 105 | } 106 | 107 | /** 108 | * The presence of this property indicates that data from api.met.no was 109 | * utilized in order to facilitate this request (as per their license 110 | * agreement). 111 | * 112 | * @return bool true if flags object has “metno-license” property or false if 113 | * not 114 | */ 115 | public function getMetnoLicense() 116 | { 117 | $field = 'metno-license'; 118 | return property_exists($this->_flags->$field); 119 | } 120 | 121 | /** 122 | * This property contains an array of IDs for each data source utilized in 123 | * servicing this request. 124 | * 125 | * @return string|bool flags object “sources” data or false if none 126 | */ 127 | public function getSources() 128 | { 129 | $field = 'sources'; 130 | return empty($this->_flags->$field) ? false : $this->_flags->$field; 131 | } 132 | 133 | /** 134 | * The presence of this property indicates which units were used for the data 135 | * in this request. 136 | * 137 | * @return string|bool flags object “units” data or false if none 138 | */ 139 | public function getUnits() 140 | { 141 | $field = 'units'; 142 | return empty($this->_flags->$field) ? false : $this->_flags->$field; 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /lib/ForecastResponse.php: -------------------------------------------------------------------------------- 1 | 18 | * @license http://opensource.org/licenses/MIT The MIT License 19 | * @version 1.0 20 | * @link http://github.com/CNG/ForecastTools 21 | * @example ../example.php 22 | */ 23 | class ForecastResponse 24 | { 25 | 26 | private $_response; 27 | 28 | /** 29 | * Create ForecastResponse object 30 | * 31 | * @param object $response Entire JSON decoded response from API 32 | */ 33 | public function __construct($response) 34 | { 35 | $this->_response = $response; 36 | } 37 | 38 | /** 39 | * Get a JSON formatted object that is the entire response from Forecast.io. 40 | * This is useful if you do not wish to use any of the get methods provided 41 | * by this class or for accessing new or otherwise not otherwise accessible 42 | * data in the response. 43 | * 44 | * @return Object JSON-formatted object with the following properties defined: 45 | * latitude, longitude, timezone, offset, currently[, minutely, hourly, daily, 46 | * alerts, flags] 47 | */ 48 | public function getRawData() 49 | { 50 | return $this->_response; 51 | } 52 | 53 | /** 54 | * The requested latitude. 55 | * 56 | * @return float The requested latitude 57 | */ 58 | public function getLatitude() 59 | { 60 | $field = 'latitude'; 61 | return property_exists($this->_response->$field); 62 | } 63 | 64 | /** 65 | * The requested longitude. 66 | * 67 | * @return float The requested longitude 68 | */ 69 | public function getLongitude() 70 | { 71 | $field = 'longitude'; 72 | return property_exists($this->_response->$field); 73 | } 74 | 75 | /** 76 | * The IANA timezone name for the requested location (e.g. America/New_York). 77 | * This is the timezone used for text forecast summaries and for determining 78 | * the exact start time of daily data points. (Developers are advised to rely 79 | * on local system settings rather than this value if at all possible: users 80 | * may deliberately set an unusual timezone, and furthermore are likely to 81 | * know what they actually want better than our timezone database does.) 82 | * 83 | * @return string The IANA timezone name for the requested location 84 | */ 85 | public function getTimezone() 86 | { 87 | $field = 'timezone'; 88 | return property_exists($this->_response->$field); 89 | } 90 | 91 | /** 92 | * The current timezone offset in hours from GMT. 93 | * 94 | * @return string The current timezone offset in hours from GMT. 95 | */ 96 | public function getOffset() 97 | { 98 | $field = 'offset'; 99 | return property_exists($this->_response->$field); 100 | } 101 | 102 | /** 103 | * Get number of ForecastDataPoint objects that exist within specified block 104 | * 105 | * @param string $type Type of data block 106 | * 107 | * @return int Returns number of ForecastDataPoint objects that exist within 108 | * specified block 109 | */ 110 | public function getCount($type) 111 | { 112 | $response = $this->_response; 113 | return empty($response->$type->data) ? false : count($response->$type->data); 114 | } 115 | 116 | /** 117 | * Get ForecastDataPoint object for current or specified time 118 | * 119 | * @return ForecastDataPoint ForecastDataPoint object for current or specified time 120 | */ 121 | public function getCurrently() 122 | { 123 | include_once 'ForecastDataPoint.php'; 124 | return new ForecastDataPoint($this->_response->currently); 125 | } 126 | 127 | /** 128 | * Get ForecastDataPoint object(s) desired within the specified block 129 | * 130 | * @param string $type Type of data block ( 131 | * @param int $index Optional numeric index of desired data point in block 132 | * beginning with 0 133 | * 134 | * @return array|ForecastDataPoint|bool Returns an array of ForecastDataPoint 135 | * objects within the block OR a single ForecastDataPoint object for specified 136 | * block OR false if no applicable block 137 | */ 138 | private function _getBlock($type, $index = null) 139 | { 140 | 141 | if ($this->getCount($type)) { 142 | 143 | include_once 'ForecastDataPoint.php'; 144 | $block_data = $this->_response->$type->data; 145 | if (is_null($index)) { 146 | $points = array(); 147 | foreach ($block_data as $point_data) { 148 | $points[] = new ForecastDataPoint($point_data); 149 | } 150 | return $points; 151 | } elseif (is_int($index) && $this->getCount($type) > $index) { 152 | return new ForecastDataPoint($block_data[$index]); 153 | } 154 | 155 | } 156 | return false; // if no block, block but no data, or invalid index specified 157 | 158 | } 159 | 160 | /** 161 | * Get ForecastDataPoint object(s) desired within the minutely block, which is 162 | * weather conditions minute-by-minute for the next hour. 163 | * 164 | * @param int $index Optional numeric index of desired data point in block 165 | * beginning with 0 166 | * 167 | * @return array|ForecastDataPoint|bool Returns an array of ForecastDataPoint 168 | * objects within the block OR a single ForecastDataPoint object for specified 169 | * block OR false if no applicable block 170 | */ 171 | public function getMinutely($index = null) 172 | { 173 | $type = 'minutely'; 174 | return $this->_getBlock($type, $index); 175 | } 176 | 177 | /** 178 | * Get ForecastDataPoint object(s) desired within the hourly block, which is 179 | * weather conditions hour-by-hour for the next two days. 180 | * 181 | * @param int $index Optional numeric index of desired data point in block 182 | * beginning with 0 183 | * 184 | * @return array|ForecastDataPoint|bool Returns an array of ForecastDataPoint 185 | * objects within the block OR a single ForecastDataPoint object for specified 186 | * block OR false if no applicable block 187 | */ 188 | public function getHourly($index = null) 189 | { 190 | $type = 'hourly'; 191 | return $this->_getBlock($type, $index); 192 | } 193 | 194 | /** 195 | * Get ForecastDataPoint object(s) desired within the daily block, which is 196 | * weather conditions day-by-day for the next week. 197 | * 198 | * @param int $index Optional numeric index of desired data point in block 199 | * beginning with 0 200 | * 201 | * @return array|ForecastDataPoint|bool Returns an array of ForecastDataPoint 202 | * objects within the block OR a single ForecastDataPoint object for specified 203 | * block OR false if no applicable block 204 | */ 205 | public function getDaily($index = null) 206 | { 207 | $type = 'daily'; 208 | return $this->_getBlock($type, $index); 209 | } 210 | 211 | /** 212 | * Get an array of ForecastAlert objects, which, if present, contain any 213 | * severe weather alerts, issued by a governmental weather authority, 214 | * pertinent to the requested location. 215 | * 216 | * @return array|bool Array of ForecastAlert objects OR false if none 217 | */ 218 | public function getAlerts() 219 | { 220 | 221 | if (!empty($this->_response->alert)) { 222 | include_once 'ForecastAlert.php'; 223 | $alerts = array(); 224 | foreach ($this->_response->alert as $alert) { 225 | $alerts[] = new ForecastAlert($alert); 226 | } 227 | return $alerts; 228 | } else { 229 | return false; 230 | } 231 | 232 | } 233 | 234 | /** 235 | * Get ForecastFlags object of miscellaneous metadata concerning this request. 236 | * 237 | * @return ForecastFlags|bool ForecastFlags object OR false if none 238 | */ 239 | public function getFlags() 240 | { 241 | 242 | if (!empty($this->_response->flags)) { 243 | include_once 'ForecastFlags.php'; 244 | return new ForecastFlags($this->_response->flags); 245 | } else { 246 | return false; 247 | } 248 | 249 | } 250 | 251 | } 252 | --------------------------------------------------------------------------------