"Nicht angemeldet."]); exit; } /* * Die Spaltenlage einer Stundenzeile. Neue Größen hängen HINTEN an - dann * bleiben die Stellen bestehender Spuren gültig und der Katalog muss nur * einen Schlüssel dazubekommen. * * 0 t Unix-Sekunden, volle Stunde * 1 gemessen 1 = die eigene Station hat diese Stunde gemessen * 2 temp °C 3 regen mm 4 schnee cm * 5 wind km/h 6 boe km/h 7 richtung Grad * 8 strahlung W/m² 9 wtief % 10 wmittel % * 11 whoch % 12 code WMO 13 feuchte % * 14 druck hPa 15 regenWkt % */ try { $db = solarDb(); $tage = max(2, min(7, intval($_GET["tage"] ?? 5))); $rueck = max(0, min(72, intval($_GET["rueck"] ?? 24))); // Auf volle Stunden rasten: die Vorhersage kennt nur Stunden, und eine // Achse, die bei 13:47 anfängt, hätte keine runden Tagesgrenzen. $jetzt = time(); $von = (intdiv($jetzt, 3600) - $rueck) * 3600; $bis = (intdiv($jetzt, 3600) + $tage * 24) * 3600; // --- Vorhersage und nachgerechnete Stunden --------------------------- $reihe = []; $stmt = $db->prepare( "SELECT UNIX_TIMESTAMP(datetime) AS t, temp, regen, schnee, wind, boe, richtung, strahlung, wolken_tief, wolken_mittel, wolken_hoch, wettercode, feuchte, druck, regen_wkt FROM weatherHours WHERE datetime BETWEEN FROM_UNIXTIME(?) AND FROM_UNIXTIME(?) ORDER BY datetime"); $stmt->bind_param("ii", $von, $bis); $stmt->execute(); $res = $stmt->get_result(); while ($z = $res->fetch_assoc()) { $t = (int)$z["t"]; $reihe[$t] = [ $t, 0, zahl($z["temp"], 1), zahl($z["regen"], 1), zahl($z["schnee"], 1), zahl($z["wind"], 1), zahl($z["boe"], 1), ganz($z["richtung"]), ganz($z["strahlung"]), ganz($z["wolken_tief"]), ganz($z["wolken_mittel"]), ganz($z["wolken_hoch"]), ganz($z["wettercode"]), ganz($z["feuchte"]), ganz($z["druck"]), ganz($z["regen_wkt"]), ]; } $stmt->close(); // --- Gemessene Stunden der eigenen Station --------------------------- // // Derselbe Ausdruck dient als Spalte und als GROUP BY - ein Schalter // statt zweier Abfragen, wie in ajax/speicher.php. // // MAX(gustSpeed), aber AVG(windSpeed): Open-Meteos Böe ist die Spitze der // Stunde. Mit einem Mittelwert auf der Messseite spränge die Böenlinie // genau am Jetzt-Strich. // // Die Windrichtung ist ein Winkel und verträgt kein arithmetisches // Mittel: aus 350° und 10° würden 180°, also die Gegenrichtung. Deshalb // die Vektorsumme über Sinus und Kosinus. if ($rueck > 0) { $stmt = $db->prepare( "SELECT FLOOR(UNIX_TIMESTAMP(datetime) / 3600) * 3600 AS t, AVG(tempAmb) AS temp, AVG(windSpeed) AS wind, MAX(gustSpeed) AS boe, AVG(hum) AS feuchte, AVG(qff) AS druck, DEGREES(ATAN2(AVG(SIN(RADIANS(windDir))), AVG(COS(RADIANS(windDir))))) AS richtung FROM weatherStation WHERE datetime >= FROM_UNIXTIME(?) AND datetime < FROM_UNIXTIME(?) GROUP BY t ORDER BY t"); $stmt->bind_param("ii", $von, $jetzt); $stmt->execute(); $res = $stmt->get_result(); while ($z = $res->fetch_assoc()) { $t = (int)$z["t"]; if (!isset($reihe[$t])) { continue; // gemessen, aber ohne Vorhersagezeile - nichts zu zeigen } $reihe[$t][1] = 1; $reihe[$t][2] = zahl($z["temp"], 1); $reihe[$t][5] = zahl($z["wind"], 1); $reihe[$t][6] = zahl($z["boe"], 1); $reihe[$t][7] = ((int)round($z["richtung"]) + 360) % 360; $reihe[$t][13] = ganz($z["feuchte"]); $reihe[$t][14] = ganz($z["druck"]); } $stmt->close(); } ksort($reihe); // --- Tage: Sonnenzeiten und Tagesspanne ------------------------------ // // Sonnenauf- und -untergang kommen aus weatherDays und nicht aus // daylight: daylight reicht nur bis morgen, das Meteogramm bis zu sieben // Tage. Tage ohne Sonnenzeit (nachgetragenes Archiv) fallen hier heraus - // sie liegen ohnehin weit vor dem angezeigten Fenster. $tageListe = []; $stmt = $db->prepare( "SELECT UNIX_TIMESTAMP(date) AS t, temp_min, temp_max, wettercode, UNIX_TIMESTAMP(CONCAT(date, ' ', sonnenauf)) AS auf, UNIX_TIMESTAMP(CONCAT(date, ' ', sonnenunter)) AS unter FROM weatherDays WHERE date BETWEEN DATE(FROM_UNIXTIME(?)) AND DATE(FROM_UNIXTIME(?)) AND sonnenauf IS NOT NULL ORDER BY date"); $stmt->bind_param("ii", $von, $bis); $stmt->execute(); $res = $stmt->get_result(); while ($z = $res->fetch_assoc()) { $tageListe[] = [ (int)$z["t"], zahl($z["temp_min"], 1), zahl($z["temp_max"], 1), ganz($z["wettercode"]), (int)$z["auf"], (int)$z["unter"], ]; } $stmt->close(); // Wie frisch die Vorhersage ist - das Modul schreibt daraus "Stand 09:40" // und färbt es warnend, wenn der Sammler stehengeblieben ist. $stand = 0; if ($res = $db->query("SELECT UNIX_TIMESTAMP(MAX(abgerufen)) AS s FROM weatherHours")) { $stand = (int)($res->fetch_assoc()["s"] ?? 0); } echo json_encode([ "von" => $von, "bis" => $bis, "jetzt" => $jetzt, "stand" => $stand, "stunden" => array_values($reihe), "tage" => $tageListe, ]); } catch (Throwable $e) { http_response_code(500); echo json_encode(["error" => "Meteogramm nicht abrufbar: " . $e->getMessage()]); } /** Zahl mit fester Nachkommastelle, NULL bleibt NULL. */ function zahl($wert, $stellen) { return $wert === null ? null : round((float)$wert, $stellen); } /** Ganze Zahl, NULL bleibt NULL - eine fehlende Bewölkung ist nicht 0 %. */ function ganz($wert) { return $wert === null ? null : (int)round((float)$wert); }