Skip to main content

🐘 6. Snippet: dwdWeather

<?php
#
# DWD Wettervorhersage MODX Snippet | MODX Weather Forecast V 22.10.045
#
# Entgeltfreie Versorgung mit DWD-Geodaten über dem Serverdienst https://opendata.dwd.de
# https://opendata.dwd.de/README.txt
#
# MOSMIX-Dateien werden in dem xml-ähnlichen kml-Format ausgeliefert, die Dateien sind als kmz-Dateien komprimiert
# DWD Stationskatalog (oder besser Vorhersagepunkte!): https://www.dwd.de/DE/leistungen/met_verfahren_mosmix/mosmix_stationskatalog.cfg?view=nasPublication&nn=16102
# z.B.: ID 10609 = Trier, ID 10513 = Koeln/Bonn, ID K428 = Bitburg, usw.
#
# Beispiele für Snippet-Aufrufe
# ohne Uhrzeit:
# [[!dwdWeather? &STATION=`K428` &TPL=`dwdWetterTPL`]]
# [[+dwdWeather]]
# (als Standard wird 12:00 Uhr genommen)
#
# alle Vorhersagen stündlich:
# [[!dwdWeather? &STATION=`K428` &fcAll=`true` &TPL=`dwdWetterTPL`]]
# [[+dwdWeather]]
#
# mit 4 Uhrzeiten pro Tag (T1 -T4) bei 12 Vorhersagen (QTY), also Vorhersage 3 Tage:
# [[!dwdWeather?
#   &STATION=`K428`
#   &TPL=`dwdWetterTPL`
#   &QTY=`12`
#   &T1=`06:00`
#   &T2=`12:00`
#   &T3=`18:00`
#   &T4=`00:00`
# ]]
# [[+dwdWeather]]
#
# eine definierte Zeit pro Tag:
# [[!dwdWeather?
#   &STATION=`K428`
#   &TPL=`dwdWetterTPL`
#   &T1=`18:00`
# ]]
# [[+dwdWeather]]
#
# oder
#
# im Dokument ein Chunk "chunkWeather" aufrufen: [[$chunkWeather? &STATION=`K428` &TPL=`dwdWetterTPL` &QTY=`QTY` &T1=`06:00` &T2=`12:00` &T3=`18:00` &T4=`00:00`]] [[+dwdWeather]]
# dann in dem Chunk das Snippet aufrufen: [[!dwdWeather? &STATION=`[[+STATION]]` &TPL=`[[+TPL]]` &QTY=`16` &T1=`[[+T1]]` &T2=`[[+T2]]` &T3=`[[+T3]]` &T4=`[[+T4]]`]]
# und ein eigenes HTML-Gerüst mit den Platzhalter erstellen
#
# Platzhalter für Chunks:
#   -> Ort und Vorhersagedatum: [[+location]] [[+pubDate]] [[+pubDateDay]]
#   -> Sonnenaufgang: [[+sunrise]], Sonnenuntergang: [[+sunset]], Tageslänge: [[+dayduration]], Luftdrucktendenz: [[+pTendenz]] [[+pDelta]]
#
# Beispiel für ein Chunk Template (z.B. dwdWetterTPL) welches per Platzhalter [[+dwdWeather]] dann platziert wird:
#  <div>
#    <div>
#    <h4>[[+fc0]] [[+fc2]] [[+fc1]]</h4>
#	   <img src='[[+fc17]]' title='[[+fc16]]' alt='' >
#	   </div>
#	   [[+fc5:gte=`0.1`:then=`<span class="label blue">[[+fc5]]</span>`:else=`<span class="label red">[[+fc5]]</span>`]]
#	   <br />
#	   <small>Sonnenschein: [[+fc14]]</small><br />
#	    <small>Wolkendecke: [[+fc10]]</small><br />
#	     <small>Niederschlag: [[+fc13]]</small><br />
#	      <small>Wind (Richtung): [[+fc8]] ([[+fc7]])</small><br />
#	       <small>Max. Windböe: [[+fc9]]</small><br />
#	        <small>Luftdruck: [[+fc11]]</small><br />
#	         <small>Luftfeuchte: [[+fc18]]</small><br />
#	          <small>Sichtweite: [[+fc15]]</small><br />
#  </div>
#
#   Vorhersage Platzhalter z.B. für Kalender
#   -> 20 Vorhersagen (10 Tage): [[+fc_V_E]]  (V = Vorhersage Nr (0-19) | E = Elemente (0-18), z.B. [[+fc_0_5]]
#
#   19 Elemente: 0 Date | 1 Time | 2 Day | 3 minT | 4 maxT | 5 2mT | 6 dewPoint | 7 windDir | 8 windSpeed | 9 windGust | 10 cloud |
#   11 hPa | 12 rainKg24h | 13 rainKg6h | 14 sun | 15 vis | 16 sigW | 17 picName | 18 hu |
#
#
#
# Variablen -Start------------------->
   # Chunk Template(default ist ohne)
   $strTPL = $modx->getOption('TPL',$scriptProperties,'');
   # Anzahl der Vorhersagen (default ist 40, bei 4 pro Tag sind das dann 10 Tage)
   $intQTY = $modx->getOption('QTY',$scriptProperties, 40);
   $strTMP = MODX_ASSETS_PATH.'dwd_temp/';
   $strURL = 'https://opendata.dwd.de/weather/local_forecasts/mos/MOSMIX_L/single_stations/';
   $strStation = $modx->getOption('STATION',$scriptProperties,'10609'); # Trier (älteste Stadt Deutschlands)
   $strURL .= $strStation . '/kml/MOSMIX_L_LATEST_' . $strStation . '.kmz';
   # Icons Bilder Pfad
   $strURLIcon = $modx->config['base_url'].'assets/dwd_img/';     # Wetter-Icons
   # z.B. Forecast 10 Tage = 24*10
   $MAX_COUNT = 24*10;
   # Forecast
   $fcAll = $modx->getOption('fcAll',$scriptProperties,'false'); # default all-Forecast false
   $time1 = $modx->getOption('T1',$scriptProperties,'12:00'); # default 12:00
   $time2 = $modx->getOption('T2',$scriptProperties,'');
   $time3 = $modx->getOption('T3',$scriptProperties,'');
   $time4 = $modx->getOption('T4',$scriptProperties,'');
   # Luftdruck gilt als stabil, wenn hPa Delta nicht grösser als
   $intPStable = 4;
   # Wetter Icons mit oder ohne Sonne (SunD3) anzeigen, ab x %
   $intSun = 30;
   # Hitzewelle gilt ab wie viel Grad? Wetter Icon Sonne 0h.png (TX)
   $valMaxT = 35.0;        # ab x Grad Celsius (z.B. 35.0)
   # wenn Winter-Sommerzeit angepasst werden muss
   $bolTimeOffset = false;
   $timeOffset = '0';
# Variablen -Ende-------------------<

   if ($bolTimeOffset) {
      # Sommerzeit/Winterzeit
      # daylight timeOffset to UTC)
      # 1 bei Sommerzeit, ansonsten 0
         if(date('I') == 1) {
            $timeOffset = '7200';
         }
         else{
            $timeOffset = '3600';
         }
   }

   # Ordner anlegen, wenn fehlt
   if(!file_exists($strTMP)) {
      mkdir($strTMP, 0755, true);
   }


# relative Luftfeuchtigkeit berechnen
# calculate relative humidity (TTT(K), Td(K))
if (!function_exists('getHumidity')) {
function getHumidity($T, $TD) {
  if (is_numeric($T) && is_numeric($TD)) {
    $T = round($T - 273.15, 1);
    $TD = round($TD - 273.15, 1);
    $RH=round(100*(exp((17.625*$TD)/(243.04+$TD)) / exp((17.625*$T)/(243.04+$T))));
  } else {
    $RH = '---';
  }
  return $RH;
}
}

if (!function_exists('xml2array')) {
function xml2array ( $xmlObject, $out = array () ) {
  foreach ( (array) $xmlObject as $index => $node )
  $out[$index] = ( is_object ( $node ) ) ? xml2array ( $node ) : $node;
  return $out;
}
}

if (!function_exists('getParamArray')) {
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;
    }
  }
}
}

# function direction in N/E/S/W instead of Grad (Windrichtung)
if (!function_exists('getWindDirection')) {
   function getWindDirection($degree = 0) {
     $direction = array('N', 'NNO', 'NO', 'ONO', 'O', 'OSO', 'SO', 'SSO', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW');
     $step = 360 / (count($direction));
     $b = floor(($degree + ($step/2)) / $step);
   return $direction[$b % count($direction)];
   }
}


# ww-Code - Hashs mit deutschen Konditionen (Code/Description), Quellen:
# https://wetterkanal.kachelmannwetter.com/was-ist-der-ww-code-in-der-meteorologie/
# https://www.dwd.de/DE/leistungen/opendata/help/schluessel_datenformate/kml/mosmix_element_weather_xls.html
# es werden nicht alle benötigt, aber wer weiss... ;-)
$strConditions_de = array(
// Bewölkung
 '0'  => 'Effektive Wolkendecke weniger als 2/8',
 '1'  => 'Effektive Wolkendecke zwischen 2/8 und 5/8',
 '2'  => 'Effektive Wolkendecke zwischen 5/8 und 6/8',
 '3'  => 'Effektive Wolkendecke mindestens 6/8',
// Dunst, Rauch, Staub oder Sand
 '4'  => 'Sicht durch Rauch oder Asche vermindert',
 '5'  => 'trockener Dunst (relative Feuchte < 80 %)',
 '6'  => 'verbreiteter Schwebstaub, nicht vom Wind herangeführt',
 '7'  => 'Staub oder Sand bzw. Gischt, vom Wind herangeführt',
 '8'  => 'gut entwickelte Staub- oder Sandwirbel',
 '9'  => 'Staub- oder Sandsturm im Gesichtskreis, aber nicht an der Station',
// Trockenereignisse
 '10' => 'feuchter Dunst (relative Feuchte > 80 %)',
 '11' => 'Schwaden von Bodennebel',
 '12' => 'durchgehender Bodennebel',
 '13' => 'Wetterleuchten sichtbar, kein Donner gehört',
 '14' => 'Niederschlag im Gesichtskreis, nicht den Boden erreichend',
 '15' => 'Niederschlag in der Ferne (> 5 km), aber nicht an der Station',
 '16' => 'Niederschlag in der Nähe (< 5 km), aber nicht an der Station',
 '17' => 'Gewitter (Donner hörbar), aber kein Niederschlag an der Station',
 '18' => 'Markante Böen im Gesichtskreis, aber kein Niederschlag an der Station',
 '19' => 'Tromben (trichterförmige Wolkenschläuche) im Gesichtskreis',
// Ereignisse der letzten Stunde, aber nicht zur Beobachtungszeit
 '20' => 'nach Sprühregen oder Schneegriesel',
 '21' => 'nach Regen',
 '22' => 'nach Schneefall',
 '23' => 'nach Schneeregen oder Eiskörnern',
 '24' => 'nach gefrierendem Regen',
 '25' => 'nach Regenschauer',
 '26' => 'nach Schneeschauer',
 '27' => 'nach Graupel- oder Hagelschauer',
 '28' => 'nach Nebel',
 '29' => 'nach Gewitter',
// Staubsturm, Sandsturm, Schneefegen oder -treiben
 '30' => 'leichter oder mäßiger Sandsturm, an Intensität abnehmend',
 '31' => 'leichter oder mäßiger Sandsturm, unveränderte Intensität',
 '32' => 'leichter oder mäßiger Sandsturm, an Intensität zunehmend',
 '33' => 'schwerer Sandsturm, an Intensität abnehmen',
 '34' => 'schwerer Sandsturm, unveränderte Intensität',
 '35' => 'schwerer Sandsturm, an Intensität zunehmend',
 '36' => 'leichtes oder mäßiges Schneefegen, unter Augenhöhe',
 '37' => 'starkes Schneefegen, unter Augenhöhe',
 '38' => 'leichtes oder mäßiges Schneetreiben, über Augenhöhe',
 '39' => 'starkes Schneetreiben, über Augenhöhe',
// Nebel oder Eisnebel
 '40' => 'Nebel in einiger Entfernung',
 '41' => 'Nebel in Schwaden oder Bänken',
 '42' => 'Nebel, Himmel erkennbar, dünner werdend',
 '43' => 'Nebel, Himmel nicht erkennbar, dünner werdend',
 '44' => 'Nebel, Himmel erkennbar, unverändert',
 '45' => 'Nebel, Himmel nicht erkennbar, unverändert',
 '46' => 'Nebel, Himmel erkennbar, dichter werdend',
 '47' => 'Nebel, Himmel nicht erkennbar, dichter werdend',
 '48' => 'Nebel mit Reifansatz, Himmel erkennbar',
 '49' => 'Nebel mit Reifansatz, Himmel nicht erkennbar',
// Sprühregen
 '50' => 'unterbrochener leichter Sprühregen',
 '51' => 'durchgehend leichter Sprühregen',
 '52' => 'unterbrochener mäßiger Sprühregen',
 '53' => 'durchgehend mäßiger Sprühregen',
 '54' => 'unterbrochener starker Sprühregen',
 '55' => 'durchgehend starker Sprühregen',
 '56' => 'leichter gefrierender Sprühregen',
 '57' => 'mäßiger oder starker gefrierender Sprühregen',
 '58' => 'leichter Sprühregen mit Regen',
 '59' => 'mäßiger oder starker Sprühregen mit Regen',
// Regen
 '60' => 'unterbrochener leichter Regen oder einzelne Regentropfen',
 '61' => 'durchgehend leichter Regen',
 '62' => 'unterbrochener mäßiger Regen',
 '63' => 'durchgehend mäßiger Regen',
 '64' => 'unterbrochener starker Regen',
 '65' => 'durchgehend starker Regen',
 '66' => 'leichter gefrierender Regen',
 '67' => 'mäßiger oder starker gefrierender Regen',
 '68' => 'leichter Schneeregen',
 '69' => 'mäßiger oder starker Schneeregen',
// Schnee
 '70' => 'unterbrochener leichter Schneefall oder einzelne Schneeflocken',
 '71' => 'durchgehend leichter Schneefall',
 '72' => 'unterbrochener mäßiger Schneefall',
 '73' => 'durchgehend mäßiger Schneefall',
 '74' => 'unterbrochener starker Schneefall',
 '75' => 'durchgehend starker Schneefall',
 '76' => 'Eisnadeln (Polarschnee)',
 '77' => 'Schneegriesel',
 '78' => 'Schneekristalle',
 '79' => 'Eiskörner (gefrorene Regentropfen)',
// Schauer
 '80' => 'leichter Regenschauer',
 '81' => 'mäßiger oder starker Regenschauer',
 '82' => 'äußerst heftiger Regenschauer',
 '83' => 'leichter Schneeregenschauer',
 '84' => 'mäßiger oder starker Schneeregenschauer',
 '85' => 'leichter Schneeschauer',
 '86' => 'mäßiger oder starker Schneeschauer',
 '87' => 'leichter Graupelschauer',
 '88' => 'mäßiger oder starker Graupelschauer',
 '89' => 'leichter Hagelschauer',
 '90' => 'mäßiger oder starker Hagelschauer',
// Gewitter
 '91' => 'Gewitter in der letzten Stunde, zurzeit leichter Regen',
 '92' => 'Gewitter in der letzten Stunde, zurzeit mäßiger oder starker Regen',
 '93' => 'Gewitter in der letzten Stunde, zurzeit leichter Schneefall/Schneeregen/Graupel/Hagel',
 '94' => 'Gewitter in der letzten Stunde, zurzeit mäßiger oder starker Schneefall/Schneeregen/Graupel/Hagel',
 '95' => 'leichtes oder mäßiges Gewitter mit Regen oder Schnee',
 '96' => 'leichtes oder mäßiges Gewitter mit Graupel oder Hagel',
 '97' => 'starkes Gewitter mit Regen oder Schnee',
 '98' => 'starkes Gewitter mit Sandsturm',
 '99' => 'starkes Gewitter mit Graupel oder Hagel',
 '100' => 'not available'
);



# Wetter Icons
if (!function_exists('wwPic')) {
   function wwPic($Code, $bolSun, $intTagBeginn, $intTagEnde, $WT, $bolMaxT) {
     # $intHour = date('H');
     $intHour = $WT;
     $bolDay  = ($intHour > $intTagBeginn && $intHour < $intTagEnde);

      switch ($Code) {
	case 0:
		# wenn Tag und heiss: 0h | wenn Tag: 0d | wenn Nacht: 0n
            if ($bolDay == true and $bolMaxT == true) {
                $icon = '0h';
            } elseif ($bolDay == true) {
                $icon = '0d';
            } else {
                $icon = '0n';
            }
		break;
	case 1:
		# wenn sonniger Tag: 1s | wenn Tag: 1d | wenn Nacht: 1n
            if ($bolDay == true and $bolSun == true) {
                $icon = '1s';
            } elseif ($bolDay == true) {
                $icon = '1d';
            } else {
                $icon = '1n';
            }
		break;
	case 2:
		# wenn sonniger Tag: 2s | wenn Tag: 2d | wenn Nacht: 2n
            if ($bolDay == true and $bolSun == true) {
                $icon = '2s';
            } elseif ($bolDay == true) {
                $icon = '2d';
            } else {
                $icon = '2n';
            }
		break;
	case 3:
                # wenn Tag: 3d | wenn Nacht: 3n
		$icon = ($bolDay) ? '3d' : '3n';
		break;
	case 4:
	case 5:
	case 6:
	case 7:
	case 8:
	case 9:
		$icon = '4-9';
		break;
	case 10:
	case 11:
	case 12:
	case 13:
	case 14:
	case 15:
	case 16:
		$icon = '10-16';
		break;
	case 17:
		$icon = '17';
		break;
	case 18:
		$icon = '18';
		break;
	case 19:
		$icon = '19';
		break;
	case 20:
		$icon = '20';
		break;
	case 21:
		$icon = '21';
		break;
	case 22:
		$icon = '22';
		break;
	case 23:
	case 24:
		$icon = '23-24';
		break;
	case 25:
		$icon = '25';
		break;
	case 26:
		$icon = '26';
		break;
	case 27:
		$icon = '27';
		break;
	case 28:
		$icon = '28';
		break;
	case 29:
		$icon = '29';
		break;
	case 30:
	case 31:
	case 32:
		$icon = '30-32';
		break;
	case 33:
	case 34:
	case 35:
		$icon = '33-35';
		break;
	case 36:
	case 37:
	case 38:
	case 39:
		$icon = '36-39';
		break;
	case 40:
	case 41:
	case 42:
	case 43:
	case 44:
	case 45:
	case 46:
	case 47:
	case 48:
	case 49:
		$icon = '40-49';
		break;
	case 50:
	case 51:
	case 52:
	case 53:
		$icon = '50-53';
		break;
	case 54:
	case 55:
	case 56:
	case 57:
	case 58:
	case 59:
		$icon = '55-59';
		break;
	case 60:
	case 61:
	case 62:
	case 63:
	case 64:
	case 65:
		$icon = '60-65';
		break;
	case 66:
	case 67:
		$icon = '66-67';
		break;
	case 68:
	case 69:
		$icon = '68-69';
		break;
	case 70:
	case 71:
	case 72:
	case 73:
	case 74:
	case 75:
	case 76:
	case 77:
	case 78:
	case 79:
		$icon = '70-79';
		break;
	case 80:
		# wenn sonniger Tag: 80s | wenn Tag: 80d | wenn Nacht: 80n
            if ($bolDay == true and $bolSun == true) {
                $icon = '80s';
            } elseif ($bolDay == true) {
                $icon = '80d';
            } else {
                $icon = '80n';
            }
		break;
	case 81:
		$icon = '81';
		break;
	case 82:
		$icon = '82';
		break;
	case 83:
	case 84:
		$icon = '83-84';
		break;
	case 85:
	case 86:
    case 87:
    case 88:
		$icon = '85-88';
		break;
	case 89:
	case 90:
		$icon = '89-90';
		break;
	case 91:
	case 92:
	case 93:
	case 94:
	case 95:
	case 96:
	case 97:
	case 98:
	case 99:
		$icon = '91-99';
		break;
	// default
         default:
		$icon = 'unknown';
		break;
      }
   return $icon.'.png';
   }
}



   # max. stündlich eine neue Datei ([Station]_[ISO-Datum]_[h]_dwdWeather.kmz) erstellen und alle alte [Station].* löschen
   # Start------------------->
   $strDatumStunde = date('Y-m-d_G');
   $strZieldatei = $strTMP.$strStation.'_'.$strDatumStunde.'_dwdWeather.kmz';

   if(!file_exists($strZieldatei)) {
      array_map('unlink', glob($strTMP.$strStation.'*'));
      # Datei per CURL abholen -Start------------------->
      if (function_exists('curl_version')) {
         $ch = curl_init($strURL);
         $zieldatei = fopen($strZieldatei, 'w');
         # deaktiviere SSL Überprüfung
         # curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
         # curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
         curl_setopt($ch, CURLOPT_FILE, $zieldatei);
         curl_setopt($ch, CURLOPT_TIMEOUT, 3600);
         curl_exec($ch);
         $intReturnCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
         fclose($zieldatei);
         # prüfe ob die Seite erreichbar ist!
         if ($intReturnCode != 200 && $intReturnCode != 302 && $intReturnCode != 304) {return 'ERROR: Page not available!';};
         }
      # Datei per CURL abholen -Ende--------------------<
   } # max. stündlich -Ende-------------------<


   # lösche Datei wenn die Dateigrösse 0 ist
   clearstatcache();
   if(0 == filesize($strZieldatei)) {
     array_map('unlink', glob($strTMP.$strStation.'*'));
     return 'ERROR: File is empty and due that deleted!';
   }


    # downloaded source data (*.kmz)
    $fn = $strZieldatei;
    $za = new ZipArchive();
    $za->open($fn);


    # Header-Infos
    $stat = $za->statIndex(0);
    $data = file_get_contents('zip://'.$strZieldatei.'#'.$stat['name']);

    # Ort, Ausgabezeit und Lokation (für Sonnenaufgang und Sonnenuntergang Berechnung)
    $xml2 = simplexml_load_string($data);
    $xmlDocument = $xml2->children('kml', true)->Document;

    $location = (string) $xmlDocument->Placemark->description;
    $coordinates = (string) $xmlDocument->Placemark->Point->coordinates;   # Bitburg "6.53,49.98,359.0"
    $coordinates = explode(',', $coordinates);

    $now = time();
    # PHP 8.1: date_sunrise, date_sunset functions are deprecated (in PHP 9.0 will be removed) and replaced with date_sun_info
    # $zenith = 90+50/60;
    # $sunset = date_sunset($now, SUNFUNCS_RET_TIMESTAMP, $coordinates[1], $coordinates[0], $zenith);
    # $sunrise = date_sunrise($now, SUNFUNCS_RET_TIMESTAMP, $coordinates[1], $coordinates[0], $zenith);
    $suninfo = date_sun_info($now, $coordinates[1], $coordinates[0]);
    $sunrise = $suninfo['sunrise'];
    $sunset  = $suninfo['sunset'];

    $mycoordinates = $coordinates[1] .', '. $coordinates[0];
    $dayduration = $sunset - $sunrise;
      $sunrise = date('H:i',$sunrise);
      $sunset = date('H:i',$sunset);
        $dayduration = round($dayduration/60/60, 2);
        $dayduration = str_replace(',', '.', $dayduration);

    # Platzhalter Sonnenaufgang, Sonnenuntergang, Tageslänge und Koordinaten
    $modx->setPlaceholder('sunrise', $sunrise);
    $modx->setPlaceholder('sunset', $sunset);
    $modx->setPlaceholder('dayduration', $dayduration);
    $modx->setPlaceholder('coordinates', $mycoordinates);

    $pubDate = (string) $xmlDocument->ExtendedData->children('dwd', true)->ProductDefinition->IssueTime;
      $pubDate = strtotime($pubDate) + $timeOffset;
        $pubDateDay = date('w', $pubDate);
        $pubDate = date('Y-m-d H:i', $pubDate);
        $wochentag = array('So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.');
        $pubDateDay = $wochentag[$pubDateDay];

    # Platzhalter Ort, Koordinaten und Veröffentlichungsdatum
    $modx->setPlaceholder('location', $location);
    $modx->setPlaceholder('pubDate', $pubDate);
    $modx->setPlaceholder('pubDateDay', $pubDateDay);


    # short name / long name (for header)
    # RR6c not available for all hours! 6am, 12am, 6pm and 12pm - if needed for all hours: change all RR6c to RR1c
    # RR6c = better precipitation forecasts
    $alias = array(
      'TN' => 'minT',        // Minimum temperature - within the last 12 hours (Kelvin) | nur 06:00 und 18:00 Uhr!
      'TX' => 'maxT',        // Maximum temperature - within the last 12 hours (Kelvin) | nur 06:00 und 18:00 Uhr!
      'TTT' => '2mT',        // Temperature 2m above surface (Kelvin)
      'Td' => 'dewPoint',    // Dewpoint 2m above surface (Kelvin)
      'DD' => 'windDir',     // 0°..360°, Wind direction
      'FF' => 'windSpeed',   // Wind speed (m/s) | m/s * 3.6 = km/h
      'FX3' => 'windGust',   // Wind speed (m/s) | m/s * 3.6 = km/h
      'Neff' => 'cloud',     // Effective cloud cover (%)
      'PPPP' => 'hPA',       // Surface pressure, reduced | hPA (mBAR)= Pa/1000
      'RRdc' => 'rainKG24h', // Total precipitation last 24 hour consistent with significant weather | Niederschlag 1 Ltr pro kg/m2 = 1 mm
      'RR6c' => 'rainKg6h',  // Total precipitation last 6 hour consistent with significant weather | Niederschlag 1 Ltr pro kg/m2 = 1 mm
      'SunD3' => 'sun',      // Sunshine duration during the last three hours (s)
      'VV' => 'vis',         // Visibility (m) | wird in km umgerechnet
      'ww' => 'sigW'         // Significant Weather (ID)
    );
    $ids = array_keys($alias);




for($i=0; $i<$za->numFiles; $i++) {
    $stat = $za->statIndex($i);
    $data = file_get_contents('zip://'.$strZieldatei.'#'.$stat['name']);

    $data = str_replace(
      array("kml:", "dwd:"),
      array("", ""),
      $data
    );

    $xml = simplexml_load_string($data);
    $timeSteps = xml2array($xml->Document->ExtendedData->ProductDefinition->ForecastTimeSteps->TimeStep);
    $lines = array_fill(0, count($timeSteps), array());


	# Datum (ISO) | Zeit | Wochentag
    foreach ($timeSteps as $key => $value) {
        $date = new DateTime($value);
        array_push($lines[$key], $date->format('Y-m-d'));
        array_push($lines[$key], $date->format('H:i'));
		array_push($lines[$key], $wochentag[$date->format('w')]);
    } // $timeSteps


   $fnode = $xml->Document->Placemark->ExtendedData->Forecast;
    foreach ($ids as $id) {
        $param = getParamArray($fnode, $id);

        if(is_array($param)){
           if (count($param) === 0) {
             $param = array_fill(0, count($timeSteps), '---');
           }
        } else {
             # PHP7.2 prevents warning: "Parameter must be an array or an object that implements Countable"
             $param = array_fill(0, count($timeSteps), '---');
        }

        foreach ($param as $key => $value) {
            # prevents PHP warning "a non-numeric value encountered"
            if($value !== null && !is_numeric($value)) {
                $value = 0;
            }
            $v = $value;

            if (in_array($id, array('TN', 'TX', 'TTT', 'Td'))) {
                $v = round(floatval($value) - 273.15, 1);
                $v = str_replace(',', '.', $v);
            }
            if ($id == 'PPPP') {
                $v = round($value / 100, 0);
            }
            if (in_array($id, array('Neff', 'Nh', 'Nm', 'Nl', 'ww'))) {
                $v = round($value);
            }
            if (in_array($id, array('RRhc', 'RRdc', 'RR6c'))) {
                $v = round($value, 1);
                $v = str_replace(',', '.', $v);
            }
            if ($id == 'VV') {
                $v = round(floatval($value) / 1000, 2);
                $v = str_replace(',', '.', $v);
            }
            if ($id == 'ww') {
                $v = $strConditions_de[$v];
            }
            if ($id == 'DD') {
                $v = getWindDirection(round($value));
            }
            if (in_array($id, array('FF', 'FX3'))) {
                $v = round(floatval($value) * 3.6);
            }

            # Returns an array with units
            if (in_array($id, array('TN', 'TX', 'TTT', 'Td'))) {
                $v = $v.' °C';
            }
            if (in_array($id, array('FF', 'FX3'))) {
                $v = $v.' km/h';
            }
            if ($id == 'Neff') {
                $v = $v.' % (effektiv)';
            }
            if ($id == 'SunD3') {
                # Zeit umrechnen in %/3h
                $v = floatval($v) / 3600;
                $v = round($v * 100 / 3);
                $v = $v.' %';
            }
            if ($id == 'DRR1') { # 1h
                $v = $v / 60;
                $v = $v.' min/h';
            }
            if ($id == 'RRdc') { # 24h
                $v = $v.' Ltr/24h';
            }
            if ($id == 'RRhc') { # 12h
                $v = $v.' Ltr/12h';
            }
            if ($id == 'RR6c') { # 6h
                $v = $v.' Ltr/6h';
            }
            if ($id == 'PPPP') {
                $v = $v.' hPA';
            }
            if ($id == 'VV') {
                $v = $v.' km';
            }
            if ($id == '-') {
                $v = '---';
            }
            array_push($lines[$key], $v);
        }
    }// foreach $ids



    // get Picture No (Significant Weather - ww)
    // get Sun (SunD3)
    // get Temp (TTT)
    // get Weather Time ($WT) | Uhrzeit
    $ww = getParamArray($fnode, 'ww');
    $rs = getParamArray($fnode, 'SunD3');
    $ttt = getParamArray($fnode, 'TTT');
    foreach ($ww as $key => $value) {
       # $intW = round(floatval($ww[$key]));
       $valTTT = round(floatval($ttt[$key]) - 273.15, 1);
       $intS = round(floatval($rs[$key]));

          # Zeit umrechnen in %/3h
          $intS = floatval($intS) / 3600;
          $intS = round($intS * 100 / 3);

          if($intS >= $intSun) {
             $bolSun = true;
          }
          else{
             $bolSun = false;
          }
          if($valTTT >= $valMaxT) {
             $bolMaxT = true;
          }
          else{
             $bolMaxT = false;
          }

      # für Wetter Icons (Tag oder Nacht)
      $tr = strtotime($sunrise);
      $tr = intval(date('G',$tr));
      $intTagBeginn = $tr - 1;

      $ts = strtotime($sunset);
      $ts = intval(date('G',$ts));
      $intTagEnde = $ts + 1;

      $WT = strtotime($lines[$key][1]); # Uhrzeit
      $WT = intval(date('G',$WT));

      array_push($lines[$key], $strURLIcon.wwPic(round(floatval($value)), $bolSun, $intTagBeginn, $intTagEnde, $WT, $bolMaxT));
    }

    // berechnen der Luftfeuchtigkeit
    // calculate humidity (hu %)
    $t = getParamArray($fnode, 'TTT');  # Temperature 2m above surface
    $d = getParamArray($fnode, 'Td');   # Dewpoint 2m above surface
    foreach ($t as $key => $value) {
        array_push($lines[$key], getHumidity($value, $d[$key]).' %');
    }


    $csvOutput = '';

    // output header
    # $csvOutput = str_replace(
    #  array_keys($alias),
    #  array_values($alias),
    #  'Date|Time|Tag|'.implode('|', $ids).'|pic'.'|hu'.'||'
    # );

    // slice & output content
    $lines = array_slice($lines, 0, $MAX_COUNT);
    foreach ($lines as $line) {
       if ($fcAll == 'false'){
           if ($line[1] == $time1 or $line[1] == $time2 or $line[1] == $time3 or $line[1] == $time4){
               $csvOutput = $csvOutput. implode('|', $line).'||';
           }
       } else {
           $csvOutput = $csvOutput. implode('|', $line).'||';
       }
    }

} // END numFiles


# echo $csvOutput;

# mehrdimensionales Array erstellen
$array = array_map(function($v){return str_getcsv($v, '|');}, explode('||', $csvOutput));
# print_r($array);

$intCA = count($array) -1;
unset($array[$intCA]); # RIP last array (it is empty)


    # Luftdrucktendenz - Zeitdifferenz in Stunden zwischen den 2 Messungen
    $tPa1 = strtotime($array[0][0].' '.$array[0][1]);
    $tPa2 = strtotime($array[1][0].' '.$array[1][1]);
    $tPaDelta = $tPa2 - $tPa1;
    $intStundenPaDelta = round($tPaDelta/60/60, 0);

    # Luftdrucktendenz (Stabil wenn hPa Delta kleiner als $intPStable)
    $hPa1 = intval($array[0][11]);
    $hPa2 = intval($array[1][11]);

    $hPaDelta = $hPa2 - $hPa1;
    $hPaDeltaABS = abs($hPaDelta);
    # bei positiven hPaDelta ein Pluszeichen für die Ausgabe setzen
      if ($hPaDelta > 0) {
          $hPaDelta = sprintf("%+d",$hPaDelta);
      }

    if ($hPaDeltaABS > $intPStable) {
       if ($hPa1 > $hPa2) {
           $strPTendenz = 'fallend';
       } else {
           $strPTendenz = 'steigend';
       }
       $modx->setPlaceholder('pDelta', '('.$hPaDelta.' hPa/'.$intStundenPaDelta.'h)');

    } else {
         $strPTendenz = 'stabil';
         $modx->setPlaceholder('pDelta', '');
    }
    $modx->setPlaceholder('pTendenz', $strPTendenz);


  $output = '';
  $arr = '';
  $arr = array();
  $arr2 = '';
  $arr2 = array();

  foreach ($array AS $key => $value) {
  if ($key >= $intQTY) break;

        foreach ($value AS $subKey => $subValue) {
            # echo $key.' | '.$subKey.' | '.$subValue.'<br>';

            # Platzhalter (z.B. für Kalender)
            $modx->setPlaceholder('fc_'.$key.'_'.$subKey, $subValue);

            # Array für getChunk (kann per Platzhalter [[+dwdWeather]] platziert werden)
            $arr = ['FC' => $key, 'fc'.$subKey => $subValue];
            $arr = $arr + $arr2;
            $arr2 = $arr;
        }

        if ($arr) {
            $output .= $modx->getChunk($strTPL, $arr);
        }
  }

$modx->setPlaceholder('dwdWeather', $output);