Temperatur mit Min/Max
This commit is contained in:
71
api/main.py
71
api/main.py
@@ -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)
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user