diff --git a/Cmfcmf/OpenWeatherMap.php b/Cmfcmf/OpenWeatherMap.php index 1490280..7e21f4d 100644 --- a/Cmfcmf/OpenWeatherMap.php +++ b/Cmfcmf/OpenWeatherMap.php @@ -18,10 +18,12 @@ 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\NotFoundException as OWMNotFoundException; +use Cmfcmf\OpenWeatherMap\UVIndex; use Cmfcmf\OpenWeatherMap\WeatherForecast; use Psr\Cache\CacheItemPoolInterface; use Psr\Http\Client\ClientInterface; @@ -67,6 +69,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/'; + /** * @var CacheItemPoolInterface|null $cache The cache to use. */ @@ -336,6 +343,45 @@ public function getHistoricUVIndex($lat, $lon, $start, $end) }, $data); } + /** + * Returns 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 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. + * + * @throws OWMException|\Exception + * + * @api + */ + public function getAirPollution($type, $lat, $lon, $date = "current") + { + $answer = $this->getRawAirPollutionData($type, $lat, $lon, $date); + if ($answer === null) { + return null; + } + $json = $this->parseJson($answer); + 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(); + } + } + /** * Directly returns the xml/json/html string returned by OpenWeatherMap for the current weather. * @@ -471,6 +517,40 @@ public function getRawUVIndexData($mode, $lat, $lon, $cnt = null, $start = null, return $this->cacheOrFetchResult($url); } + /** + * 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($type, $lat, $lon, $date = "current") + { + 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; + } + } + /** * Returns whether or not the last result was fetched from the cache. * @@ -505,7 +585,10 @@ private function cacheOrFetchResult($url) $response = $this->httpClient->sendRequest($this->httpRequestFactory->createRequest("GET", $url)); $result = $response->getBody()->getContents(); if ($response->getStatusCode() !== 200) { - throw new OWMException('OpenWeatherMap returned a response with status code ' . $response->getStatusCode() . ' and the following content '. $result); + 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 . '`'); } if ($this->cache !== null) { diff --git a/Cmfcmf/OpenWeatherMap/AirPollution/BaseAirPollution.php b/Cmfcmf/OpenWeatherMap/AirPollution/BaseAirPollution.php new file mode 100644 index 0000000..e9f4bd0 --- /dev/null +++ b/Cmfcmf/OpenWeatherMap/AirPollution/BaseAirPollution.php @@ -0,0 +1,46 @@ +time = new \DateTime($json->time, new \DateTimeZone('UTC')); + $this->location = new Location($json->location->latitude, $json->location->longitude); + } +} 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 @@ +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/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 d08e302..d43df24 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 d313862..2a8bd5a 100644 --- a/tests/FakeData.php +++ b/tests/FakeData.php @@ -128,6 +128,10 @@ public static function forecastXML() XML; + const AIR_POLLUTION_CO = << diff --git a/tests/OpenWeatherMapTest.php b/tests/OpenWeatherMapTest.php index c5d3607..bf4f173 100644 --- a/tests/OpenWeatherMapTest.php +++ b/tests/OpenWeatherMapTest.php @@ -168,6 +168,14 @@ public function testGetDailyWeatherForecast() $this->assertInstanceOf('\Cmfcmf\OpenWeatherMap\WeatherForecast', $dailyForecast); } + 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() { $weather = $this->owm; 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); }