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() 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( async def get_daily_aggregated_data(
days: int = Query(365, ge=1, le=730, description="Anzahl Tage zurück (max 730)") 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() conn = get_db_connection()
try: try:
with conn.cursor() as cursor: with conn.cursor() as cursor:
@@ -387,17 +387,15 @@ async def get_daily_aggregated_data(
if days >= 365: if days >= 365:
cursor.execute(""" cursor.execute("""
SELECT SELECT
0 as id,
date_trunc('day', datetime) as datetime, date_trunc('day', datetime) as datetime,
AVG(temperature) as temperature, AVG(temperature)::float as temperature,
ROUND(AVG(humidity)) as humidity, MIN(temperature)::float as min_temperature,
AVG(pressure) as pressure, MAX(temperature)::float as max_temperature,
AVG(wind_speed * 1.60934) as wind_speed, ROUND(AVG(humidity))::int as humidity,
MAX(wind_gust * 1.60934) as wind_gust, AVG(pressure)::float as pressure,
AVG(wind_dir) as wind_dir, AVG(wind_speed * 1.60934)::float as wind_speed,
AVG(rain) as rain, MAX(wind_gust * 1.60934)::float as wind_gust,
AVG(rain_rate) as rain_rate, AVG(wind_dir)::float as wind_dir
MAX(received_at) as received_at
FROM weather_data FROM weather_data
GROUP BY date_trunc('day', datetime) GROUP BY date_trunc('day', datetime)
ORDER BY datetime ASC ORDER BY datetime ASC
@@ -405,17 +403,15 @@ async def get_daily_aggregated_data(
else: else:
cursor.execute(""" cursor.execute("""
SELECT SELECT
0 as id,
date_trunc('day', datetime) as datetime, date_trunc('day', datetime) as datetime,
AVG(temperature) as temperature, AVG(temperature)::float as temperature,
ROUND(AVG(humidity)) as humidity, MIN(temperature)::float as min_temperature,
AVG(pressure) as pressure, MAX(temperature)::float as max_temperature,
AVG(wind_speed * 1.60934) as wind_speed, ROUND(AVG(humidity))::int as humidity,
MAX(wind_gust * 1.60934) as wind_gust, AVG(pressure)::float as pressure,
AVG(wind_dir) as wind_dir, AVG(wind_speed * 1.60934)::float as wind_speed,
AVG(rain) as rain, MAX(wind_gust * 1.60934)::float as wind_gust,
AVG(rain_rate) as rain_rate, AVG(wind_dir)::float as wind_dir
MAX(received_at) as received_at
FROM weather_data FROM weather_data
WHERE datetime >= NOW() - make_interval(days => %s) WHERE datetime >= NOW() - make_interval(days => %s)
GROUP BY date_trunc('day', datetime) GROUP BY date_trunc('day', datetime)
@@ -428,6 +424,37 @@ async def get_daily_aggregated_data(
conn.close() 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)
ORDER BY datetime ASC
""", (days,))
results = cursor.fetchall()
return [dict(row) for row in results]
finally:
conn.close()
@app.get("/weather/rain-daily", response_model=List[dict], tags=["Aggregated Data"]) @app.get("/weather/rain-daily", response_model=List[dict], tags=["Aggregated Data"])
async def get_daily_rain_data( async def get_daily_rain_data(
days: int = Query(30, ge=1, le=365, description="Anzahl Tage zurück") days: int = Query(30, ge=1, le=365, description="Anzahl Tage zurück")

View File

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

View File

@@ -46,8 +46,8 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
const aggregationSuffix = useMemo(() => { const aggregationSuffix = useMemo(() => {
switch (timeRange) { switch (timeRange) {
case '7d': case '7d':
case '30d':
return ' (Stundenmittel)' return ' (Stundenmittel)'
case '30d':
case '365d': case '365d':
return ' (Tagesmittel)' return ' (Tagesmittel)'
default: default:
@@ -55,6 +55,30 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
} }
}, [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) // Gemeinsame Chart-Optionen (angepasst an Zeitraum)
const getCommonOptions = () => { const getCommonOptions = () => {
// X-Achsen-Konfiguration basierend auf Zeitraum // X-Achsen-Konfiguration basierend auf Zeitraum
@@ -123,7 +147,7 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
tooltip: { tooltip: {
shared: true, shared: true,
crosshairs: 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: { plotOptions: {
series: { series: {
@@ -147,7 +171,87 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
// Temperatur Chart // Temperatur Chart
const temperatureOptions = useMemo(() => { 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) 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 min = Math.min(...temps)
const max = Math.max(...temps) const max = Math.max(...temps)
const range = max - min const range = max - min
@@ -191,7 +295,7 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
} }
}] }]
} }
}, [sortedData, aggregationSuffix]) }, [sortedData, temperatureSuffix, timeRange])
// Luftfeuchtigkeit Chart // Luftfeuchtigkeit Chart
const humidityOptions = useMemo(() => ({ const humidityOptions = useMemo(() => ({
@@ -227,6 +331,18 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
// Luftdruck Chart // Luftdruck Chart
const pressureOptions = useMemo(() => { const pressureOptions = useMemo(() => {
const pressures = sortedData.filter(item => item.pressure != null).map(item => item.pressure) 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 min = Math.min(...pressures)
const max = Math.max(...pressures) const max = Math.max(...pressures)
const range = max - min const range = max - min
@@ -370,7 +486,7 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
valueSuffix: ' km/h' valueSuffix: ' km/h'
} }
}, { }, {
name: 'Windböen', name: 'Böe' + windGustSuffix,
data: sortedData data: sortedData
.filter(item => item.wind_gust != null) .filter(item => item.wind_gust != null)
.map(item => [new Date(item.datetime).getTime(), item.wind_gust]), .map(item => [new Date(item.datetime).getTime(), item.wind_gust]),
@@ -405,7 +521,7 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
}, },
series series
} }
}, [sortedData, timeRange]) }, [sortedData, timeRange, windGustSuffix])
// Windrichtung Chart // Windrichtung Chart
const windDirOptions = useMemo(() => ({ const windDirOptions = useMemo(() => ({
@@ -558,7 +674,7 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
<div className="chart-item"> <div className="chart-item">
<div className="current-value">Aktuell: {current.temperature?.toFixed(1) || '-'}°C</div> <div className="current-value">Aktuell: {current.temperature?.toFixed(1) || '-'}°C</div>
<div className="chart-container"> <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"> <div className="chart-wrapper">
<HighchartsReact highcharts={Highcharts} options={temperatureOptions} /> <HighchartsReact highcharts={Highcharts} options={temperatureOptions} />
</div> </div>