[Modul] OpenWeatherMap

Data ist die alte, OneCall die neue API

Im Data-Modul gibt es kein Property “Location” sondern zwei getrennte - “longitude” und “latitude”

1 „Gefällt mir“

Danke Dir. Dann hab ich mal alles umgestellt. Jetzt funktioniert auch das Script :slight_smile:

Habe noch ein “autoplay” für die Zeit bzw. Bewegung des Wetters integriert. So spart man sich das aktivieren auf der Seite.

fetch(„``https://api.rainviewer.com/public/weather-maps.json“``)
.then(r => r.json())
.then(data => {
frames = data.radar.past;
frameSlider.max = frames.length - 1;
currentFrame = frames.length - 1;
loadFrame(currentFrame);
togglePlay(); // ← Automatische Animation beim Laden!
});

1 „Gefällt mir“

Für alle, die mein Wetterradar-Script für @demel42 OpenWeatherOneCall Modul verwenden, gibt es hier Version 1.1 mit responsivem Design, zB für die Handy-App

2 „Gefällt mir“

Fein! Hab mir die Location flexibel gemacht da im Wohnmobil eingesetzt. (GPS kommt vom RUTx50):

Frage zum Regenradar: aktuell zeigt es die Vergangenheit bis jetzt, sollte das nicht etwas in die Zukunft schauen? Ich muss mal graben, hatte das mal umgesetzt …

Gruß, Michael

Das wäre cool. Hab ich mich auch schon gefragt, da es ja eigentlich eine WetterVORHERSAGE sein soll :stuck_out_tongue:

Bilder und Film gibt es zumindest für DE über das Modul von pitti

Unwetterwarnung im Store

Geht mit Rain Viewer: Leading Weather Radar App for Accurate Rain & Snow Forecasts| Rain Viewer leider nicht.

Ich such noch mal was/wie ich das gelöst hatte… melde mich dann, kann etwas dauern

ich hab das Script mal etwas für meine Belange vergewaltigt …man verzeihe mir, aber das html Zeugs ist nicht meine Sache… dafür gibt es etwas Forecast und die Standortdaten kommen aus dem Location Control da ich das im Wohnmobil nutze und die Ortsänderungen da rein schreibe … vielleicht kann ein Experte mehr draus machen:

<?php
declare(strict_types=1);

$htmlBoxID = 19318;
$owmInstID = 30398;
$locInstID = 31099;
$zoom = 9;
$theme = 'dark';

// Standortdaten
$config = json_decode(IPS_GetConfiguration($locInstID), true);
$location = json_decode($config["Location"], true);
$lat = $location['latitude'];
$lon = $location['longitude'];
$daily_forecast_count = (int) IPS_GetProperty($owmInstID, 'daily_forecast_count');

// Aktuelle Werte
function valOrOWM($id, $owmInstID, $ident) {
    return ($id > 0) ? GetValueFormatted($id) : GetValueFormatted(IPS_GetObjectIDByIdent($ident, $owmInstID));
}
$temperature = valOrOWM(0, $owmInstID, 'Temperature');
$humidity    = valOrOWM(0, $owmInstID, 'Humidity');
$wind_speed  = valOrOWM(0, $owmInstID, 'WindSpeed');
$rain_1h     = valOrOWM(0, $owmInstID, 'Rain_1h');
$clouds      = GetValueFormatted(IPS_GetObjectIDByIdent('Cloudiness', $owmInstID));

// Forecastdaten
$forecast = [];
for ($i = 0; $i < $daily_forecast_count; $i++) {
    $pre = 'DailyForecast';
    $post = '_' . sprintf('%02d', $i);
    $idSnow = @IPS_GetObjectIDByIdent($pre . 'Snow' . $post, $owmInstID);
    $forecast[] = [
        'dt' => GetValueInteger(IPS_GetObjectIDByIdent($pre . 'Begin' . $post, $owmInstID)),
        'min' => GetValueFloat(IPS_GetObjectIDByIdent($pre . 'TemperatureMin' . $post, $owmInstID)),
        'max' => GetValueFloat(IPS_GetObjectIDByIdent($pre . 'TemperatureMax' . $post, $owmInstID)),
        'wind' => GetValueFloat(IPS_GetObjectIDByIdent($pre . 'WindSpeed' . $post, $owmInstID)),
        'rain' => GetValueFloat(IPS_GetObjectIDByIdent($pre . 'Rain' . $post, $owmInstID)),
        'snow' => $idSnow ? GetValueFloat($idSnow) : 0,
        'icon' => GetValueString(IPS_GetObjectIDByIdent($pre . 'ConditionIcon' . $post, $owmInstID)),
        'description' => GetValueString(IPS_GetObjectIDByIdent($pre . 'Conditions' . $post, $owmInstID)),
        'humidity' => GetValueFloat(IPS_GetObjectIDByIdent($pre . 'Humidity' . $post, $owmInstID)),
        'clouds' => GetValueFloat(IPS_GetObjectIDByIdent($pre . 'Cloudiness' . $post, $owmInstID)),
    ];
}
$json = json_encode($forecast, JSON_UNESCAPED_SLASHES | JSON_HEX_APOS | JSON_HEX_QUOT);

// HTML Ausgabe
$html = <<<HTML
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Radar & Forecast</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"/>
<style>
:root { --k: 0.75; }
html, body, #map { height: 100%; margin: 0; }
body.theme-dark { --bg: rgba(40,40,40,0.85); --text: #fff; --panel: #2b2b2b; --border:#444; --btn-bg:#333; --btn-fg:#fff; --btn-border:#999;}
body.theme-light { --bg: rgba(255,255,255,0.9); --text: #222; --panel: #fff; --border:#ccc; --btn-bg:#fff; --btn-fg:#000; --btn-border:#ddd;}
body { background: var(--bg); color: var(--text); font-family: system-ui, Segoe UI, Roboto, sans-serif; font-size: calc(12px * var(--k)); }

.panel { background: var(--panel); color: var(--text); border-radius: calc(10px * var(--k)); padding: calc(8px * var(--k)); box-shadow: 0 4px 14px rgba(0,0,0,.25); backdrop-filter: blur(4px); position: absolute; z-index:1000; }

#map { z-index:0; }

#current {
  top: calc(10px * var(--k));
  left: 50px;
  display: flex;
  gap: calc(12px * var(--k));
  align-items: center;
  flex-wrap: wrap;
  width: auto;
  max-width: calc(100% - 40px);
  font-size: calc(11px * var(--k));
}

#current-values {
  display: flex;
  gap: calc(10px * var(--k));
  flex-wrap: nowrap;
}

#current-values div {
  display: flex;
  align-items: center;
  gap: calc(4px * var(--k));
  white-space: nowrap;
}

#controls {
  top: calc(60px * var(--k));
  right: 10px;
  left: auto;
  width: calc(200px * var(--k));
  font-size: calc(9px * var(--k));
  padding: calc(6px * var(--k));
}
@media (min-width: 800px) {
  #controls { top: calc(10px * var(--k)); }
}

#controls label {
  font-size: calc(9px * var(--k));
  margin: calc(4px * var(--k)) 0 calc(2px * var(--k));
}

#controls input[type=range] {
  width: 100%;
  height: calc(16px * var(--k));
}

#frameTime {
  margin-top: calc(4px * var(--k));
  font-size: calc(9px * var(--k));
}

#forecast { bottom: calc(10px * var(--k)); right: calc(10px * var(--k)); display:flex; gap: calc(8px * var(--k)); }
.forecast-entry { text-align:center; font-size: calc(11px * var(--k)); }
.forecast-entry img { width: calc(40px * var(--k)); height: calc(40px * var(--k)); }

#legend {
  bottom: calc(10px * var(--k));
  left: calc(10px * var(--k));
  font-size: calc(10px * var(--k));
  background: var(--panel);
  padding: calc(6px * var(--k));
  border-radius: calc(6px * var(--k));
  z-index: 1001;
}
#legend .legend-entry { display:flex; align-items:center; gap: calc(8px * var(--k)); margin-bottom: calc(6px * var(--k)); white-space:nowrap; }
#legend .legend-color { width: calc(18px * var(--k)); height: calc(12px * var(--k)); border: 1px solid var(--border); flex: 0 0 auto; border-radius: 2px; }

#controls .row button {
  background: var(--btn-bg);
  border: 1px solid var(--btn-border);
  border-radius: 4px;
  padding: 2px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--btn-fg);
}
#controls .row button:hover {
  background: rgba(255,255,255,0.15);
}
#controls .row button .ico svg {
  fill: var(--btn-fg);
  width: 20px;
  height: 20px;
}
</style>
</head>
<body class="theme-{$theme}">
<div id="map"></div>

<div id="current" class="panel">
  <div id="current-values">
    <div>🌡 {$temperature}</div>
    <div>💧 {$humidity}</div>
    <div>🍃 {$wind_speed}</div>
    <div>☔ {$rain_1h}</div>
    <div>☁ {$clouds}</div>
  </div>
</div>

<div id="controls" class="panel">
  <div class="row" style="display:flex; gap:4px; align-items:center; margin-bottom:4px;">
    <button id="btnPrev" title="Vorheriger Frame" aria-label="Vorheriger">
      <span class="ico" aria-hidden="true">
        <svg viewBox="0 0 24 24"><path d="M15.5 5.5 8.5 12l7 6.5-1.5 1.5L5.5 12l8.5-8.5 1.5 2z"/></svg>
      </span>
    </button>
    <button id="btnPlay" title="Abspielen" aria-label="Abspielen">
      <span class="ico" aria-hidden="true">
        <svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
      </span>
    </button>
    <button id="btnNext" title="Nächster Frame" aria-label="Nächster">
      <span class="ico" aria-hidden="true">
        <svg viewBox="0 0 24 24"><path d="m8.5 5.5 1.5-2L18.5 12l-8.5 8.5-1.5-1.5 7-6.5-7-6.5z"/></svg>
      </span>
    </button>
  </div>
  <label for="frameSlider">Zeit</label>
  <input type="range" id="frameSlider" min="0" max="0" step="1" value="0">
  <label for="opacityRange">Deckkraft</label>
  <input type="range" id="opacityRange" min="0" max="1" step="0.01" value="0.5">
  <span id="frameTime">⏳</span>
</div>

<div id="forecast" class="panel"></div>

<div id="legend" class="panel">
  <div class="legend-entry"><div class="legend-color" style="background:#b3d9ff;"></div><div>Sehr leicht</div></div>
  <div class="legend-entry"><div class="legend-color" style="background:#3399ff;"></div><div>Leicht</div></div>
  <div class="legend-entry"><div class="legend-color" style="background:#0066ff;"></div><div>Mäßig</div></div>
  <div class="legend-entry"><div class="legend-color" style="background:#cc3300;"></div><div>Stark</div></div>
  <div class="legend-entry"><div class="legend-color" style="background:#990099;"></div><div>Extrem</div></div>
</div>

<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script>
const LAT={$lat}, LON={$lon};
const map = L.map('map').setView([LAT,LON], {$zoom});
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{maxZoom:19}).addTo(map);
L.marker([LAT,LON]).addTo(map).bindPopup("Standort");

let frames=[], currentFrame=0, radarLayer=null, playInterval=null;
const frameSlider=document.getElementById('frameSlider');
const opacitySlider=document.getElementById('opacityRange');
const timeDisplay=document.getElementById('frameTime');

function loadFrame(i){
  if(!frames.length) return;
  const ts=frames[i].time;
  const url="https://tilecache.rainviewer.com"+frames[i].path+"/256/{z}/{x}/{y}/2/1_1.png";
  if(radarLayer) map.removeLayer(radarLayer);
  radarLayer=L.tileLayer(url,{opacity:parseFloat(opacitySlider.value),zIndex:500}).addTo(map);
  timeDisplay.textContent=new Date(ts*1000).toLocaleTimeString();
  frameSlider.value=i;
  currentFrame=i;
}
function nextFrame(){ currentFrame=(currentFrame+1)%frames.length; loadFrame(currentFrame); }

opacitySlider.addEventListener('input',()=>{ if(radarLayer) radarLayer.setOpacity(parseFloat(opacitySlider.value)); });
frameSlider.addEventListener('input',e=>{ currentFrame=parseInt(e.target.value,10); loadFrame(currentFrame); });

fetch("https://api.rainviewer.com/public/weather-maps.json")
  .then(r=>r.json())
  .then(data=>{ frames=(data.radar.past||[]).concat(data.radar.nowcast||[]); if(!frames.length) return; frameSlider.max=frames.length-1; currentFrame=frames.length-1; loadFrame(currentFrame); })
  .catch(console.error);

// Forecast
const forecast={$json};
const ICON_URL="https://raw.githubusercontent.com/basmilius/weather-icons/dev/production/fill/svg/";
const iconMap={"01d":"clear-day","01n":"clear-night","02d":"partly-cloudy-day","02n":"partly-cloudy-night","03d":"cloudy","04d":"overcast","09d":"rain","10d":"rain","11d":"thunderstorms-day","13d":"snow"};
const box=document.getElementById("forecast");
forecast.forEach(f=>{
  const d=new Date(f.dt*1000);
  const day=d.toLocaleDateString("de-CH",{weekday:"short"});
  const icon=ICON_URL+(iconMap[f.icon]||"not-available")+".svg";
  const entry=document.createElement("div");
  entry.className="forecast-entry";
  entry.innerHTML="<div class='day'>"+day+"</div><img src='"+icon+"'><div class='temp'>"+Math.round(f.max)+"°/"+Math.round(f.min)+"°</div>";
  box.appendChild(entry);
});

// Steuerungstasten
const btnPrev=document.getElementById('btnPrev');
const btnPlay=document.getElementById('btnPlay');
const btnNext=document.getElementById('btnNext');

btnPrev.addEventListener('click', ()=>{
  currentFrame=(currentFrame-1+frames.length)%frames.length;
  loadFrame(currentFrame);
});

btnNext.addEventListener('click', ()=>{
  nextFrame();
});

btnPlay.addEventListener('click', ()=>{
  if(playInterval){
    clearInterval(playInterval);
    playInterval=null;
  } else {
   

    playInterval=setInterval(nextFrame, 1000);
  }
});
</script>
</body>
</html>
HTML;

SetValueString($htmlBoxID,$html);

Gruß Michael

Super gemacht, danke für den Tipp mit der Vorhersage…

Habe jetzt meinen Skript auf Version 1.2 aktualisiert, wo die Vorhersage ebenfalls möglich ist. Jedoch startet mein Skript mit dem letzten Live-Bild.

Die Quelle der Location belasse ich aber im Modul von @demel42 , was bedingt, dass diese befüllt wird. Diesbezüglich wurde die Info im Skript ergänzt.

Auch hat dein Skript ein wenig das Design, in meinen Augen negativ, verändert.

1 „Gefällt mir“

Ja, das Design hat gelitten, da fehlt mir die Kompetenz​:smiling_face_with_tear:… ich hatte gehofft, dass jemand (du) das geradebiegen kannst :+1:

Ich verwende das Skript auch, gefällt mir gut. Aber ich sehe keinen Unterschied zwischen 1.1 und 1.2.

Was ist da hinzugekommen? Die Vorhersage rechts unten gabe es auch schon vorher.

Die Vorhersage der Radarbilder ist hinzugekommen… Wenn die Kachel mit V 1.2 startet, siehst du das daran, dass der Schieber mit der Zeit nicht ganz rechts ist. Weiter nach rechts kommen die Vorschaubilder.

Ah, du meinste das

Da wird jetzt bis 7:00 angezeigt und vorher “nur” bis zur aktuellen Zeit.

Genau, wenn die Seite neu geladen wird, wird immer das letzte Live-Bild angezeigt, dann kannst du in Prinzip nach rechts ein wenig in die Zukunft schauen…

2 „Gefällt mir“

Gibt es eine Möglichkeit, die Deckkraft im Skript zu ändern? Ich habe gesucht, aber nichts gefunden.

Gefunden :wink:

    <label for="opacityRange">Deckkraft</label>
    <input id="opacityRange" type="range" min="0" max="1" step="0.01" value="1" 

Hier den Wert für value setzen.

1 „Gefällt mir“

Ich habe eben die Anzeige in einer andere Visualisierung eingebaut. Die Visu wrid auf einem Tablett im Querformat dargestellt. Im Browser am Laptop werden die Wolken dargestellt. Am Tablett in der App nicht. Ich kann auch keine Zeit wechseln und unter dem Balken der Deckkraft wird mir eine kleine Sanduhr angezeigt.

Am Handy funktioniert die Anzeige korrekt.

Hast du eine Idee @mb-stern ?

Wenn das nur das Tablett ist, dann liegt es am Browser des Tablets….

Ähhm, wie bekomme ich das Radarbild? Habe nichts gefunden unter der Instanz..

lg

Du must eine Stringvariable anlegen und diese als HTML Content definieren. Die ID von der Variable kommt in das Skript und jenes befüllt dann diese Variable die du auf Visu verlinkst.

1 „Gefällt mir“