Get geographical weather data by city name or cordinates
This commit is contained in:
148
src/Service/OpenWeatherClient.php
Normal file
148
src/Service/OpenWeatherClient.php
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
|
||||
final class OpenWeatherClient
|
||||
{
|
||||
private ClientInterface $http;
|
||||
private string $apiKey;
|
||||
private string $baseUrl;
|
||||
|
||||
public function __construct(
|
||||
ClientInterface $http,
|
||||
string $apiKey,
|
||||
string $baseUrl = 'https://api.openweathermap.org/data/3.0'
|
||||
) {
|
||||
$this->http = $http;
|
||||
$this->apiKey = $apiKey;
|
||||
$this->baseUrl = rtrim($baseUrl, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch One Call (3.0) current/forecast data
|
||||
*
|
||||
* @param float $lat
|
||||
* @param float $lon
|
||||
* @param array{
|
||||
* exclude?: string,
|
||||
* units?: 'standard'|'metric'|'imperial',
|
||||
* lang?: string
|
||||
* } $options
|
||||
* @return array<string,mixed>
|
||||
* @throws \RuntimeException on HTTP or API error
|
||||
*/
|
||||
public function oneCall(float $lat, float $lon, array $options = []): array
|
||||
{
|
||||
$query = [
|
||||
'lat' => $lat,
|
||||
'lon' => $lon,
|
||||
'appid' => $this->apiKey,
|
||||
];
|
||||
|
||||
if (!empty($options['exclude'])) {
|
||||
$query['exclude'] = $options['exclude']; // comma-separated string
|
||||
}
|
||||
if (!empty($options['units'])) {
|
||||
$query['units'] = $options['units'];
|
||||
}
|
||||
if (!empty($options['lang'])) {
|
||||
$query['lang'] = $options['lang'];
|
||||
}
|
||||
|
||||
try {
|
||||
$res = $this->http->request('GET', "{$this->baseUrl}/onecall", [
|
||||
'query' => $query,
|
||||
'http_errors' => false,
|
||||
'timeout' => 8.0,
|
||||
'connect_timeout' => 5.0,
|
||||
]);
|
||||
} catch (GuzzleException $e) {
|
||||
throw new \RuntimeException('Weather service is unavailable', 0, $e);
|
||||
}
|
||||
|
||||
$status = $res->getStatusCode();
|
||||
$body = (string) $res->getBody();
|
||||
|
||||
$data = json_decode($body, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new \RuntimeException('Invalid response from weather service');
|
||||
}
|
||||
|
||||
if ($status >= 400) {
|
||||
$message = $data['message'] ?? 'OpenWeather API error';
|
||||
throw new \RuntimeException($message);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Geocode a city name to coordinates using OpenWeather Geocoding API.
|
||||
*
|
||||
* @param string $city
|
||||
* @return array{lat: float, lon: float}
|
||||
*/
|
||||
public function geocodeCity(string $city): array
|
||||
{
|
||||
$query = [
|
||||
'q' => $city,
|
||||
'limit' => 1,
|
||||
'appid' => $this->apiKey,
|
||||
];
|
||||
|
||||
try {
|
||||
$res = $this->http->request('GET', 'https://api.openweathermap.org/geo/1.0/direct', [
|
||||
'query' => $query,
|
||||
'http_errors' => false,
|
||||
'timeout' => 8.0,
|
||||
'connect_timeout' => 5.0,
|
||||
]);
|
||||
} catch (GuzzleException $e) {
|
||||
throw new \RuntimeException('Geocoding service is unavailable', 0, $e);
|
||||
}
|
||||
|
||||
$status = $res->getStatusCode();
|
||||
$body = (string) $res->getBody();
|
||||
|
||||
$data = json_decode($body, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new \RuntimeException('Invalid response from geocoding service');
|
||||
}
|
||||
|
||||
if ($status >= 400) {
|
||||
$message = is_array($data) && isset($data['message']) ? $data['message'] : 'Geocoding API error';
|
||||
throw new \RuntimeException($message);
|
||||
}
|
||||
|
||||
if (!is_array($data) || count($data) === 0 || !isset($data[0]['lat'], $data[0]['lon'])) {
|
||||
throw new \RuntimeException('City not found');
|
||||
}
|
||||
|
||||
return [
|
||||
'lat' => (float) $data[0]['lat'],
|
||||
'lon' => (float) $data[0]['lon'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method: One Call by city name.
|
||||
*
|
||||
* @param string $city
|
||||
* @param array{
|
||||
* exclude?: string,
|
||||
* units?: 'standard'|'metric'|'imperial',
|
||||
* lang?: string
|
||||
* } $options
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function oneCallByCity(string $city, array $options = []): array
|
||||
{
|
||||
$coords = $this->geocodeCity($city);
|
||||
return $this->oneCall($coords['lat'], $coords['lon'], $options);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user