From c9710c3d67738ad0d761ae8b8aacda20e6fffc84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Kl=C3=ADma?= Date: Wed, 16 Oct 2019 14:34:48 +0200 Subject: [PATCH 1/2] added Air Pollution API --- Cmfcmf/OpenWeatherMap.php | 65 +++++++++++++++++- Cmfcmf/OpenWeatherMap/AirPollution.php | 73 ++++++++++++++++++++ Cmfcmf/OpenWeatherMap/AirPollutionData.php | 79 ++++++++++++++++++++++ tests/OpenWeatherMapTest.php | 11 +++ 4 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 Cmfcmf/OpenWeatherMap/AirPollution.php create mode 100644 Cmfcmf/OpenWeatherMap/AirPollutionData.php diff --git a/Cmfcmf/OpenWeatherMap.php b/Cmfcmf/OpenWeatherMap.php index 1490280..9e73e64 100644 --- a/Cmfcmf/OpenWeatherMap.php +++ b/Cmfcmf/OpenWeatherMap.php @@ -18,10 +18,11 @@ namespace Cmfcmf; +use Cmfcmf\OpenWeatherMap\AirPollution; use Cmfcmf\OpenWeatherMap\CurrentWeather; -use Cmfcmf\OpenWeatherMap\UVIndex; use Cmfcmf\OpenWeatherMap\CurrentWeatherGroup; use Cmfcmf\OpenWeatherMap\Exception as OWMException; +use Cmfcmf\OpenWeatherMap\UVIndex; use Cmfcmf\OpenWeatherMap\WeatherForecast; use Psr\Cache\CacheItemPoolInterface; use Psr\Http\Client\ClientInterface; @@ -67,6 +68,11 @@ class OpenWeatherMap */ private $uvIndexUrl = 'https://api.openweathermap.org/data/2.5/uvi'; + /** + * @var string The basic api url to fetch air pollution data from. + */ + private $airPollutionUrl = 'https://api.openweathermap.org/pollution/v1/co'; + /** * @var CacheItemPoolInterface|null $cache The cache to use. */ @@ -336,6 +342,31 @@ public function getHistoricUVIndex($lat, $lon, $start, $end) }, $data); } + /** + * Returns atmosferic pollution by Carbon Monoxie + * + * @param float $lat The location's latitude. + * @param float $lon The location's longitude. + * @param ?DateTime|string $dateTime Time of the measurement. Set null for "current" + * + * @return AirPollution + * + * @api + * @throws \Exception + */ + public function getAirPollution($lat, $lon, $dateTime = null) + { + if ($dateTime === null) { + $dateTime = 'current'; + } + + $answer = $this->getRawAirPollutionData($lat, $lon, $dateTime); + + $json = $this->parseJson($answer); + + return new AirPollution($json); + } + /** * Directly returns the xml/json/html string returned by OpenWeatherMap for the current weather. * @@ -471,6 +502,20 @@ public function getRawUVIndexData($mode, $lat, $lon, $cnt = null, $start = null, return $this->cacheOrFetchResult($url); } + /** + * @param $lat + * @param $lon + * @param $dateTime + * @return bool|string Returns the fetched data. + * @throws OWMException + */ + public function getRawAirPollutionData($lat, $lon, $dateTime) + { + $url = $this->buildAirPollutionUrl($lat, $lon, $dateTime, $this->airPollutionUrl); + + return $this->cacheOrFetchResult($url); + } + /** * Returns whether or not the last result was fetched from the cache. * @@ -505,6 +550,9 @@ private function cacheOrFetchResult($url) $response = $this->httpClient->sendRequest($this->httpRequestFactory->createRequest("GET", $url)); $result = $response->getBody()->getContents(); if ($response->getStatusCode() !== 200) { + if ($result === '{"message":"not found"}' && $response->getStatusCode() === 404) { + throw new OWMException('OpenWeatherMap returned that air pollution data for this location cannot be found - try less precision location.'); + } throw new OWMException('OpenWeatherMap returned a response with status code ' . $response->getStatusCode() . ' and the following content '. $result); } @@ -606,6 +654,21 @@ private function buildQueryUrlParameter($query) } } + /** + * Build the url to fetch air pollution data from. + * + * @param $lat + * @param $lon + * @param $dateTime + * @param string $url The url to prepend. + * + * @return string The fetched url + */ + private function buildAirPollutionUrl($lat, $lon, $dateTime, $url) + { + return $url."/$lat,$lon/$dateTime.json?appid=" . $this->apiKey; + } + /** * @param string $answer The content returned by OpenWeatherMap. * diff --git a/Cmfcmf/OpenWeatherMap/AirPollution.php b/Cmfcmf/OpenWeatherMap/AirPollution.php new file mode 100644 index 0000000..8e24015 --- /dev/null +++ b/Cmfcmf/OpenWeatherMap/AirPollution.php @@ -0,0 +1,73 @@ +dateTime = new \DateTime($json->time, new \DateTimeZone('UTC')); + $this->location = new Location($json->location->latitude, $json->location->longitude); + $airPollutionData = []; + foreach ($json->data as $measurement) { + $airPollutionData[] = new AirPollutionData($measurement); + } + $this->data = $airPollutionData; + } + + /** + * @return AirPollutionData|null + */ + public function getLastAirPollutionData() + { + if (count($this->data) === 0) { + return null; + } + + return reset($this->data); + } +} diff --git a/Cmfcmf/OpenWeatherMap/AirPollutionData.php b/Cmfcmf/OpenWeatherMap/AirPollutionData.php new file mode 100644 index 0000000..9772cd6 --- /dev/null +++ b/Cmfcmf/OpenWeatherMap/AirPollutionData.php @@ -0,0 +1,79 @@ +precision = (float)$json->precision; + $this->pressure = (float)$json->pressure; + $this->value = (float)$json->value; + } + + /** + * @return float + */ + public function getPrecision(): float + { + return $this->precision; + } + + /** + * @return float + */ + public function getPressure(): float + { + return $this->pressure; + } + + /** + * @return float + */ + public function getValue(): float + { + return $this->value; + } + +} diff --git a/tests/OpenWeatherMapTest.php b/tests/OpenWeatherMapTest.php index c5d3607..f816c38 100644 --- a/tests/OpenWeatherMapTest.php +++ b/tests/OpenWeatherMapTest.php @@ -167,6 +167,17 @@ public function testGetDailyWeatherForecast() $this->assertInstanceOf('\Cmfcmf\OpenWeatherMap\WeatherForecast', $dailyForecast); } + + public function testGetAirPollution() + { + $airPollutionCurrent = $this->owm->getAirPollution(40, -74, null); + + $this->assertInstanceOf(OpenWeatherMap\AirPollution::class, $airPollutionCurrent); + + $airPollutionPast = $this->owm->getAirPollution(40, -74, new \DateTime('now - 2 months')); + + $this->assertInstanceOf(OpenWeatherMap\AirPollution::class, $airPollutionPast); + } public function testWasCached() { From 941af8d1b35e1c98368b46b77e68f72db469d2fc Mon Sep 17 00:00:00 2001 From: Christian Flach Date: Thu, 31 Oct 2019 20:11:41 +0100 Subject: [PATCH 2/2] Implement other air pollution apis --- Cmfcmf/OpenWeatherMap.php | 104 +++++++++++------- .../BaseAirPollution.php} | 35 +----- .../AirPollution/COAirPollution.php | 23 ++++ .../AirPollution/NO2AirPollution.php | 49 +++++++++ .../AirPollution/O3AirPollution.php | 37 +++++++ .../PrecisionPressureValueAirPollution.php | 43 ++++++++ .../AirPollution/SO2AirPollution.php | 23 ++++ Cmfcmf/OpenWeatherMap/AirPollutionData.php | 1 - Cmfcmf/OpenWeatherMap/NotFoundException.php | 26 +++++ Cmfcmf/OpenWeatherMap/Util/Unit.php | 23 +++- Examples/AirPollution.php | 81 ++++++++++++++ README.md | 1 + tests/FakeData.php | 4 + tests/OpenWeatherMapTest.php | 15 +-- tests/TestHttpClient.php | 2 + 15 files changed, 381 insertions(+), 86 deletions(-) rename Cmfcmf/OpenWeatherMap/{AirPollution.php => AirPollution/BaseAirPollution.php} (53%) create mode 100644 Cmfcmf/OpenWeatherMap/AirPollution/COAirPollution.php create mode 100644 Cmfcmf/OpenWeatherMap/AirPollution/NO2AirPollution.php create mode 100644 Cmfcmf/OpenWeatherMap/AirPollution/O3AirPollution.php create mode 100644 Cmfcmf/OpenWeatherMap/AirPollution/PrecisionPressureValueAirPollution.php create mode 100644 Cmfcmf/OpenWeatherMap/AirPollution/SO2AirPollution.php create mode 100644 Cmfcmf/OpenWeatherMap/NotFoundException.php create mode 100644 Examples/AirPollution.php diff --git a/Cmfcmf/OpenWeatherMap.php b/Cmfcmf/OpenWeatherMap.php index 9e73e64..7e21f4d 100644 --- a/Cmfcmf/OpenWeatherMap.php +++ b/Cmfcmf/OpenWeatherMap.php @@ -22,6 +22,7 @@ use Cmfcmf\OpenWeatherMap\CurrentWeather; use Cmfcmf\OpenWeatherMap\CurrentWeatherGroup; use Cmfcmf\OpenWeatherMap\Exception as OWMException; +use Cmfcmf\OpenWeatherMap\NotFoundException as OWMNotFoundException; use Cmfcmf\OpenWeatherMap\UVIndex; use Cmfcmf\OpenWeatherMap\WeatherForecast; use Psr\Cache\CacheItemPoolInterface; @@ -71,7 +72,7 @@ class OpenWeatherMap /** * @var string The basic api url to fetch air pollution data from. */ - private $airPollutionUrl = 'https://api.openweathermap.org/pollution/v1/co'; + private $airPollutionUrl = 'https://api.openweathermap.org/pollution/v1/'; /** * @var CacheItemPoolInterface|null $cache The cache to use. @@ -343,28 +344,42 @@ public function getHistoricUVIndex($lat, $lon, $start, $end) } /** - * Returns atmosferic pollution by Carbon Monoxie + * Returns air pollution data * - * @param float $lat The location's latitude. - * @param float $lon The location's longitude. - * @param ?DateTime|string $dateTime Time of the measurement. Set null for "current" + * @param string $type One of CO, O3, SO2, and NO2. + * @param string $lat The location's latitude. + * @param string $lon The location's longitude. + * @param string $date The date to gather data from. If you omit this parameter or supply "current", returns current data. + * + * @return AirPollution\COAirPollution|AirPollution\NO2AirPollution|AirPollution\O3AirPollution|AirPollution\SO2AirPollution|null The air pollution data or null if no data was found. + * + * We use strings as $lat and $lon, since the exact number of digits in $lat and $lon determines the search range. + * For example, there is a difference between using "1.5" and "1.5000". + * We also use a string for $date, since it may either be "current" or an (abbreviated) ISO 8601 timestamp like 2016Z. * - * @return AirPollution + * @throws OWMException|\Exception * * @api - * @throws \Exception */ - public function getAirPollution($lat, $lon, $dateTime = null) + public function getAirPollution($type, $lat, $lon, $date = "current") { - if ($dateTime === null) { - $dateTime = 'current'; + $answer = $this->getRawAirPollutionData($type, $lat, $lon, $date); + if ($answer === null) { + return null; } - - $answer = $this->getRawAirPollutionData($lat, $lon, $dateTime); - $json = $this->parseJson($answer); - - return new AirPollution($json); + switch ($type) { + case "O3": + return new AirPollution\O3AirPollution($json); + case "NO2": + return new AirPollution\NO2AirPollution($json); + case "SO2": + return new AirPollution\SO2AirPollution($json); + case "CO": + return new AirPollution\COAirPollution($json); + default: + throw new \LogicException(); + } } /** @@ -503,17 +518,37 @@ public function getRawUVIndexData($mode, $lat, $lon, $cnt = null, $start = null, } /** - * @param $lat - * @param $lon - * @param $dateTime - * @return bool|string Returns the fetched data. - * @throws OWMException + * Fetch raw air pollution data + * + * @param string $type One of CO, O3, SO2, and NO2. + * @param string $lat The location's latitude. + * @param string $lon The location's longitude. + * @param string $date The date to gather data from. If you omit this parameter or supply "current", returns current data. + * + * @return string|null The air pollution data or null if no data was found. + * + * We use strings as $lat and $lon, since the exact number of digits in $lat and $lon determines the search range. + * For example, there is a difference between using "1.5" and "1.5000". + * We also use a string for $date, since it may either be "current" or an (abbreviated) ISO 8601 timestamp like 2016Z. + * + * @api */ - public function getRawAirPollutionData($lat, $lon, $dateTime) + public function getRawAirPollutionData($type, $lat, $lon, $date = "current") { - $url = $this->buildAirPollutionUrl($lat, $lon, $dateTime, $this->airPollutionUrl); - - return $this->cacheOrFetchResult($url); + if (!in_array($type, ["CO", "O3", "SO2", "NO2"])) { + throw new \InvalidArgumentException('Invalid $type received.'); + } + if (!is_string($lat) || !is_string($lon) || !is_string($date)) { + throw new \InvalidArgumentException('$lat, $lon and $date all must be strings.'); + } + + $url = $this->airPollutionUrl . strtolower($type) . "/$lat,$lon/$date.json?appid=" . $this->apiKey; + + try { + return $this->cacheOrFetchResult($url); + } catch (OWMNotFoundException $e) { + return null; + } } /** @@ -550,10 +585,10 @@ private function cacheOrFetchResult($url) $response = $this->httpClient->sendRequest($this->httpRequestFactory->createRequest("GET", $url)); $result = $response->getBody()->getContents(); if ($response->getStatusCode() !== 200) { - if ($result === '{"message":"not found"}' && $response->getStatusCode() === 404) { - throw new OWMException('OpenWeatherMap returned that air pollution data for this location cannot be found - try less precision location.'); + if ($result === "{\"message\":\"not found\"}\n" && $response->getStatusCode() === 404) { + throw new OWMNotFoundException(); } - throw new OWMException('OpenWeatherMap returned a response with status code ' . $response->getStatusCode() . ' and the following content '. $result); + throw new OWMException('OpenWeatherMap returned a response with status code ' . $response->getStatusCode() . ' and the following content `'. $result . '`'); } if ($this->cache !== null) { @@ -654,21 +689,6 @@ private function buildQueryUrlParameter($query) } } - /** - * Build the url to fetch air pollution data from. - * - * @param $lat - * @param $lon - * @param $dateTime - * @param string $url The url to prepend. - * - * @return string The fetched url - */ - private function buildAirPollutionUrl($lat, $lon, $dateTime, $url) - { - return $url."/$lat,$lon/$dateTime.json?appid=" . $this->apiKey; - } - /** * @param string $answer The content returned by OpenWeatherMap. * diff --git a/Cmfcmf/OpenWeatherMap/AirPollution.php b/Cmfcmf/OpenWeatherMap/AirPollution/BaseAirPollution.php similarity index 53% rename from Cmfcmf/OpenWeatherMap/AirPollution.php rename to Cmfcmf/OpenWeatherMap/AirPollution/BaseAirPollution.php index 8e24015..e9f4bd0 100644 --- a/Cmfcmf/OpenWeatherMap/AirPollution.php +++ b/Cmfcmf/OpenWeatherMap/AirPollution/BaseAirPollution.php @@ -16,19 +16,16 @@ * @see https://OpenWeatherMap.org/appid */ -namespace Cmfcmf\OpenWeatherMap; +namespace Cmfcmf\OpenWeatherMap\AirPollution; use Cmfcmf\OpenWeatherMap\Util\Location; -/** - * AirPollution class used to hold the air pollution and time of measurement - */ -class AirPollution +abstract class BaseAirPollution { /** * @var \DateTime */ - public $dateTime; + public $time; /** * @var Location @@ -36,13 +33,6 @@ class AirPollution public $location; /** - * @var AirPollutionData[] - */ - public $data; - - /** - * Create a new air pollution object. - * * @param object $json * * @throws \Exception @@ -50,24 +40,7 @@ class AirPollution */ public function __construct($json) { - $this->dateTime = new \DateTime($json->time, new \DateTimeZone('UTC')); + $this->time = new \DateTime($json->time, new \DateTimeZone('UTC')); $this->location = new Location($json->location->latitude, $json->location->longitude); - $airPollutionData = []; - foreach ($json->data as $measurement) { - $airPollutionData[] = new AirPollutionData($measurement); - } - $this->data = $airPollutionData; - } - - /** - * @return AirPollutionData|null - */ - public function getLastAirPollutionData() - { - if (count($this->data) === 0) { - return null; - } - - return reset($this->data); } } diff --git a/Cmfcmf/OpenWeatherMap/AirPollution/COAirPollution.php b/Cmfcmf/OpenWeatherMap/AirPollution/COAirPollution.php new file mode 100644 index 0000000..b37e429 --- /dev/null +++ b/Cmfcmf/OpenWeatherMap/AirPollution/COAirPollution.php @@ -0,0 +1,23 @@ +value = new Unit($json->data->no2->value, "g/m³", "", $json->data->no2->precision); + $this->valueStratosphere = new Unit($json->data->no2_strat->value, "g/m³", "", $json->data->no2_strat->precision); + $this->valueTroposphere = new Unit($json->data->no2_trop->value, "g/m³", "", $json->data->no2_trop->precision); + } +} diff --git a/Cmfcmf/OpenWeatherMap/AirPollution/O3AirPollution.php b/Cmfcmf/OpenWeatherMap/AirPollution/O3AirPollution.php new file mode 100644 index 0000000..c745903 --- /dev/null +++ b/Cmfcmf/OpenWeatherMap/AirPollution/O3AirPollution.php @@ -0,0 +1,37 @@ +value = new Unit($json->data, "DU"); + } +} diff --git a/Cmfcmf/OpenWeatherMap/AirPollution/PrecisionPressureValueAirPollution.php b/Cmfcmf/OpenWeatherMap/AirPollution/PrecisionPressureValueAirPollution.php new file mode 100644 index 0000000..ac4d246 --- /dev/null +++ b/Cmfcmf/OpenWeatherMap/AirPollution/PrecisionPressureValueAirPollution.php @@ -0,0 +1,43 @@ +values = []; + foreach ($json->data as $data) { + $this->values[] = [ + "value" => new Unit($data->value, "g/m³", "", $data->precision), + "pressure" => new Unit($data->pressure, "hPa"), + ]; + } + } +} diff --git a/Cmfcmf/OpenWeatherMap/AirPollution/SO2AirPollution.php b/Cmfcmf/OpenWeatherMap/AirPollution/SO2AirPollution.php new file mode 100644 index 0000000..aa7de23 --- /dev/null +++ b/Cmfcmf/OpenWeatherMap/AirPollution/SO2AirPollution.php @@ -0,0 +1,23 @@ +value; } - } diff --git a/Cmfcmf/OpenWeatherMap/NotFoundException.php b/Cmfcmf/OpenWeatherMap/NotFoundException.php new file mode 100644 index 0000000..b7fdbe6 --- /dev/null +++ b/Cmfcmf/OpenWeatherMap/NotFoundException.php @@ -0,0 +1,26 @@ +value = (float)$value; $this->unit = (string)$unit; $this->description = (string)$description; + $this->precision = $precision === null ? null : (float)$precision; } /** @@ -112,6 +119,16 @@ public function getDescription() return $this->description; } + /** + * Get the value's precision. + * + * @return float|null The value's precision. Can be null if unknown. + */ + public function getPrecision() + { + return $this->precision; + } + /** * Get the value as formatted string with unit. * diff --git a/Examples/AirPollution.php b/Examples/AirPollution.php new file mode 100644 index 0000000..1ca9db5 --- /dev/null +++ b/Examples/AirPollution.php @@ -0,0 +1,81 @@ +'; +if (php_sapi_name() === 'cli') { + $lf = "\n"; + $cli = true; +} + +// Language of data (try your own language here!): +$lang = 'de'; + +// Units (can be 'metric' or 'imperial' [default]): +$units = 'metric'; + +// You can use every PSR-17 compatible HTTP request factory +// and every PSR-18 compatible HTTP client. +$httpRequestFactory = new RequestFactory(); +$httpClient = GuzzleAdapter::createWithConfig([]); + +$owm = new OpenWeatherMap($myApiKey, $httpClient, $httpRequestFactory); + +// Example 1: Get current uv index in Berlin. +$o3 = $owm->getAirPollution("O3", "52", "13"); +$no2 = $owm->getAirPollution("NO2", "52", "13"); +$so2 = $owm->getAirPollution("SO2", "52", "13"); +$co = $owm->getAirPollution("CO", "52", "13"); + +echo "O3: "; +if ($o3 === null) { + echo "no data" . $lf; +} else { + echo $o3->value . $lf; +} + +echo "NO2: "; +if ($no2 === null) { + echo "no data" . $lf; +} else { + echo $no2->value; +} + +echo "SO2: "; +if ($so2 === null) { + echo "no data" . $lf; +} else { + foreach ($so2->values as $data) { + echo "value: " . $data["value"] . " (precision: " . $data["value"]->getPrecision() . ", pressure: " . $data["pressure"] . ")" . $lf; + } +} + +echo "CO: "; +if ($co === null) { + echo "no data" . $lf; +} else { + foreach ($co->values as $data) { + echo "value: " . $data["value"] . " (precision: " . $data["value"]->getPrecision() . ", pressure: " . $data["pressure"] . ")" . $lf; + } +} diff --git a/README.md b/README.md index a8c6c37..e8cbae9 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ http://home.openweathermap.org/ and put it into `Examples/ApiKey.ini`. - `CurrentWeather.php` shows how to receive the current weather. - `WeatherForecast.php` shows how to receive weather forecasts. - `UVIndex.php` shows how to receive uv index data. +- `AirPollution.php` show how to receive air pollution data. Contributing ============ diff --git a/tests/FakeData.php b/tests/FakeData.php index c7aa35c..3d090c4 100644 --- a/tests/FakeData.php +++ b/tests/FakeData.php @@ -127,4 +127,8 @@ public static function forecastXML() XML; + + const AIR_POLLUTION_CO = <<assertInstanceOf('\Cmfcmf\OpenWeatherMap\WeatherForecast', $dailyForecast); } - - public function testGetAirPollution() - { - $airPollutionCurrent = $this->owm->getAirPollution(40, -74, null); - - $this->assertInstanceOf(OpenWeatherMap\AirPollution::class, $airPollutionCurrent); - $airPollutionPast = $this->owm->getAirPollution(40, -74, new \DateTime('now - 2 months')); - - $this->assertInstanceOf(OpenWeatherMap\AirPollution::class, $airPollutionPast); + public function testGetAirPollution() + { + $airPollutionCurrent = $this->owm->getAirPollution("CO", "40", "-74", "current"); + $this->assertInstanceOf(OpenWeatherMap\AirPollution\COAirPollution::class, $airPollutionCurrent); + $airPollutionPast = $this->owm->getAirPollution("CO", "40", "-74", "2016Z"); + $this->assertInstanceOf(OpenWeatherMap\AirPollution\COAirPollution::class, $airPollutionPast); } public function testWasCached() diff --git a/tests/TestHttpClient.php b/tests/TestHttpClient.php index 7e5fea3..253598c 100644 --- a/tests/TestHttpClient.php +++ b/tests/TestHttpClient.php @@ -51,6 +51,8 @@ public function sendRequest(RequestInterface $request): ResponseInterface $content = $this->forecast($format); } elseif (strpos($url, 'group') !== false) { $content = $this->group($format); + } elseif (strpos($url, "pollution") !== false) { + $content = FakeData::AIR_POLLUTION_CO; } else { $content = $this->currentWeather($format); }