| Server IP : 202.61.199.114 / Your IP : 216.73.217.139 Web Server : nginx/1.22.1 System : Linux de.arni-solutions.de 6.1.0-49-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64 User : web20 ( 1018) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /var/www/clients/client2/arni-solutions.de/web/weather/src/ |
Upload File : |
<?php
declare(strict_types=1);
require_once __DIR__ . '/helpers.php';
final class AirQualityService
{
public function fetchGrouped(DateTimeImmutable $center, int $dayOffset): array
{
$pastDays = 4;
$forecastDays = max(4, $dayOffset + 4);
if ($forecastDays < 1) {
$forecastDays = 7;
}
$url = sprintf(
'https://air-quality-api.open-meteo.com/v1/air-quality?latitude=%s&longitude=%s&hourly=european_aqi,pm10,pm2_5,alder_pollen,birch_pollen,grass_pollen,mugwort_pollen,ragweed_pollen&timezone=%s&past_days=%d&forecast_days=%d',
rawurlencode(LAT),
rawurlencode(LON),
rawurlencode(APP_TIMEZONE),
$pastDays,
$forecastDays
);
$data = fetchJson($url, 1800); // Cache air quality data for 30 minutes
if (!is_array($data) || !isset($data['hourly']['time'])) {
return ['hourly_by_date' => [], 'error' => 'Luftqualitätsdaten konnten nicht geladen werden.'];
}
return ['hourly_by_date' => $this->groupHourlyByDate($data['hourly']), 'error' => null];
}
private function groupHourlyByDate(array $hourly): array
{
$result = [];
$count = count($hourly['time'] ?? []);
for ($i = 0; $i < $count; $i++) {
$timestamp = (string)$hourly['time'][$i];
$date = substr($timestamp, 0, 10);
$hour = (int)substr($timestamp, 11, 2);
$result[$date][] = [
'hour' => $hour,
'aqi' => isset($hourly['european_aqi'][$i]) ? (float)$hourly['european_aqi'][$i] : null,
'pm10' => isset($hourly['pm10'][$i]) ? (float)$hourly['pm10'][$i] : null,
'pm25' => isset($hourly['pm2_5'][$i]) ? (float)$hourly['pm2_5'][$i] : null,
'alder' => isset($hourly['alder_pollen'][$i]) ? (float)$hourly['alder_pollen'][$i] : null,
'birch' => isset($hourly['birch_pollen'][$i]) ? (float)$hourly['birch_pollen'][$i] : null,
'grass' => isset($hourly['grass_pollen'][$i]) ? (float)$hourly['grass_pollen'][$i] : null,
'mugwort' => isset($hourly['mugwort_pollen'][$i]) ? (float)$hourly['mugwort_pollen'][$i] : null,
'ragweed' => isset($hourly['ragweed_pollen'][$i]) ? (float)$hourly['ragweed_pollen'][$i] : null,
];
}
return $result;
}
public function summarizeDay(array $rows): array
{
if ($rows === []) {
return [
'has_data' => false,
'aqi_max' => null,
'aqi_avg' => null,
'aqi_label' => 'keine Daten',
'pm25_avg' => null,
'pm10_avg' => null,
'pollen_peak' => null,
'pollen_label' => 'keine Daten',
'pollen_level' => 'keine Daten',
];
}
$aqis = array_values(array_filter(array_column($rows, 'aqi'), static function($v) { return $v !== null; }));
$pm25 = array_values(array_filter(array_column($rows, 'pm25'), static function($v) { return $v !== null; }));
$pm10 = array_values(array_filter(array_column($rows, 'pm10'), static function($v) { return $v !== null; }));
$pollenMax = [
'Erle' => $this->maxNullable(array_column($rows, 'alder')),
'Birke' => $this->maxNullable(array_column($rows, 'birch')),
'Gräser' => $this->maxNullable(array_column($rows, 'grass')),
'Beifuß' => $this->maxNullable(array_column($rows, 'mugwort')),
'Ambrosia' => $this->maxNullable(array_column($rows, 'ragweed')),
];
arsort($pollenMax);
$peakName = (string)array_key_first($pollenMax);
$peakValue = $pollenMax[$peakName] ?? null;
return [
'has_data' => $aqis !== [] || $pm25 !== [] || $pm10 !== [] || $peakValue !== null,
'aqi_max' => $aqis !== [] ? max($aqis) : null,
'aqi_avg' => $aqis !== [] ? array_sum($aqis) / count($aqis) : null,
'aqi_label' => $aqis !== [] ? $this->aqiLabel((float)max($aqis)) : 'keine Daten',
'pm25_avg' => $pm25 !== [] ? array_sum($pm25) / count($pm25) : null,
'pm10_avg' => $pm10 !== [] ? array_sum($pm10) / count($pm10) : null,
'pollen_peak' => $peakValue !== null ? ['name' => $peakName, 'value' => $peakValue] : null,
'pollen_label' => $peakValue !== null ? $peakName . ' ' . number_format((float)$peakValue, 1, ',', '') : 'keine Daten',
'pollen_level' => $peakValue !== null ? $this->pollenLevel((float)$peakValue) : 'keine Daten',
];
}
private function maxNullable(array $values): ?float
{
$values = array_values(array_filter($values, static function($v) { return $v !== null; }));
return $values !== [] ? (float)max($values) : null;
}
private function aqiLabel(float $aqi): string
{
if ($aqi <= 20) {
return 'sehr gut';
} elseif ($aqi <= 40) {
return 'gut';
} elseif ($aqi <= 60) {
return 'mäßig';
} elseif ($aqi <= 80) {
return 'schlecht';
} elseif ($aqi <= 100) {
return 'sehr schlecht';
} else {
return 'extrem schlecht';
}
}
private function pollenLevel(float $value): string
{
if ($value < 1) {
return 'kaum Belastung';
} elseif ($value < 10) {
return 'leicht';
} elseif ($value < 50) {
return 'mittel';
} elseif ($value < 100) {
return 'hoch';
} else {
return 'sehr hoch';
}
}
}