Temperatur mit Min/Max

This commit is contained in:
rxf
2026-03-30 11:43:06 +02:00
parent 267f8198b9
commit d4a5f1b1c9
3 changed files with 172 additions and 29 deletions

View File

@@ -375,11 +375,11 @@ async def get_hourly_aggregated_data(
conn.close()
@app.get("/weather/daily-aggregated", response_model=List[WeatherData], tags=["Aggregated Data"])
@app.get("/weather/daily-aggregated", response_model=List[dict], tags=["Aggregated Data"])
async def get_daily_aggregated_data(
days: int = Query(365, ge=1, le=730, description="Anzahl Tage zurück (max 730)")
):
"""Gibt täglich aggregierte Wetterdaten zurück (Tagesmittel)"""
"""Gibt täglich aggregierte Wetterdaten zurück (Tagesmittel mit Min/Max-Temperaturen)"""
conn = get_db_connection()
try:
with conn.cursor() as cursor:
@@ -387,17 +387,15 @@ async def get_daily_aggregated_data(
if days >= 365:
cursor.execute("""
SELECT
0 as id,
date_trunc('day', datetime) as datetime,
AVG(temperature) as temperature,
ROUND(AVG(humidity)) as humidity,
AVG(pressure) as pressure,
AVG(wind_speed * 1.60934) as wind_speed,
MAX(wind_gust * 1.60934) as wind_gust,
AVG(wind_dir) as wind_dir,
AVG(rain) as rain,
AVG(rain_rate) as rain_rate,
MAX(received_at) as received_at
AVG(temperature)::float as temperature,
MIN(temperature)::float as min_temperature,
MAX(temperature)::float as max_temperature,
ROUND(AVG(humidity))::int as humidity,
AVG(pressure)::float as pressure,
AVG(wind_speed * 1.60934)::float as wind_speed,
MAX(wind_gust * 1.60934)::float as wind_gust,
AVG(wind_dir)::float as wind_dir
FROM weather_data
GROUP BY date_trunc('day', datetime)
ORDER BY datetime ASC
@@ -405,17 +403,46 @@ async def get_daily_aggregated_data(
else:
cursor.execute("""
SELECT
0 as id,
date_trunc('day', datetime) as datetime,
AVG(temperature) as temperature,
ROUND(AVG(humidity)) as humidity,
AVG(pressure) as pressure,
AVG(wind_speed * 1.60934) as wind_speed,
MAX(wind_gust * 1.60934) as wind_gust,
AVG(wind_dir) as wind_dir,
AVG(rain) as rain,
AVG(rain_rate) as rain_rate,
MAX(received_at) as received_at
AVG(temperature)::float as temperature,
MIN(temperature)::float as min_temperature,
MAX(temperature)::float as max_temperature,
ROUND(AVG(humidity))::int as humidity,
AVG(pressure)::float as pressure,
AVG(wind_speed * 1.60934)::float as wind_speed,
MAX(wind_gust * 1.60934)::float as wind_gust,
AVG(wind_dir)::float as wind_dir
FROM weather_data
WHERE datetime >= NOW() - make_interval(days => %s)
GROUP BY date_trunc('day', datetime)
ORDER BY datetime ASC
""", (days,))
results = cursor.fetchall()
return [dict(row) for row in results]
finally:
conn.close()
@app.get("/weather/daily-with-minmax", response_model=List[dict], tags=["Aggregated Data"])
async def get_daily_with_minmax_data(
days: int = Query(30, ge=1, le=90, description="Anzahl Tage zurück (max 90)")
):
"""Gibt täglich aggregierte Wetterdaten mit Min/Max-Temperaturen zurück"""
conn = get_db_connection()
try:
with conn.cursor() as cursor:
cursor.execute("""
SELECT
date_trunc('day', datetime) as datetime,
AVG(temperature)::float as temperature,
MIN(temperature)::float as min_temperature,
MAX(temperature)::float as max_temperature,
ROUND(AVG(humidity))::int as humidity,
AVG(pressure)::float as pressure,
AVG(wind_speed * 1.60934)::float as wind_speed,
MAX(wind_gust * 1.60934)::float as wind_gust,
AVG(wind_dir)::float as wind_dir
FROM weather_data
WHERE datetime >= NOW() - make_interval(days => %s)
GROUP BY date_trunc('day', datetime)

View File

@@ -40,7 +40,7 @@ function App() {
rainUrl = `${baseUrl}/weather/rain-daily?days=7`
break
case '30d':
weatherUrl = `${baseUrl}/weather/hourly-aggregated?days=30`
weatherUrl = `${baseUrl}/weather/daily-with-minmax?days=30`
rainUrl = `${baseUrl}/weather/rain-daily?days=30`
break
case '365d':

View File

@@ -46,8 +46,8 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
const aggregationSuffix = useMemo(() => {
switch (timeRange) {
case '7d':
case '30d':
return ' (Stundenmittel)'
case '30d':
case '365d':
return ' (Tagesmittel)'
default:
@@ -55,6 +55,30 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
}
}, [timeRange])
// Spezieller Suffix für Temperatur bei 30d/365d
const temperatureSuffix = useMemo(() => {
switch (timeRange) {
case '7d':
return ' (Stundenmittel)'
case '30d':
case '365d':
return ' (Tages-Min/Max)'
default:
return ''
}
}, [timeRange])
// Spezieller Suffix für Windböen bei 30d/365d
const windGustSuffix = useMemo(() => {
switch (timeRange) {
case '30d':
case '365d':
return ' (TagesMax)'
default:
return ''
}
}, [timeRange])
// Gemeinsame Chart-Optionen (angepasst an Zeitraum)
const getCommonOptions = () => {
// X-Achsen-Konfiguration basierend auf Zeitraum
@@ -123,7 +147,7 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
tooltip: {
shared: true,
crosshairs: true,
xDateFormat: timeRange === '24h' ? '%d.%m.%Y %H:%M' : (timeRange === '7d' || timeRange === '30d' ? '%d.%m.%Y - %Hh' : '%d.%m.%Y')
xDateFormat: timeRange === '24h' ? '%d.%m.%Y %H:%M' : (timeRange === '7d' ? '%d.%m.%Y - %Hh' : '%d.%m.%Y')
},
plotOptions: {
series: {
@@ -147,7 +171,87 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
// Temperatur Chart
const temperatureOptions = useMemo(() => {
// Bei 30d und 365d: Min/Max-Temperaturen anzeigen
if (timeRange === '30d' || timeRange === '365d') {
const minTemps = sortedData.filter(item => item.min_temperature != null).map(item => item.min_temperature)
const maxTemps = sortedData.filter(item => item.max_temperature != null).map(item => item.max_temperature)
// Prüfe, ob Daten vorhanden sind
if (minTemps.length === 0 || maxTemps.length === 0) {
return {
...getCommonOptions(),
yAxis: {
...getCommonOptions().yAxis,
title: { text: null }
},
series: []
}
}
let yMin = Math.min(...minTemps)
let yMax = Math.max(...maxTemps)
// Füge einen kleinen Puffer hinzu (2°C oben/unten), um den Platz optimal zu nutzen
yMin = Math.floor(yMin - 2)
yMax = Math.ceil(yMax + 2)
return {
...getCommonOptions(),
yAxis: {
title: { text: null },
gridLineColor: 'rgba(0, 0, 0, 0.05)',
min: yMin,
max: yMax,
startOnTick: false,
endOnTick: false
},
series: [
{
name: 'Maximaltemperatur',
data: sortedData.filter(item => item.max_temperature != null).map(item => [new Date(item.datetime).getTime(), item.max_temperature]),
color: 'rgb(255, 99, 132)',
type: 'line',
lineWidth: 2,
connectNulls: false,
gapSize: 2 * 24 * 3600 * 1000,
gapUnit: 'value',
tooltip: {
valueDecimals: 1,
valueSuffix: ' °C'
}
},
{
name: 'Minimaltemperatur',
data: sortedData.filter(item => item.min_temperature != null).map(item => [new Date(item.datetime).getTime(), item.min_temperature]),
color: 'rgb(54, 162, 235)',
type: 'line',
lineWidth: 2,
connectNulls: false,
gapSize: 2 * 24 * 3600 * 1000,
gapUnit: 'value',
tooltip: {
valueDecimals: 1,
valueSuffix: ' °C'
}
}
]
}
}
// Standard: Temperatur als Flächendiagramm
const temps = sortedData.filter(item => item.temperature != null).map(item => item.temperature)
if (temps.length === 0) {
return {
...getCommonOptions(),
yAxis: {
...getCommonOptions().yAxis,
title: { text: null }
},
series: []
}
}
const min = Math.min(...temps)
const max = Math.max(...temps)
const range = max - min
@@ -191,7 +295,7 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
}
}]
}
}, [sortedData, aggregationSuffix])
}, [sortedData, temperatureSuffix, timeRange])
// Luftfeuchtigkeit Chart
const humidityOptions = useMemo(() => ({
@@ -227,6 +331,18 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
// Luftdruck Chart
const pressureOptions = useMemo(() => {
const pressures = sortedData.filter(item => item.pressure != null).map(item => item.pressure)
if (pressures.length === 0) {
return {
...getCommonOptions(),
yAxis: {
...getCommonOptions().yAxis,
title: { text: null }
},
series: []
}
}
const min = Math.min(...pressures)
const max = Math.max(...pressures)
const range = max - min
@@ -370,7 +486,7 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
valueSuffix: ' km/h'
}
}, {
name: 'Windböen',
name: 'Böe' + windGustSuffix,
data: sortedData
.filter(item => item.wind_gust != null)
.map(item => [new Date(item.datetime).getTime(), item.wind_gust]),
@@ -405,7 +521,7 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
},
series
}
}, [sortedData, timeRange])
}, [sortedData, timeRange, windGustSuffix])
// Windrichtung Chart
const windDirOptions = useMemo(() => ({
@@ -558,7 +674,7 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
<div className="chart-item">
<div className="current-value">Aktuell: {current.temperature?.toFixed(1) || '-'}°C</div>
<div className="chart-container">
<h3><span>🌡 Temperatur{aggregationSuffix}</span><span className="unit">[°C]</span></h3>
<h3><span>🌡 Temperatur{temperatureSuffix}</span><span className="unit">[°C]</span></h3>
<div className="chart-wrapper">
<HighchartsReact highcharts={Highcharts} options={temperatureOptions} />
</div>