Sonnenstunden forecast nächsten Tag

Hallo Zusammen,
ich bin gerade auf Euer Thema gestoßen. Habt Ihr es schonmal mit dem DWD MOSMIX Datensatz probiert. Ich nutze die Daten vom DWD seit längerem. Darin sind Sonnenstunden, Globale Strahlung, etc enthalten. Wenn noch Interesse besteht, würde ich mal meine Scripte „aufräumen“ und zur Verfügung stellen.

1 „Gefällt mir“

… oh ja, ich hätte Interesse an dem Script.

Gruß
zkra

Da gibt es auch schon PVforecast hier im Forum.

@KlausPS

Hallo Klaus,
wenn du eine Lösung für die Sonnenstunden hast, bitte immer her damit.
Mit den Sonnenstunden bin ich für die Solarprognose besser zurecht gekommen, als mit dem PV Forecast.

Danke u. viele Grüße,
Doc

Hab mir mal PVforecast angeschaut. Verwendet MOSMIX wie ich auch seit langem nutze.
Ich habe derzeit nur ein Script, das ich übers Wochenende putzen werde und hier reinstellen. Das PVforecast ist sogar schon ein Modul und für PV zugeschnitten. Da ich mir ein Meteogramm aus den MOSMIX Daten erstelle, werde ich bei meinem Script bleiben.

Nochmal zurück zu MOSMIX vom DWD. Hier schonmal ein paar Links:
Beschreibung MOSMIX Daten
MOSMIX Übersichtsseite

1 „Gefällt mir“

Mich würde hier hauptsächlich die gesamt Sonnenscheindauer für den nächsten Tag interessieren.
Die meisten anderen Werte bekommt man ja auch problemlos schon von den vielen verschiedenen anderen Anbietern.
Die Sonnenscheindauer war hier immer schon ein gefragter Wert.

Viele Grüße,
Doc

1 „Gefällt mir“

So, hier ist mein script, um die MOSMIX Daten beim DWD abzugreifen und als array für die weitere Bearbeitung bereitzustellen.

<?php
/**
* DWD_MOSMIX.php
* 
* @package  DWD MOSMIX
* @author   Klaus Stadler
* @version  0.9
* @link     
* 
*/
declare(strict_types=1);


$kmlfile         = getMOSMIXfile('10382');      // get MOSMIX kmz file from DWD with respective stationID
$MOSMIXforecast = ReadKMLfile($kmlfile);        // read XML kml-file and extract forecast data to array
print_ArrayKeys($MOSMIXforecast);               // print forecast array 


/**
*   Downloads DWD MOSMIX data as kmz-file and unzips it.
*   This function will create local raw data from DWD as kmz-file and unzip to kml-file. Path is: IPS_GetKernelDir()."temp/"
* 
*   @param   string       $stationID    DWD station catalogue https://www.dwd.de/DE/leistungen/met_verfahren_mosmix/mosmix_stationskatalog.cfg;jsessionid=0C075FA579398AA826F1AF517918264D.live11044?view=nasPublication&nn=16102.
*   @return  string|bool  false if failed or filename of unzipped kmz-file.
*/
function getMOSMIXfile(string $stationID = '10382'): string|bool {
    // build URL for downloading kmz-file
    $DWDmosmixURL       = 'opendata.dwd.de/weather/local_forecasts/mos/MOSMIX_L/single_stations/'.$stationID.'/kml/MOSMIX_L_LATEST_'.$stationID.'.kmz';
    $KMZlocalfilename   = IPS_GetKernelDir() . "temp/MOSMIX_L_LATEST_".$stationID.".kmz";
    $UnzipDir           = IPS_GetKernelDir() . "temp/";
    $MOSMIXfile         = false;

    // check whether the DWD file is more up-to-date than the local file 
    if (file_exists($KMZlocalfilename))                         // does a KMZ File exist in local temp directory
        $MOSMIXtimelocal = filemtime($KMZlocalfilename);        // get timestamp of local file
    else  $MOSMIXtimelocal = 0;

    if (file_exists('ftp://'.$DWDmosmixURL))                   // does a KMZ File exist on opendata.dwd.de
        $MOSMIXtimeremote = filemtime('ftp://'.$DWDmosmixURL); // get timestamp of remote file at DWD
    else 
        echo("ERROR MOSMIX: no such remote KMZ file at DWD https://" . $DWDmosmixURL);

    echo "TimeStamp of DWD KMZ File: ", date('Y-m-d H:i', $MOSMIXtimeremote), " TimeStamp of cached file: ", date('Y-m-d H:i', $MOSMIXtimelocal), PHP_EOL; 

    if ( !(file_exists($KMZlocalfilename) && (filemtime($KMZlocalfilename) > (time() - (1 * 3600) ))) ) {
        if (file_put_contents($KMZlocalfilename, @fopen('https://'. $DWDmosmixURL, 'r')) != false)
            echo "donwloaded: https://$DWDmosmixURL", PHP_EOL;
        else
            exit("ERROR MOSMIX: when downloading KMZ file");
    } else {
        echo "already available: https://$DWDmosmixURL", PHP_EOL;
    }

    // unzip file
    $zip = new ZipArchive();
    if ($zip->open($KMZlocalfilename)) {
        $MOSMIXfile = IPS_GetKernelDir() . "temp/" . $zip->statIndex(0)['name'] ;
        if ($zip->extractTo($UnzipDir)) echo "file zipped successfully", PHP_EOL; else echo "ERROR in ZIP!!!", PHP_EOL;
        $zip->close();
    } else 
        exit("ERROR MOSMIX: file not successfully zipped");
   
    return $MOSMIXfile;
}

/**
* Reads xml-object of kml file and extract forecast data as array.
*
* @param   string       $MOSMIXfile     route to unzipped kml file.
* @return  string|bool  false if failed or array of DWD MOSMIX forecast data
*/
function ReadKMLfile(string $MOSMIXfile):array|bool {
    $KMLdata = file_get_contents($MOSMIXfile);
    $xml2 = simplexml_load_string($KMLdata);
    $xmlDocument = $xml2->children('kml', true)->Document;

    //location, IssueTime, geocoordinates, duration of daylight
    $location = $xmlDocument->Placemark->description;
    $longitude      = (float) substr((string) $xmlDocument->Placemark->Point->coordinates, 0, 5);
    $latidtude      = (float) substr((string) $xmlDocument->Placemark->Point->coordinates, 6, 5);
    $altidue        = (float) substr((string) $xmlDocument->Placemark->Point->coordinates, 12, 4);
    $zenith = 90+50/60;
    $sunset  = date_sunset (time(), SUNFUNCS_RET_TIMESTAMP, $latidtude, $longitude, $zenith);
    $sunrise = date_sunrise(time(), SUNFUNCS_RET_TIMESTAMP, $latidtude, $longitude, $zenith);
    $dayduration = $sunset - $sunrise;

    $pubDate = (string) $xmlDocument->ExtendedData->children('dwd', true)->ProductDefinition->IssueTime;
    echo "ISSUE TIME of KML-File in UTC: ", $pubDate, PHP_EOL;
    $pubDate = strtotime($pubDate) ;
    $pubDateDay = date('w', $pubDate);
    $pubDate = date('Y-m-d H:i', $pubDate);
    $wochentag = array('So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.');
    $pubDateDay = $wochentag[$pubDateDay];

    $KMLdata = str_replace(                    // replace all kml xpath with dwd
        array("kml:", "dwd:"),
        array("", ""),
        $KMLdata);

    $xml = simplexml_load_string($KMLdata);
    $IssueTimeUTC = (string) $xml->Document->ExtendedData->ProductDefinition->IssueTime;   // in UTC

    // collect all elementNames from actual forecasts
    foreach ($xml->Document->Placemark->ExtendedData->Forecast as $KEY => $VALUE) {
        $elementNames[] = (string) $VALUE['elementName'];
    }

    // create empty array to be populated with forecast data
    $timeSteps = xml2array($xml->Document->ExtendedData->ProductDefinition->ForecastTimeSteps->TimeStep);
    $lines = array_fill(0, count($timeSteps), array()); 

	// Date (ISO) | Time (local) | Weekday
    foreach ($timeSteps as $key => $value) {
        $date = new DateTime($value);
        $date->setTimezone(new DateTimeZone('Europe/Berlin'));                  // make sure we are in the correct time zone
        $lines[$key] += ['TimeStamp' => $date->getTimestamp()];                 // timestamp is now in local time!!! and in unix seconds
        $lines[$key] += ['DateLong' => $date->format('Y-m-d H:i:s (e)') ];      // 'Y-m-d H:i:sP'
        $lines[$key] += ['Date'=>$date->format('Y-m-d') ];     
        $lines[$key] += ['Time'=>$date->format('H:i') ];
        $lines[$key] += ['Weekday' => $wochentag[$date->format('w')]];
    } 

    $MetDataElements = GETMetElementsDefinitions();
    $fnode = $xml->Document->Placemark->ExtendedData->Forecast;
    foreach ($elementNames as $id) {     // for all elements in forecast ["TN","TX","TTT","Td","DD","FF","FX3","Neff","PPPP","RRdc","RR6c","SunD3","VV","ww", ...] 
        $param = getParamArray($fnode, $id);
        if(is_array($param))    {
           if (count($param) === 0) 
                $param = array_fill(0, count($timeSteps), null);
        } else {
             $param = array_fill(0, count($timeSteps), null);
        }
        foreach ($param as $key => $value) {    // loop über alle Elemente, die in der Forecast für den jeweilige Datenfeld vorhanden sind.
            if (!is_numeric($value)) { $v = null; } else {$v = round(floatval($value), 2);}
            if (is_numeric($value)) $value = (float)$value;

            switch ($MetDataElements[$id]['UnitOfMeasurement']) {
                case 'K':
                    if (is_numeric($value)) 
                        if (substr($id,0,1) == 'E') $v = round($value, 1); else $v = round($value - 273.15, 1); 
                    break;
                case 'Pa': 
                    $v = round($value / 100, 0);    // umrechnen in hekto Pascal
                    break;
                case '% (0..100)': 
                    $v = round((float)$value);
                    break;
                case '-':
                    $v = round((float)$value);
                    // - unter anderem in Klartext ww: if ($id == 'ww') $strConditions_de[$v];
                     break;
                case 'kg / m2':
                    $v = round((float)$value, 1); 
                    break;
                case 'm':
                    $v = round(floatval($value) / 1000, 3);     // Distanz in km umwandeln 
                    break;
                case 's': 
                    if ($id == 'SunD3') {
                        // Zeit umrechnen in %/3h
                        $v = floatval($v) / 3600;
                        $v = round($v * 100 / 3);
                    }
                    if ($id == 'SunD1') {
                        // Zeit umrechnen in %/1h
                        $v = floatval($v) / 3600;
                        $v = round($v * 100 );
                    }
                    if ($id == 'DRR1') { // 1h
                        $v = $v / 3600;
                        $v = round($v * 100 );
                    }
                    if ($id == 'SunD') { // 1h
                        $v = $v / ($dayduration);
                        $v = round($v * 100 );
                    }
                    break;
                default:
            }
            $lines[$key] += ["$id"=>$v];
        }
    }
    return $lines;
}

/**
*   retrieves description of meteorological elements from DWD
*   @return array   key:ShortName, Values: UnitOfMeasurement, Description
*/
function GETMetElementsDefinitions():array {
    $xmlStr = file_get_contents("https://opendata.dwd.de/weather/lib/MetElementDefinition.xml"); 
    $xml = simplexml_load_string($xmlStr) or die("Error: Cannot create object");
    $list = [];
    foreach (json_decode(json_encode($xml),TRUE)['MetElement'] as $id => $value) {
        $list += [$value['ShortName'] =>  [ 'UnitOfMeasurement'=>$value['UnitOfMeasurement'], 'Description'=>$value['Description'] ]];
    }
    return $list;
}


//==================================ARRAY HELPER=====================================================================
/**
*   output of array as a table
*   @param      $main   array to be printed
*   @return     none, only output
*/
function print_ArrayKeys(array $main) {
    $del = "\t"; $excel = true;
    foreach ($main as $keyMain => $itemMain) {
        $out = $keyMain . $del; //print_r($itemMain);
        $keynames = "key" . $del;
        if (gettype($itemMain)  == 'array') {
            foreach (array_keys($itemMain) as $Key => $Val) {
                if ($Val == 'TimeStamp') $data = date('Y-m-d H:i', $itemMain[$Val]); else $data = $itemMain[$Val];
                if ($excel) 
                    $out .= str_replace('.', ',', (string) $data) . $del;
                else
                    $out .= $Val . $del . str_replace('.', ',', $data) . $del;
                $keynames .= $Val . $del;
            }
        } else {
            if (strlen((string)$itemMain) ==10) $data = date('Y-m-d H:i', $itemMain);else $data = $itemMain;
            $out .= $itemMain . $del . str_replace(".",",", $data) . $del;
        }
        if ( ($keyMain == 0)  and $excel) echo $keynames, PHP_EOL;
        echo $out, PHP_EOL;
    }     
}

/**
*   converts xml into array
*/
function xml2array ( $xmlObject, $out = array () ):array {
  foreach ( (array) $xmlObject as $index => $node )
  $out[$index] = ( is_object ( $node ) ) ? xml2array ( $node ) : $node;
  return $out;
}

/**
*   converts xml-node data to array
*/
function getParamArray($rootObj, $id) {
  foreach ($rootObj as $param) {
    if ((string) $param['elementName'] == $id) {
      $output = preg_replace('!\s+!', ';', (string) $param->value);
      $output = explode(';', $output);
      array_shift($output);
      return $output;
    }
  }
}


1 „Gefällt mir“

Zeile 57

schlägt fehl, wenn man oben (Zeile 29, 30) die Pfade verändert, da es „temp“ üblicherweise nicht gibt, habe ich auf „user/wetter“ umgestellt.

$MOSMIXfile = $UnzipDir . $zip->statIndex(0)['name'] ;

Danach läuft das Script, ich werde das auch mal beobachten.

Danke

alles klar, ich hatte bei mir ein temp Verzeichnis angelegt. :smile:

Danke Dir fürs veröffentlichen.
Ich habe es selber jetzt noch nicht ausprobiert, kommt man damit leicht an die Sonnenstunden dran?
Hast du ein Beispiel, wie du die Daten da weiter verarbeitest?

Viele Grüße,
Doc

Hallo Klaus,
Danke für das Skript! Ich musste in Zeile 26 und 72 „|bool“ löschen, damit es lief?!
Ich wäre auch an Sniplets bzgl. der Weiterverarbeitung der Daten, insbes. bzgl. allem was bei der Prognose für Solarforecast hilft, interessiert.
VG, Oliver

Moin moin,

vielen dank und bei mir läuft es auch. Das sind aber nun ne Menge an Daten, insofern wäre ich auch an Möglichkeiten der Weiterverarbeitung hinsichtlich der Sonnenstd. Interessiert.

Danke und Gruß Michael

Ja, das stimmt. DWD hat da eine Menge Daten je nach Station.
Aber die Weiterverarbeitung ist eigentlich ganz einfach.
im Script werden für SunD1 und SunD3 die Werte in Prozent umgerecnet:

            case 's': 
                if ($id == 'SunD3') {
                    # Zeit umrechnen in %/3h
                    $v = floatval($v) / 3600;
                    $v = round($v * 100 / 3);
                }
                if ($id == 'SunD1') {
                    # Zeit umrechnen in %/1h
                    $v = floatval($v) / 3600;
                    $v = round($v * 100 );
                }

Ich verwende die Daten dann in meinem Meteogramm (siehe oben). Hierbei extrahiere ich die Daten als array mit einer kleinen Function und nutze sie dann in Highcharts. Man kann das aber auch in eine geloggte Variable speichern und dann mit Graphik von Symcon darstellen.

Hier die Extraktionsfunktion:

function extract_keys($arr ,$keysToExtract) {
array_unshift($keysToExtract, null);
$finalARR = ;
foreach ($arr as $mkey => $mvalue) {
$subarr = ;
foreach ($mvalue as $vkey => $vvalue) {
if ( array_search($vkey, $keysToExtract) <> false) {
if ($vkey == ‚TimeStamp‘)
$subarr = $vvalue * 1000;
else
$subarr = $vvalue;
}
}
$finalARR[$mkey] = $subarr;
}
return $finalARR;
}

(frag mich nicht warum die Darstellung der Funktion nicht geklappt hat) und hier noch der Aufruf: extract_keys($DWDarray, [‚TimeStamp‘,‚SunD1‘])

das Ergebnis sieht dann so aus:

Array
(
[0] => Array
(
[0] => 1699268400000
[1] => 27
)

[1] => Array
    (
        [0] => 1699272000000
        [1] => 27
    )

[2] => Array
    (
        [0] => 1699275600000
        [1] => 25
    )

[3] => Array
    (
        [0] => 1699279200000
        [1] => 28
    )

[4] => Array
    (
        [0] => 1699282800000
        [1] => 25
    )

[5] => Array
    (
        [0] => 1699286400000
        [1] => 17
    )                //    und so weiter
1 „Gefällt mir“

Hab ich ein Browserproblem oder seht Ihr auch ein einigen Stellen nur Rechtecke im Code?:
grafik

Nee, ist kein Browserproblem …

@KlausPS ,
kannst du bitte noch mal versuchen, die extract funktion hier zu posten, das sie keine Sonderzeichen enthält.

Danke u. viele Grüße,
Doc

edit: oder ist das so gemeint?
… scheint noch etwas zu fehlen …

function extract_keys($arr ,$keysToExtract) {
array_unshift($keysToExtract, null);
$finalARR = ;
foreach ($arr as $mkey => $mvalue) {
$subarr = ;
foreach ($mvalue as $vkey => $vvalue) {
if ( array_search($vkey, $keysToExtract) <> false) {
if ($vkey == ‚TimeStamp‘)
$subarr = $vvalue * 1000;
else
$subarr = $vvalue;
}
}
$finalARR[$mkey] = $subarr;
}
return $finalARR;
}

Ich glaube, mit den Werten SunD1/SunD3 können wir nicht viel anfangen, das ist die vergangene Sonnenscheindauer.

Das einzige, was evtl. interessant ist, wäre dann die globale Einstrahlung Rad1h oder die relative Sonnenscheindauer 24h PSdxx mit den drei verschiedenen Bereichen.

Allerdings weiss ich noch nicht, wie ich den Wert interpretieren soll, der wird stündlich ausgegeben und hat nur morgens Werte drin?

Oder hat jemand etwas anderes gefunden?

Viele Grüße,
Doc

Das ist in der Beschreibung blöd erklärt und stimmt nicht, in der kmz/kml findest du z.B.

                <dwd:ForecastTimeSteps>
                    <dwd:TimeStep>2023-11-06T16:00:00.000Z</dwd:TimeStep>
                    <dwd:TimeStep>2023-11-06T17:00:00.000Z</dwd:TimeStep>
                    <dwd:TimeStep>2023-11-06T18:00:00.000Z</dwd:TimeStep>
                    <dwd:TimeStep>2023-11-06T19:00:00.000Z</dwd:TimeStep>
                    <dwd:TimeStep>2023-11-06T20:00:00.000Z</dwd:TimeStep>
                    <dwd:TimeStep>2023-11-06T21:00:00.000Z</dwd:TimeStep>
                    <dwd:TimeStep>2023-11-06T22:00:00.000Z</dwd:TimeStep>
                    <dwd:TimeStep>2023-11-06T23:00:00.000Z</dwd:TimeStep>
                    <dwd:TimeStep>2023-11-07T00:00:00.000Z</dwd:TimeStep>
                    <dwd:TimeStep>2023-11-07T01:00:00.000Z</dwd:TimeStep>
                    <dwd:TimeStep>2023-11-07T02:00:00.000Z</dwd:TimeStep>
                    <dwd:TimeStep>2023-11-07T03:00:00.000Z</dwd:TimeStep>
                    <dwd:TimeStep>2023-11-07T04:00:00.000Z</dwd:TimeStep>
                    <dwd:TimeStep>2023-11-07T05:00:00.000Z</dwd:TimeStep>
                    <dwd:TimeStep>2023-11-07T06:00:00.000Z</dwd:TimeStep>

und die dazugehörigen Einzelwerte

                <dwd:Forecast dwd:elementName="SunD1">
                    <dwd:value>    300.00       0.00       0.00       0.00       0.00       0.00       0.00       0.00       0.00       0.00       0.00       0.00       0.00       0.00       0.00      60.00     420.00     600.00     660.00     360.00     300.00     300.00     360.00     240.00     180.00       0.00       0.00       0.00       

@ralf
Hallo Ralf,

ja du scheinst recht zu haben.
Ich hatte mir nur die Tabelle angesehen, die vom Script erzeugt wurde, da scheinen die Spalten wohl durcheinander geraten bzw. verschoben zu sein. und man sieht nur alle 24h einen Wert.

In der MOSMIX Beschreibung vom DVD steht allerdings, das dies nur vergangene Werte sein sollen.

Die Frage ist auch, ob hier der SunD1 Wert für uns brauchbar ist?

MOSMIX-Element: SunD1 - Anmerkungen

Datum 26.07.2018

Die Sonnenscheindauer (SunD1) an einem wolkenlosen Tag verhält sich nicht physikalisch: Sonnaufgang und -untergangszeiten passen gut, allerdings ist erst mehrere Stunden nach Sonnenaufgang (3-4h) wirklich 100% einer Stunde sonnig.
Die Verwendung des MOSMIX Parameters 1h-Sonnenscheindauer ist generell mit Vorsicht zu genießen. Die beiden Stunden nach Sonnenauf- und vor Sonnenuntergang liefern unrealistische Werte, da Beobachtungen jahreszeitenweise und nicht tageweise in die MOS-Gleichungen eingehen. Wir raten dazu, nur die 24h relativen Sonnenscheindauern zu verwenden.

Hast du denn schon etwas brauchbares damit machen können?

VG,
Doc

hier kommt ein neuer Versuch:

function extract_keys($arr ,$keysToExtract) {

array_unshift($keysToExtract, null);    // das erste Element wird nicht verarbeitet, daher eines einfügen
$finalARR = [];
foreach ($arr as $mkey => $mvalue) {
    $subarr = [];
    foreach ($mvalue as $vkey => $vvalue) {
        if ( array_search($vkey, $keysToExtract) <> false) {
            if ($vkey == 'TimeStamp')
                $subarr[] = $vvalue * 1000;
            else
                $subarr[] = $vvalue;
        }
    }
    $finalARR[$mkey] = $subarr;
}
return $finalARR;

}

Meine Sonnenscheinvorhersage im Meteogramm funktioniert. Sieht aktuell so aus:

Das hängt von Euerer DWD StationID ab. Nicht alle haben alle Daten!