From bc58fa2f8db342bf78c7bb97b2040f061b1db866 Mon Sep 17 00:00:00 2001 From: dnsimmons Date: Sun, 28 Jun 2020 20:03:30 -0400 Subject: [PATCH] Added onecall api support and data struct refactoring --- readme.md | 121 ++--------- src/OpenWeather.php | 326 ++++++++++++++++++++++++----- src/OpenWeatherServiceProvider.php | 4 +- src/config/openweather.php | 15 +- 4 files changed, 305 insertions(+), 161 deletions(-) diff --git a/readme.md b/readme.md index b257e91..48ac596 100644 --- a/readme.md +++ b/readme.md @@ -16,7 +16,14 @@ OpenWeather is a [Laravel](https://laravel.com) package simplifying working with The package supports the following free Open Weather Map APIs: - [Current Weather](https://openweathermap.org/current) -- [5 Day 3 Hour Forecast](https://openweathermap.org/forecast5) +- [4 Day 3 Hour Forecast](https://openweathermap.org/api/hourly-forecast) +- [Onecall Forecast](https://openweathermap.org/api/one-call-api) +- 5 Day Historical (coming soon) + +### Free API Limitations + +- 60 calls/minute up to 1,000,000 calls/month +- 1000 calls/day when using Onecall requests ### Requirements @@ -48,115 +55,25 @@ Publish the required package configuration file using the artisan command: Edit the `config/openweather.php` file in your Laravel instance and modify the `api_key` value with your Open Weather Map api key (App ID). return [ - 'api_key' => 'your-api-key', - 'api_endpoint_current' => 'http://api.openweathermap.org/data/2.5/weather?', - 'api_endpoint_forecast' => 'http://api.openweathermap.org/data/2.5/forecast?', - 'api_lang' => 'en', + 'api_key' => 'your-api-key', + 'api_endpoint_current' => 'https://api.openweathermap.org/data/2.5/weather?', + 'api_endpoint_forecast' => 'https://api.openweathermap.org/data/2.5/forecast?', + 'api_endpoint_onecall' => 'https://api.openweathermap.org/data/2.5/onecall?', + 'api_endpoint_icons' => 'https://openweathermap.org/img/w/', + 'api_lang' => 'en', + 'format_date' => 'm/d/Y', + 'format_time' => 'h:i A', + 'format_day' => 'l' ]; ## Usage -### Current Weather +In the example below we fetch the current weather by postal code. $weather = new OpenWeather(); $current = $weather->getCurrentWeatherByPostal('02111'); print_r($current); -**Output** - - Array - ( - [timestamp] => 1551160643 - [location] => Array - ( - [name] => Boston - [country] => US - [latitude] => 42.36 - [longitude] => -71.06 - ) - - [condition] => Array - ( - [name] => Clear - [desc] => clear sky - [icon] => http://openweathermap.org/img/w/01n.png - ) - - [forecast] => Array - ( - [temp] => 26 - [temp_min] => 24 - [temp_max] => 28 - [pressure] => 1016 - [humidity] => 42 - [sunrise] => 1551180262 - [sunset] => 1551220226 - ) - - ) - -### 5 Day 3 Hour Forecast - - $weather = new OpenWeather(); - $forecast = $weather->getForecastWeatherByPostal('02111'); - print_r($forecast); - -**Output** - - Array - ( - [location] => Array - ( - [name] => Boston - [country] => US - [latitude] => 42.3603 - [longitude] => -71.0583 - ) - - [forecast] => Array - ( - [0] => Array - ( - [timestamp] => 1551160800 - [condition] => Array - ( - [name] => Clear - [desc] => clear sky - [icon] => http://openweathermap.org/img/w/01n.png - ) - - [forecast] => Array - ( - [temp] => 25 - [temp_min] => 25 - [temp_max] => 29 - [pressure] => 1014 - [humidity] => 100 - ) - - ) - - [1] => Array - ( - [timestamp] => 1551171600 - [condition] => Array - ( - [name] => Clear - [desc] => clear sky - [icon] => http://openweathermap.org/img/w/01n.png - ) - - [forecast] => Array - ( - [temp] => 24 - [temp_min] => 24 - [temp_max] => 27 - [pressure] => 1017 - [humidity] => 100 - ) - - ) - ... ## Methods @@ -177,3 +94,5 @@ Units can be imperial (default), metric, or kelvin. All methods return an array **getForecastWeatherByCoords**(*string $latitude*, *string $longitude*, *string $units*) **getForecastWeatherByPostal**(*string $postal*, *string $units*) + +**getOnecallWeatherByCoords**(*string $latitude*, *string $longitude*, *string $units*, *string $exclude*) diff --git a/src/OpenWeather.php b/src/OpenWeather.php index 80e96c7..39829f5 100644 --- a/src/OpenWeather.php +++ b/src/OpenWeather.php @@ -2,7 +2,7 @@ namespace Dnsimmons\OpenWeather; -use Config; +use Illuminate\Support\Facades\Config; /** * OpenWeather is a Laravel package simplifying working with the free Open Weather Map APIs. @@ -11,7 +11,7 @@ * @package OpenWeather * @author David Simmons * @license https://opensource.org/licenses/LGPL-3.0 LGPL-3.0 -* @version 1.0.0 +* @version 1.0.1 * @since 2019-01-01 */ class OpenWeather{ @@ -19,26 +19,36 @@ class OpenWeather{ private $api_key = NULL; private $api_endpoint_current = NULL; private $api_endpoint_forecast = NULL; + private $api_endpoint_onecall = NULL; private $api_lang = NULL; + private $url_icons = NULL; + private $format_date = NULL; + private $format_time = NULL; + private $format_units = NULL; /** * Constructor. * - * @return void + * @return void */ public function __construct(){ $this->api_key = Config::get('openweather.api_key'); $this->api_endpoint_current = Config::get('openweather.api_endpoint_current'); $this->api_endpoint_forecast = Config::get('openweather.api_endpoint_forecast'); + $this->api_endpoint_onecall = Config::get('openweather.api_endpoint_onecall'); + $this->api_endpoint_icons = Config::get('openweather.api_endpoint_icons'); $this->api_lang = Config::get('openweather.api_lang'); + $this->format_date = Config::get('openweather.format_date'); + $this->format_time = Config::get('openweather.format_time'); + $this->format_day = Config::get('openweather.format_day'); } /** * Performs an API request and returns the response. * Returns FALSE on failure. - * + * * @param string $url Request URI - * @return string + * @return string|bool */ private function doRequest(string $url){ $response = @file_get_contents($url); @@ -48,19 +58,37 @@ private function doRequest(string $url){ /** * Parses and returns an OpenWeather current weather API response as an array of formatted values. * Returns FALSE on failure. - * + * * @param string $response OpenWeather API response JSON. - * @return array + * @return array|bool */ private function parseCurrentResponse(string $response){ + $struct = json_decode($response, TRUE); if(!isset($struct['cod']) || $struct['cod'] != 200){ return FALSE; } return [ - 'timestamp' => $struct['dt'], + 'formats' => [ + 'lang' => $this->api_lang, + 'date' => $this->format_date, + 'day' => $this->format_day, + 'time' => $this->format_time, + 'units' => $this->format_units, + ], + 'datetime' => [ + 'timestamp' => $struct['dt'], + 'timestamp_sunrise' => $struct['sys']['sunrise'], + 'timestamp_sunset' => $struct['sys']['sunset'], + 'formatted_date' => date($this->format_date, $struct['dt']), + 'formatted_day' => date($this->format_day, $struct['dt']), + 'formatted_time' => date($this->format_time, $struct['dt']), + 'formatted_sunrise' => date($this->format_time, $struct['sys']['sunrise']), + 'formatted_sunset' => date($this->format_time, $struct['sys']['sunset']), + ], 'location' => [ - 'name' => $struct['name'], + 'id' => (isset($struct['id'])) ? $struct['id'] : 0, + 'name' => $struct['name'], 'country' => $struct['sys']['country'], 'latitude' => $struct['coord']['lat'], 'longitude' => $struct['coord']['lon'], @@ -68,16 +96,14 @@ private function parseCurrentResponse(string $response){ 'condition' => [ 'name' => $struct['weather'][0]['main'], 'desc' => $struct['weather'][0]['description'], - 'icon' => 'http://openweathermap.org/img/w/'.$struct['weather'][0]['icon'].'.png', + 'icon' => $this->api_endpoint_icons.$struct['weather'][0]['icon'].'.png', ], 'forecast' => [ - 'temp' => round($struct['main']['temp']), - 'temp_min' => round($struct['main']['temp_min']), - 'temp_max' => round($struct['main']['temp_max']), - 'pressure' => round($struct['main']['pressure']), - 'humidity' => round($struct['main']['humidity']), - 'sunrise' => $struct['sys']['sunrise'], - 'sunset' => $struct['sys']['sunset'], + 'temp' => round($struct['main']['temp']), + 'temp_min' => round($struct['main']['temp_min']), + 'temp_max' => round($struct['main']['temp_max']), + 'pressure' => round($struct['main']['pressure']), + 'humidity' => round($struct['main']['humidity']), ] ]; } @@ -85,36 +111,54 @@ private function parseCurrentResponse(string $response){ /** * Parses and returns an OpenWeather forecast weather API response as an array of formatted values. * Returns FALSE on failure. - * + * * @param string $response OpenWeather API response JSON. - * @return array + * @return array|bool */ private function parseForecastResponse(string $response){ $struct = json_decode($response, TRUE); if(!isset($struct['cod']) || $struct['cod'] != 200){ return FALSE; } + $forecast = []; foreach($struct['list'] as $item){ $forecast[] = [ - 'timestamp' => $item['dt'], + 'datetime' => [ + 'timestamp' => $item['dt'], + 'timestamp_sunrise' => $struct['city']['sunrise'], + 'timestamp_sunset' => $struct['city']['sunset'], + 'formatted_date' => date($this->format_date, $item['dt']), + 'formatted_day' => date($this->format_day, $item['dt']), + 'formatted_time' => date($this->format_time, $item['dt']), + 'formatted_sunrise' => date($this->format_time, $struct['city']['sunrise']), + 'formatted_sunset' => date($this->format_time, $struct['city']['sunset']), + ], 'condition' => [ 'name' => $item['weather'][0]['main'], 'desc' => $item['weather'][0]['description'], - 'icon' => 'http://openweathermap.org/img/w/'.$item['weather'][0]['icon'].'.png', + 'icon' => $this->api_endpoint_icons.$item['weather'][0]['icon'].'.png', ], 'forecast' => [ - 'temp' => round($item['main']['temp']), - 'temp_min' => round($item['main']['temp_min']), - 'temp_max' => round($item['main']['temp_max']), - 'pressure' => round($item['main']['pressure']), - 'humidity' => round($item['main']['humidity']), + 'temp' => round($item['main']['temp']), + 'temp_min' => round($item['main']['temp_min']), + 'temp_max' => round($item['main']['temp_max']), + 'pressure' => round($item['main']['pressure']), + 'humidity' => round($item['main']['humidity']), ] ]; } return [ + 'formats' => [ + 'lang' => $this->api_lang, + 'date' => $this->format_date, + 'day' => $this->format_day, + 'time' => $this->format_time, + 'units' => $this->format_units, + ], 'location' => [ - 'name' => $struct['city']['name'], + 'id' => (isset($struct['city']['id'])) ? $struct['city']['id'] : 0, + 'name' => $struct['city']['name'], 'country' => $struct['city']['country'], 'latitude' => $struct['city']['coord']['lat'], 'longitude' => $struct['city']['coord']['lon'], @@ -123,12 +167,137 @@ private function parseForecastResponse(string $response){ ]; } + /** + * Parses and returns an OpenWeather onecall weather API response as an array of formatted values. + * Returns FALSE on failure. + * + * @param string $response OpenWeather API response JSON. + * @return array|bool + */ + private function parseOnecallResponse(string $response) + { + + $struct = json_decode($response, TRUE); + if(!isset($struct['cod']) || $struct['cod'] != 200){ + // @TODO rght now there is no cod element to check in the API response + } + + $forecast = []; + + $current = []; + if(isset($struct['current'])) { + $current['datetime'] = [ + 'timestamp' => $struct['current']['dt'], + 'timestamp_sunrise' => $struct['current']['sunrise'], + 'timestamp_sunset' => $struct['current']['sunset'], + 'formatted_date' => date($this->format_date, $struct['current']['dt']), + 'formatted_day' => date($this->format_day, $struct['current']['dt']), + 'formatted_time' => date($this->format_time, $struct['current']['dt']), + 'formatted_sunrise' => date($this->format_time, $struct['current']['sunrise']), + 'formatted_sunset' => date($this->format_time, $struct['current']['sunset']), + ]; + $current['condition'] = [ + 'name' => $struct['current']['weather'][0]['main'], + 'desc' => $struct['current']['weather'][0]['description'], + 'icon' => $this->api_endpoint_icons.$struct['current']['weather'][0]['icon'].'.png', + ]; + $current['forecast'] = [ + 'temp' => round($struct['current']['temp']), + 'pressure' => round($struct['current']['pressure']), + 'humidity' => round($struct['current']['humidity']), + ]; + } + + $minutely = []; + if(isset($struct['minutely'])){ + //@TODO implement once better supported by the API + } + + $hourly = []; + if(isset($struct['hourly'])){ + foreach($struct['hourly'] as $item){ + $hourly[] = [ + 'datetime' => [ + 'timestamp' => $item['dt'], + 'timestamp_sunrise' => $struct['current']['sunrise'], + 'timestamp_sunset' => $struct['current']['sunset'], + 'formatted_date' => date($this->format_date, $item['dt']), + 'formatted_day' => date($this->format_day, $item['dt']), + 'formatted_time' => date($this->format_time, $item['dt']), + 'formatted_sunrise' => date($this->format_time, $struct['current']['sunrise']), + 'formatted_sunset' => date($this->format_time, $struct['current']['sunset']), + ], + 'condition' => [ + 'name' => $item['weather'][0]['main'], + 'desc' => $item['weather'][0]['description'], + 'icon' => $this->url_icons.$item['weather'][0]['icon'].'.png', + ], + 'forecast' => [ + 'temp' => round($item['temp']), + 'pressure' => round($item['pressure']), + 'humidity' => round($item['humidity']), + ] + ]; + } + $forecast['hourly'] = $hourly; + } + + $daily = []; + if(isset($struct['daily'])){ + foreach($struct['daily'] as $item){ + $daily[] = [ + 'datetime' => [ + 'timestamp' => $item['dt'], + 'timestamp_sunrise' => $item['sunrise'], + 'timestamp_sunset' => $item['sunset'], + 'formatted_date' => date($this->format_date, $item['dt']), + 'formatted_day' => date($this->format_day, $item['dt']), + 'formatted_time' => date($this->format_time, $item['dt']), + 'formatted_sunrise' => date($this->format_time, $item['sunrise']), + 'formatted_sunset' => date($this->format_time, $item['sunset']), + ], + 'condition' => [ + 'name' => $item['weather'][0]['main'], + 'desc' => $item['weather'][0]['description'], + 'icon' => $this->api_endpoint_icons.$item['weather'][0]['icon'].'.png', + ], + 'forecast' => [ + 'temp' => round($item['temp']['day']), + 'temp_min' => round($item['temp']['min']), + 'temp_max' => round($item['temp']['max']), + 'pressure' => round($item['pressure']), + 'humidity' => round($item['humidity']), + ] + ]; + } + $forecast['daily'] = $daily; + } + + return [ + 'formats' => [ + 'lang' => $this->api_lang, + 'date' => $this->format_date, + 'day' => $this->format_day, + 'time' => $this->format_time, + 'units' => $this->format_units, + ], + 'location' => [ + 'latitude' => $struct['lat'], + 'longitude' => $struct['lon'], + ], + 'current' => $current, + 'minutely' => $minutely, + 'hourly' => $hourly, + 'daily' => $daily + ]; + } + /** * Returns an OpenWeather API response for current weather. * Returns FALSE on failure. - * + * * @param array $params Array of request parameters. - * @return array + * @return array|bool */ private function getCurrentWeather(array $params){ $params = http_build_query($params); @@ -147,9 +316,9 @@ private function getCurrentWeather(array $params){ /** * Returns an OpenWeather API response for forecast weather. * Returns FALSE on failure. - * + * * @param array $params Array of request parameters. - * @return array + * @return array|bool */ private function getForecastWeather(array $params){ $params = http_build_query($params); @@ -165,16 +334,37 @@ private function getForecastWeather(array $params){ return $response; } + /** + * Returns an OpenWeather API response for onecall weather. + * Returns FALSE on failure. + * + * @param array $params Array of request parameters. + * @return array|bool + */ + private function getOnecallWeather(array $params){ + $params = http_build_query($params); + $request = $this->api_endpoint_onecall.$params; + $response = $this->doRequest($request); + if(!$response){ + return FALSE; + } + $response = $this->parseOnecallResponse($response); + if(!$response){ + return FALSE; + } + return $response; + } /** * Returns current weather by city name. * Returns FALSE on failure. - * + * * @param string $city City name (Boston) or city name and country code (Boston,US). * @param string $units Units of measurement (imperial, metric, kelvin) - * @return array + * @return array|bool */ public function getCurrentWeatherByCityName(string $city, string $units='imperial'){ + $this->format_units = $units; return $this->getCurrentWeather([ 'q' => $city, 'units' => $units, @@ -186,14 +376,15 @@ public function getCurrentWeatherByCityName(string $city, string $units='imperia /** * Returns current weather by city ID. * Returns FALSE on failure. - * + * * @param int $id OpenWeather city ID * @param string $units Units of measurement (imperial, metric, kelvin) - * @return array + * @return array|bool */ public function getCurrentWeatherByCityId(int $id, string $units='imperial'){ + $this->format_units = $units; return $this->getCurrentWeather([ - 'id' => $city, + 'id' => $id, 'units' => $units, 'lang' => $this->api_lang, 'appid' => $this->api_key @@ -203,16 +394,17 @@ public function getCurrentWeatherByCityId(int $id, string $units='imperial'){ /** * Returns current weather by latitude and longitude. * Returns FALSE on failure. - * + * * @param string $latitude Latitude * @param string $longitude Longitude * @param string $units Units of measurement (imperial, metric, kelvin) - * @return array + * @return array|bool */ public function getCurrentWeatherByCoords(string $latitude, string $longitude, string $units='imperial'){ + $this->format_units = $units; return $this->getCurrentWeather([ 'lat' => $latitude, - 'lng' => $longitude, + 'lon' => $longitude, 'units' => $units, 'lang' => $this->api_lang, 'appid' => $this->api_key @@ -222,12 +414,13 @@ public function getCurrentWeatherByCoords(string $latitude, string $longitude, s /** * Returns current weather by postal code. * Returns FALSE on failure. - * + * * @param string $postal Postal code * @param string $units Units of measurement (imperial, metric, kelvin) - * @return array + * @return array|bool */ public function getCurrentWeatherByPostal(string $postal, string $units='imperial'){ + $this->format_units = $units; return $this->getCurrentWeather([ 'zip' => $postal, 'units' => $units, @@ -239,12 +432,13 @@ public function getCurrentWeatherByPostal(string $postal, string $units='imperia /** * Returns forecast weather by city name. * Returns FALSE on failure. - * + * * @param string $city City name (Boston) or city name and country code (Boston,US). * @param string $units Units of measurement (imperial, metric, kelvin) - * @return array + * @return array|bool */ public function getForecastWeatherByCityName(string $city, string $units='imperial'){ + $this->format_units = $units; return $this->getForecastWeather([ 'q' => $city, 'units' => $units, @@ -256,14 +450,15 @@ public function getForecastWeatherByCityName(string $city, string $units='imperi /** * Returns forecast weather by city ID. * Returns FALSE on failure. - * + * * @param int $id OpenWeather city ID * @param string $units Units of measurement (imperial, metric, kelvin) - * @return array + * @return array|bool */ public function getForecastWeatherByCityId(int $id, string $units='imperial'){ + $this->format_units = $units; return $this->getForecastWeather([ - 'id' => $city, + 'id' => $id, 'units' => $units, 'lang' => $this->api_lang, 'appid' => $this->api_key @@ -273,16 +468,17 @@ public function getForecastWeatherByCityId(int $id, string $units='imperial'){ /** * Returns forecast weather by latitude and longitude. * Returns FALSE on failure. - * + * * @param string $latitude Latitude * @param string $longitude Longitude * @param string $units Units of measurement (imperial, metric, kelvin) - * @return array + * @return array|bool */ public function getForecastWeatherByCoords(string $latitude, string $longitude, string $units='imperial'){ + $this->format_units = $units; return $this->getForecastWeather([ 'lat' => $latitude, - 'lng' => $longitude, + 'lon' => $longitude, 'units' => $units, 'lang' => $this->api_lang, 'appid' => $this->api_key @@ -290,14 +486,15 @@ public function getForecastWeatherByCoords(string $latitude, string $longitude, } /** - * Returns forecast weather by posta code. + * Returns forecast weather by postal code. * Returns FALSE on failure. - * + * * @param string $postal Postal code * @param string $units Units of measurement (imperial, metric, kelvin) - * @return array + * @return array|bool */ public function getForecastWeatherByPostal(string $postal, string $units='imperial'){ + $this->format_units = $units; return $this->getForecastWeather([ 'zip' => $postal, 'units' => $units, @@ -306,4 +503,27 @@ public function getForecastWeatherByPostal(string $postal, string $units='imperi ]); } -} // end class \ No newline at end of file + /** + * Returns onecast weather by latitude and longitude. + * Returns FALSE on failure. + * + * @param string $latitude Latitude + * @param string $longitude Longitude + * @param string $units Units of measurement (imperial, metric, kelvin) + * @param string $exclude Optional exclude specific data by tag (hourly, daily) + * @return array|bool + */ + public function getOnecallWeatherByCoords(string $latitude, string $longitude, string $units='imperial', string $exclude='') + { + $this->format_units = $units; + return $this->getOnecallWeather([ + 'lat' => $latitude, + 'lon' => $longitude, + 'part' => 'minutely'.($exclude != '') ? ','.$exclude : '', + 'units' => $units, + 'lang' => $this->api_lang, + 'appid' => $this->api_key + ]); + } + +} // end class diff --git a/src/OpenWeatherServiceProvider.php b/src/OpenWeatherServiceProvider.php index 4990f11..b7c4cb8 100644 --- a/src/OpenWeatherServiceProvider.php +++ b/src/OpenWeatherServiceProvider.php @@ -12,7 +12,7 @@ class OpenWeatherServiceProvider extends ServiceProvider{ */ public function boot(){ $this->publishes([ - __DIR__.'/config/openweather.php' => config_path('openweather.php'), + __DIR__ . '/config/openweather.php' => config_path('openweather.php'), ]); } @@ -24,6 +24,6 @@ public function boot(){ public function register(){ } - + } diff --git a/src/config/openweather.php b/src/config/openweather.php index 394cfb7..8dd9676 100644 --- a/src/config/openweather.php +++ b/src/config/openweather.php @@ -1,10 +1,15 @@ 'your-api-key', - 'api_endpoint_current' => 'http://api.openweathermap.org/data/2.5/weather?', - 'api_endpoint_forecast' => 'http://api.openweathermap.org/data/2.5/forecast?', - 'api_lang' => 'en', + 'api_key' => 'a53946f8c34c69cb84eb9b3db5e68240', + 'api_endpoint_current' => 'https://api.openweathermap.org/data/2.5/weather?', + 'api_endpoint_forecast' => 'https://api.openweathermap.org/data/2.5/forecast?', + 'api_endpoint_onecall' => 'https://api.openweathermap.org/data/2.5/onecall?', + 'api_endpoint_icons' => 'https://openweathermap.org/img/w/', + 'api_lang' => 'en', + 'format_date' => 'm/d/Y', + 'format_time' => 'h:i A', + 'format_day' => 'l' ]; -?> \ No newline at end of file +?>