ACHTUNG: Der GDS-FTP-Server wird zum 15.01.2018 abgeschaltet.
Wie schon im ersten Beitrag zu erahnen, war ich mit den Wetterdaten auf Landkreisebene nicht wirklich zufrieden. Es soll zwar irgendwann auch eine Json-Schnittstelle mit Gemeindewarnungen geben, aber diese gibt noch nicht.
Der DWD liefert per FTP-Server – ebenfalls kostenlos – Wetterdaten im CAP-Format auf Gemeindeebene an. Das Ganze nennt sich GDS (Global Basic Data Set) und ist hier zu finden:
http://www.dwd.de/DE/leistungen/gds/gds.html?nn=480258
Nach einer kostenlosen Registrierung erhält man per Mail die FTP-Zugangsdaten. Der FTP-Server ist voll von Wetterdaten und Grafiken. An der richtigen Stelle findet man eine Menge ZIP-Dateien die unterschiedlich viele XML-CAP-Dateien enthalten.
In der zuletzt erstellten ZIP-Datei sind die aktuellen Meldungen enthalten. Für jede Wetterwarnung in Deutschland steht eine XML-CAP-Datei, worin die betroffenen Regionen aufgelistet sind (auf Gemeindeebene).
Hier ein Beispiel eines „Warnhinweis vor STARKWIND“ für das Seegebiet Viking in der Nordsee (Warncell-ID 401000006):
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <alert xmlns="urn:oasis:names:tc:emergency:cap:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oasis:names:tc:emergency:cap:1.2 https://werdis.dwd.de/conf/CAP-DWD-Profil-v2.1.xsd"> <identifier>2.49.0.1.276.DWD.PVW.1484593937218.17</identifier> <sender>CAP@dwd.de</sender> <sent>2017-01-16T19:13:00+00:00</sent> <status>Actual</status> <msgType>Alert</msgType> <source>PVW</source> <scope>Public</scope> <info> <language>de-DE</language> <category>Met</category> <event>Starkwind</event> <responseType>None</responseType> <urgency>Immediate</urgency> <severity>Minor</severity> <certainty>Observed</certainty> <eventCode> <valueName>PROFILE_VERSION</valueName> <value>2.1</value> </eventCode> <eventCode> <valueName>LICENSE</valueName> <value>Geobasisdaten: Copyright Bundesamt für Kartographie und Geodäsie, Frankfurt am Main, 2013</value> </eventCode> <eventCode> <valueName>II</valueName> <value>14</value> </eventCode> <eventCode> <valueName>GROUP</valueName> <value>WIND</value> </eventCode> <eventCode> <valueName>AREA_COLOR</valueName> <value>255 255 0</value> </eventCode> <effective>2017-01-16T17:19:00+00:00</effective> <onset>2017-01-16T17:19:00+00:00</onset> <senderName>DWD / Seewetterdienst Hamburg</senderName> <headline>Warnhinweis vor STARKWIND </headline> <description>Süd um 6, südwestdrehend, abnehmend 4 bis 5, strichweise diesig, See 3 Meter. </description> <instruction/> <web>http://www.wettergefahren.de</web> <contact>Deutscher Wetterdienst</contact> <area> <areaDesc>Viking</areaDesc> <geocode> <valueName>WARNCELLID</valueName> <value>401000006</value> </geocode> <geocode> <valueName>STATE</valueName> <value>SH</value> </geocode> <altitude>0.0</altitude> <ceiling>9842.5197</ceiling> </area> </info> </alert>
Im Gegensatz zu den Json-Daten gibt es hierzu eine vollständige Dokumentation, was mich sehr gefreut hat:
http://www.dwd.de/DE/leistungen/gds/help/warnungen/cap_dwd_profile_de_pdf.pdf
Das Prinzip der Json-Variante, bei der bei jedem Aufruf der Webseite, die Json-Daten ausgewertet wurden, lässt sich hier natürlich nicht anwenden. Die Zeit, die das Script brauchen würde, die Infos aus den Dateien in der ZIP-Datei vom FTP-Server zu lesen, wäre viel zu lang und die Darstellung auf der Webseite wäre viel zu sehr verzögert.
Aus diesem Grund ist mir die Idee gekommen, die Wetterdaten selbst aufzubereiten und im gleichen Format wie der DWD es tut bereitzustellen. Das hat den Vorteil, dass ich mein bisheriges Script zur Anzeige der Warnungen kaum verändern muss.
Also erstelle ich ein Script, welches wie folgt strukturiert ist:
- Anmeldung an FTP-Server
- Prüfen ob Datei bereits heruntergeladen wurde (unnötigen Traffic vermeiden)
- Download der aktuellsten ZIP-Datei (Sortierung nach Name)
- Abmeldung von FTP-Server
- Entpacken in temporäres Verzeichnis
- XML-Dateien parsen und Informationen sammeln und aufbereiten
- Speichern der Daten als Datei (Datenbank wäre natürlich auch möglich)
Ergebnis ist folgendes Script, welches per Cronjob alle 10 Minuten ausgeführt wird:
<?php require_once("functions.php"); $ftp_server = "**server**"; $ftp_username = "**username**"; $ftp_userpass = "**password**"; $ftp_conn = ftp_connect($ftp_server) or die("Could not connect to $ftp_server"); $login = ftp_login($ftp_conn, $ftp_username, $ftp_userpass); ftp_pasv($ftp_conn, true); $filelist = array(); $filelist = ftp_nlist($ftp_conn,"/gds/gds/specials/alerts/cap/GER/community_status_geometry"); sort($filelist); $remote_file = end($filelist); $local_file = "download_cache/".pathinfo($remote_file)['basename']; if (!file_exists($local_file)) { echo "updated file '$remote_file' available<br />"; $files = glob("download_cache/*"); foreach($files as $file){ if(is_file($file)) unlink($file); } if (!DownloadFile($ftp_conn, $remote_file, $local_file)) { exit(); } $files = glob("unzip_cache/*"); foreach($files as $file){ if(is_file($file)) unlink($file); } if(!Unzip($local_file, "unzip_cache/")) { exit(); } } else { echo "local file '$local_file' still up to date<br />"; } ftp_close($ftp_conn); $alerts = array(); $files = glob("unzip_cache/*"); foreach($files as $file){ if(is_file($file)) { $xml = simplexml_load_file($file); foreach($xml->info->area as $area) { $alert['start'] = (int) strtotime($xml->info->onset) * 1000; $alert['end'] = (int) strtotime($xml->info->expires) * 1000; $alert['regionName'] = (string) $area->areaDesc; $alert['level'] = getLevel($xml->info); $alert['type'] = (string) getEventCode("GROUP", $xml->info); $alert['altitudeStart'] = getAltitudeStartFromAltitude((float) $area->altitude); $alert['event'] = (string) $xml->info->event; $alert['headline'] = (string) $xml->info->headline; $alert['description'] = (string) $xml->info->description; $alert['altitudeEnd'] = getAltitudeEndFromCeiling((float) $area->ceiling); $alert['stateShort'] = (string) getGeocode("STATE", $area); $alert['instruction'] = (string) $xml->info->instruction; $alert['state'] = getState($area); $alert['ii'] = (int) getEventCode("II", $xml->info); $alert['published'] = (int) strtotime($xml->info->effective) * 1000; $alerts['time'] = (int) strtotime(date("c")) * 1000; if ($alert['regionName'] != "polygonal event area") { if((string) $xml->info->urgency == "Immediate") { $alerts['warnings'][(string) $area->geocode[0]->value][] = $alert; } else { $alerts['vorabInformation'][(string) $area->geocode[0]->value][] = $alert; } } } } } echo "found ".count($alerts['warnings'])." warnings<br />"; echo "saving warnings to 'warnings.json'<br />"; $fp = fopen("warnings.json", "w"); fwrite($fp, json_encode($alerts, JSON_PRETTY_PRINT)); fclose($fp); echo "warnings successfully saved<br /> "; ?>
<?php function Unzip($zipFile, $unzipDir) { echo "<br />"; echo "Unzipping '$zipFile' <br />"; $zip = new ZipArchive; $result = $zip->open($zipFile); if($result !== true){ echo "Error :- Unable to open the Zip File: $result"; return false; } /* Extract Zip File */ $zip->extractTo($unzipDir); $zip->close(); echo "Unzipped to '$unzipDir' <br /><br />"; return true; } function DownloadFile($ftp_conn, $server_file, $local_file) { echo "Downloading '$server_file' from server <br />"; echo "Creating local file '$local_file' <br />"; $fp = fopen($local_file,"w"); // download server file and save it to open local file if (ftp_fget($ftp_conn, $fp, $server_file, FTP_BINARY, 0)) { echo "Successfully written to '$local_file'. <br />"; return true; } else { echo "Error downloading '$server_file'. <br />"; return false; } fclose($fp); } function getLevel($info) { if ($info->urgency == "Future") return 1; switch($info->severity) { case "M": return 1; break; case "Minor": return 2; break; case "Moderate": return 3; break; case "Severe": return 4; break; case "Extreme": return 5; break; } } function getState($area) { $stateShort = getGeocode("STATE", $area); switch($stateShort) { case "NRW": return "Nordrhein-Westfalen"; break; case "RP": return "Rheinland-Pfalz"; break; case "BY": return "Bayern"; break; case "BW": return "Baden-Württemberg"; break; case "HE": return "Hessen"; break; case "SN": return "Sachsen"; break; case "TH": return "Thüringen"; break; case "NS": return "Niedersachsen"; break; case "HH": return "Hamburg"; break; case "HB": return "Bremen"; break; case "SH": return "Schleswig-Holstein"; break; case "SL": return "Saarland"; break; case "SA": return "Sachsen-Anhalt"; break; case "BB": return "Brandenburg"; break; case "BL": return "Berlin"; break; case "MV": return "Mecklenburg-Vorpomern"; break; } } function getGeocode($geocode, $area) { foreach($area->geocode as $code) { if ($code->valueName == $geocode){ return (string) $code->value; } } } function getEventCode($eventCode, $info) { foreach($info->eventCode as $code) { if ($code->valueName == $eventCode){ return (string) $code->value; } } } function getAltitudeStart($area) { $altCode = getGeocode("ALTITUDE", $area); switch($altCode) { case "B": return 200; case "C": return 400; case "D": return 600; case "E": return 800; case "F": return 1000; case "G": return 1500; case "H": return 2000; case "L": return 0; case "M": return 0; case "N": return 0; case "A": return 0; } } function getAltitudeEnd($area) { $altCode = getGeocode("ALTITUDE", $area); switch($altCode) { case "B": return 3000; case "C": return 3000; case "D": return 3000; case "E": return 3000; case "F": return 3000; case "G": return 3000; case "H": return 3000; case "L": return 800; case "M": return 600; case "N": return 400; case "A": return 200; } } function getAltitudeStartFromAltitude($altitude) { $result = round($altitude * 0.3048); if ($result == 0) { return null; } else { return $result; } } function getAltitudeEndFromCeiling($ceiling) { $result = round($ceiling * 0.3048); if ($result == 3000) { return null; } else { return $result; } } ?>
Der Abruf der Wetterwarnungen für eine bestimmte Gemeinde geschieht über folgendes Script:
<?php $region = $_GET['regionCode']; header("Content-Type: application/json"); $json = file_get_contents("warnings.json"); $jsonObj = json_decode($json); $json = null; //Speicherauslastung verringern $warnings = array(); $warnings['time'] = (string) $jsonObj->time; $warnings['warnings'][$region] = $jsonObj->warnings->$region; $warnings['vorabInformation'][$region] = $jsonObj->vorabInformation->$region; $jsonObj = null; //Speicherauslastung verringern echo "warnWetter.loadWarnings(".json_encode($warnings).");"; ?>
Beispiel:
http://mt88.eu/weather/get_warnings.php?regionCode=401000006
Weitere Infos: Unwetterdaten Service
Hey,
bin gerade auch an der Stelle. Optimierung: Ergänze noch in Zeile 2 von get_warnings.php:
header(„Content-Type: application/json“);
Damit Tools wie z.B. Postman es gleich als json erkennen.
Du lädst immer nur die letzte ZIP-Datei herunter. Enthält diese somit immer alle aktiven und noch anstehenden Wetterwarnungen? Das habe ich nämlich bisher noch nicht herausgefunden.
Grüße
Markus
Hey,
danke für deine Optimierung, werde ich ergänzen.
Danke auch für die Erwähnung von Postman, kannte ich noch nicht.
Genau so ist es, die aktuellste ZIP-Datei enthält für jede Wetterwarnung eine XML-Datei. Eine XML-Datei kann sich aber auf mehrere Regionen beziehen.
Der DWD hat vor Kurzem seine API leicht verändert, verwende mittlerweile schon die Aktualisierung. Werde die hier noch dokumentieren. Die bisherige Variante läuft aber weiter.
Gruß
eMpTy
Hallo ! Ein wirklich sehr schönes Skript ! Habe es genau so übernommen und es funktioniert gut. Jetzt nur meine Frage vielleicht kann mir jemand dabei helfen. Wie bekomme ich diese Json Rohdaten auf meinen WordPress Blog, so das es von jedermann abgerufen werden kann ? Über Hilfe wäre ich dankbar.
Viele Grüße Angelo
Also, ich habe jetzt mal auf eine Wetterwarnung gewartet http://mt88.eu/weather/get_warnings.php?regionCode=114511000
Aber es wird nichts ausgeworfen…….
Bitte die Warncell-IDs mit der 8 am Anfang verwenden, in deinem Fall: 814511000
Sollte ich vielleicht nochmal deutlich dabei schreiben.
Dann bekommst du auch deine erwartete Warnung:
http://mt88.eu/weather/get_warnings.php?regionCode=814511000
Vielen Dank !
Hallo ich noch mal, ich will ja nicht nerven. Aber ich habe das Script bei mir auf dem Server und bin mir auch sicher das die Zugangsdaten zum dwd funktionieren, aber leider lädt der nichts runter.
Würde ja auch gerne deine Daten nutzen, aber die müssen https fähig sein und das ist eben bei dir nicht der Fall
Hast du eine Idee ?
Gruss Angelo
Hi Angelo,
wird denn vom Script irgendetwas ausgegeben?
Es werden ja normalerweise mehrere Logzeilen ausgegeben, wie weit kommt denn das Script?
Nein, es werden keine Zeiten ausgegeben. Deshalb habe ich mich ja gewundert.
Das Script zeigt immer nur die Leere Maske. Bei deinem Script werden in der Json Ansicht eben die Zeiten angezeigt.
schalte mal übergangsweise die php fehlerausgabe ein mit
"error_reporting(E_ALL);"
in der ersten Zeile nach" vom "update_warnings.php" script, wenn dein server das nicht zulässt, musst du evtl. eine .htaccess datei anlegen, die die Fehlerausgabe aktiviert (den Befehl findest du im internet)
hast du die functions.php auch in den selben Ordner gelegt? in meinem Beitrag kannst du den Code aufklappen
warnWetter.loadWarnings(
{
time: „“,
warnings: {
401000006: null
},
vorabInformation: {
401000006: null
}
}
)
Und bei deinem Script stehen da eben Zahlen…bei mir nicht
Moin,
nach dem gleichen Ansatz bin ich auch vorgegangen, um die Warnmeldungen für meine Homepage aufzubereiten.
Unterschiede: Daten werden per Cronjob vom FTP-Server geholt und in einer PostgreSQL-Datenbank gespeichert. Der XML-Parser basiert nicht auf simplexml, sondern ich habe mir einen eigenen Top-down-Parser gebaut.
Zur visuellen Darstellung als Leaflet-Karte werden die Warndaten mit den Landkreis/Gemeinde-Geodaten verknüpft und von einem einfachen JavaScript ausgewertet.
Alternativ kann auch für einen einzelnen Landkreis und dessen Gemeinden eine Unwetterwarnung angezeigt werden.
Grüßle Jürgen