Wetterdienst: Erweitert
This commit is contained in:
@@ -39,6 +39,8 @@ public sealed class DwdWeatherServiceTests
|
||||
<dwd:Forecast dwd:elementName="FF"><dwd:value>5</dwd:value></dwd:Forecast>
|
||||
<dwd:Forecast dwd:elementName="FX1"><dwd:value>-</dwd:value></dwd:Forecast>
|
||||
<dwd:Forecast dwd:elementName="RR1c"><dwd:value>1.25</dwd:value></dwd:Forecast>
|
||||
<dwd:Forecast dwd:elementName="R101"><dwd:value>67.6</dwd:value></dwd:Forecast>
|
||||
<dwd:Forecast dwd:elementName="SunD1"><dwd:value>1800</dwd:value></dwd:Forecast>
|
||||
<dwd:Forecast dwd:elementName="Neff"><dwd:value>75</dwd:value></dwd:Forecast>
|
||||
<dwd:Forecast dwd:elementName="ww"><dwd:value>61</dwd:value></dwd:Forecast>
|
||||
</kml:ExtendedData></kml:Placemark></kml:Document>
|
||||
@@ -52,6 +54,8 @@ public sealed class DwdWeatherServiceTests
|
||||
Assert.Equal(18, hour.WindSpeedKmh);
|
||||
Assert.Null(hour.WindGustKmh);
|
||||
Assert.Equal(1.3, hour.PrecipitationMm);
|
||||
Assert.Equal(68, hour.PrecipitationProbabilityPercent);
|
||||
Assert.Equal(30, hour.SunshineDurationMinutes);
|
||||
Assert.Equal(61, hour.WeatherCode);
|
||||
}
|
||||
|
||||
|
||||
@@ -150,6 +150,10 @@ public sealed class DwdWeatherService(HttpClient http)
|
||||
WindSpeedKmh = Round(Value("FF", i) * 3.6, 1),
|
||||
WindGustKmh = Round(Value("FX1", i) * 3.6, 1),
|
||||
PrecipitationMm = Round(Value("RR1c", i), 1),
|
||||
// Beide Zusatzwerte stammen aus derselben MOSMIX-L-Datei. R101 bezeichnet die
|
||||
// Wahrscheinlichkeit für >0,1 mm in der letzten Stunde, SunD1 liegt in Sekunden vor.
|
||||
PrecipitationProbabilityPercent = Round(Value("R101", i), 0),
|
||||
SunshineDurationMinutes = Round(Value("SunD1", i) / 60, 0),
|
||||
CloudCoverPercent = Round(Value("Neff", i), 0),
|
||||
WeatherCode = Value("ww", i) is { } code ? (int)Math.Round(code) : null,
|
||||
});
|
||||
|
||||
@@ -42,6 +42,10 @@ public sealed class WeatherForecastHour
|
||||
public double? WindSpeedKmh { get; set; }
|
||||
public double? WindGustKmh { get; set; }
|
||||
public double? PrecipitationMm { get; set; }
|
||||
/// <summary>Stündliche MOSMIX-Wahrscheinlichkeit für mehr als 0,1 mm Niederschlag.</summary>
|
||||
public double? PrecipitationProbabilityPercent { get; set; }
|
||||
/// <summary>MOSMIX-Sonnenscheindauer der letzten Stunde, in Minuten.</summary>
|
||||
public double? SunshineDurationMinutes { get; set; }
|
||||
public double? CloudCoverPercent { get; set; }
|
||||
public int? WeatherCode { get; set; }
|
||||
}
|
||||
|
||||
@@ -41,6 +41,27 @@ public sealed class SchoolDayWeatherSummaryTests
|
||||
Assert.Equal(SchoolDayWeatherKind.Sunny, summary!.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void From_VerwendetFuerKennzahlenNurAchtBisSechzehnUhr()
|
||||
{
|
||||
var snapshot = Snapshot(
|
||||
Hour(7, temperature: 5, rain: 3, probability: 95, sunshineMinutes: 0),
|
||||
Hour(8, temperature: 12, probability: 20, sunshineMinutes: 30),
|
||||
Hour(16, temperature: 17, probability: 65, sunshineMinutes: 45),
|
||||
Hour(17, temperature: 25, rain: 4, probability: 99, sunshineMinutes: 60));
|
||||
|
||||
var summary = SchoolDayWeatherSummary.From(snapshot, Day);
|
||||
|
||||
Assert.Equal(12, summary!.MinTemperatureC);
|
||||
Assert.Equal(17, summary.MaxTemperatureC);
|
||||
Assert.Equal(0, summary.PrecipitationMm);
|
||||
Assert.Equal(65, summary.MaxHourlyPrecipitationProbabilityPercent);
|
||||
Assert.Equal(1.3, summary.SunshineDurationHours);
|
||||
Assert.Contains("08–16 Uhr", summary.Tooltip);
|
||||
Assert.Contains("Regenwahrscheinlichkeit: bis 65 % (stündlich)", summary.Tooltip);
|
||||
Assert.Contains($"Sonnenschein: {summary.SunshineDurationHours:0.#} h", summary.Tooltip);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void From_AmtlicheGewitterwarnung_PraegtSymbolUndTooltip()
|
||||
{
|
||||
@@ -93,10 +114,12 @@ public sealed class SchoolDayWeatherSummaryTests
|
||||
};
|
||||
|
||||
private static WeatherForecastHour Hour(int hour, int? code = 0, double? temperature = 20,
|
||||
double? rain = 0, double? gust = 20, double? cloud = 20) => new()
|
||||
double? rain = 0, double? gust = 20, double? cloud = 20,
|
||||
double? probability = null, double? sunshineMinutes = null) => new()
|
||||
{
|
||||
ValidAt = Day.ToDateTime(new TimeOnly(hour, 0)), WeatherCode = code,
|
||||
TemperatureC = temperature, PrecipitationMm = rain, WindGustKmh = gust,
|
||||
CloudCoverPercent = cloud,
|
||||
CloudCoverPercent = cloud, PrecipitationProbabilityPercent = probability,
|
||||
SunshineDurationMinutes = sunshineMinutes,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ public sealed class SchoolDayWeatherSummary
|
||||
public double? MinTemperatureC { get; init; }
|
||||
public double? MaxTemperatureC { get; init; }
|
||||
public double PrecipitationMm { get; init; }
|
||||
public double? MaxHourlyPrecipitationProbabilityPercent { get; init; }
|
||||
public double? SunshineDurationHours { get; init; }
|
||||
public double? MaxWindGustKmh { get; init; }
|
||||
public IReadOnlyList<WeatherWarning> Warnings { get; init; } = [];
|
||||
|
||||
@@ -38,10 +40,15 @@ public sealed class SchoolDayWeatherSummary
|
||||
{
|
||||
get
|
||||
{
|
||||
var lines = new List<string> { Label };
|
||||
var lines = new List<string> { $"{Label} · 08–16 Uhr" };
|
||||
if (MinTemperatureC is { } min && MaxTemperatureC is { } max)
|
||||
lines.Add(Math.Abs(max - min) < 0.05 ? $"{min:0.#} °C" : $"{min:0.#}–{max:0.#} °C");
|
||||
lines.Add("Temperatur: " + (Math.Abs(max - min) < 0.05
|
||||
? $"{min:0.#} °C" : $"{min:0.#}–{max:0.#} °C"));
|
||||
if (MaxHourlyPrecipitationProbabilityPercent is { } probability)
|
||||
lines.Add($"Regenwahrscheinlichkeit: bis {probability:0} % (stündlich)");
|
||||
if (PrecipitationMm >= 0.05) lines.Add($"Niederschlag: {PrecipitationMm:0.#} mm");
|
||||
if (SunshineDurationHours is { } sunshine)
|
||||
lines.Add($"Sonnenschein: {sunshine:0.#} h");
|
||||
if (MaxWindGustKmh is { } gust) lines.Add($"Max. Böen: {gust:0.#} km/h");
|
||||
foreach (var warning in Warnings)
|
||||
{
|
||||
@@ -63,7 +70,7 @@ public sealed class SchoolDayWeatherSummary
|
||||
var schoolHours = allHours.Where(x =>
|
||||
{
|
||||
var hour = Local(x.ValidAt).Hour;
|
||||
return hour >= 6 && hour <= 18;
|
||||
return hour >= 8 && hour <= 16;
|
||||
}).ToList();
|
||||
var hours = schoolHours.Count > 0 ? schoolHours : allHours;
|
||||
if (hours.Count == 0) return null;
|
||||
@@ -71,6 +78,10 @@ public sealed class SchoolDayWeatherSummary
|
||||
var warnings = snapshot.Warnings.Where(x => WarningTouchesDate(x, date)).ToList();
|
||||
var codes = hours.Where(x => x.WeatherCode.HasValue).Select(x => x.WeatherCode!.Value).ToList();
|
||||
var precipitation = hours.Sum(x => x.PrecipitationMm ?? 0);
|
||||
var probabilities = hours.Where(x => x.PrecipitationProbabilityPercent.HasValue)
|
||||
.Select(x => x.PrecipitationProbabilityPercent!.Value).ToList();
|
||||
var sunshineMinutes = hours.Where(x => x.SunshineDurationMinutes.HasValue)
|
||||
.Select(x => x.SunshineDurationMinutes!.Value).ToList();
|
||||
var gust = hours.Where(x => x.WindGustKmh.HasValue).Select(x => x.WindGustKmh!.Value).DefaultIfEmpty().Max();
|
||||
var hasGust = hours.Any(x => x.WindGustKmh.HasValue);
|
||||
var warningText = string.Join(" ", warnings.SelectMany(x => new[] { x.Event, x.Headline }));
|
||||
@@ -95,6 +106,10 @@ public sealed class SchoolDayWeatherSummary
|
||||
MinTemperatureC = temperatures.Count > 0 ? temperatures.Min() : null,
|
||||
MaxTemperatureC = temperatures.Count > 0 ? temperatures.Max() : null,
|
||||
PrecipitationMm = Math.Round(precipitation, 1, MidpointRounding.AwayFromZero),
|
||||
MaxHourlyPrecipitationProbabilityPercent = probabilities.Count > 0
|
||||
? Math.Round(probabilities.Max(), 0, MidpointRounding.AwayFromZero) : null,
|
||||
SunshineDurationHours = sunshineMinutes.Count > 0
|
||||
? Math.Round(sunshineMinutes.Sum() / 60, 1, MidpointRounding.AwayFromZero) : null,
|
||||
MaxWindGustKmh = hasGust ? Math.Round(gust, 1, MidpointRounding.AwayFromZero) : null,
|
||||
Warnings = warnings,
|
||||
};
|
||||
|
||||
@@ -753,9 +753,12 @@ weiterhin keine automatisierte Testsuite (kein PHP-Testframework im Projekt).
|
||||
|
||||
**Nachtrag Wetter im Wochenstundenplan:** Die MOSMIX-Ausgabe des Servers umfasst nun die
|
||||
vollständige Prognosespanne bis +240 Stunden statt nur 72 Stunden. Der schreibgeschützte
|
||||
Wochenüberblick verdichtet die Stundenwerte von 06–18 Uhr je Tag auf ein kleines Symbol im
|
||||
Wochenüberblick verdichtet die Stundenwerte von 08–16 Uhr je Tag auf ein kleines Symbol im
|
||||
Tageskopf (Priorität Gewitter → Sturm ab 62 km/h Böen → Regen → bewölkt → sonnig). Der
|
||||
Tooltip nennt Temperaturspanne, Niederschlag, maximale Böen und betroffene Warnungen.
|
||||
Tooltip nennt Temperaturspanne, die höchste stündliche Regenwahrscheinlichkeit (`R101`),
|
||||
Niederschlagsmenge, aufsummierte Sonnenscheindauer (`SunD1`), maximale Böen und betroffene
|
||||
Warnungen. Alle Werte stammen aus derselben bereits geladenen MOSMIX-L-Datei; ein UV-Index
|
||||
wird bewusst nicht geschätzt, weil MOSMIX ihn nicht direkt enthält.
|
||||
Amtliche DWD-Warnungen und Vorabinformationen erhalten zusätzlich ein Warnsymbol am Tag und
|
||||
eine gut sichtbare Zusammenfassung oberhalb des Rasters; mehrtägige Warnungen werden jedem
|
||||
überlappten Schultag zugeordnet. Vergangene Tage und Wochen außerhalb der verfügbaren
|
||||
|
||||
Reference in New Issue
Block a user