Fix DWD WMS integration: proper time dimension parsing, better animation

This commit is contained in:
Clawd
2026-02-11 13:19:28 +00:00
parent 794bd70552
commit 2f1c42a1b4

View File

@@ -1,15 +1,15 @@
Module.register("MMM-CombinedMap", { Module.register("MMM-CombinedMap", {
defaults: { defaults: {
// Map settings // Map settings
lat: 49.8833, lat: 49.8666,
lng: 8.8333, lng: 8.8505,
zoom: 7, zoom: 7,
width: "300px", width: "450px",
height: "300px", height: "300px",
// Marker // Marker
showMarker: true, showMarker: true,
markerColor: "#3388ff", markerColor: "#00cc00",
// Layers // Layers
showTraffic: true, showTraffic: true,
@@ -19,10 +19,15 @@ Module.register("MMM-CombinedMap", {
tomtomApiKey: "", tomtomApiKey: "",
// Animation settings // Animation settings
animationSpeed: 500, // ms per frame animationSpeed: 800, // ms per frame
radarOpacity: 0.7, radarOpacity: 0.7,
trafficOpacity: 0.7, trafficOpacity: 0.7,
// Radar frames
historyFrames: 12, // 1 hour of history (5 min intervals)
forecastFrames: 24, // 2 hours of forecast
extraDelayLastFrame: 2000, // Extra delay on last frame
// Update interval (ms) - how often to refresh radar data // Update interval (ms) - how often to refresh radar data
updateInterval: 5 * 60 * 1000, // 5 minutes updateInterval: 5 * 60 * 1000, // 5 minutes
}, },
@@ -47,7 +52,7 @@ Module.register("MMM-CombinedMap", {
this.radarLayers = []; this.radarLayers = [];
this.currentRadarIndex = 0; this.currentRadarIndex = 0;
this.animationTimer = null; this.animationTimer = null;
this.radarTimestamps = []; this.availableTimestamps = [];
}, },
getDom: function() { getDom: function() {
@@ -100,7 +105,7 @@ Module.register("MMM-CombinedMap", {
// Radar layer (DWD) // Radar layer (DWD)
if (this.config.showRadar) { if (this.config.showRadar) {
this.initRadarLayers(); this.fetchRadarCapabilities();
} }
// Marker // Marker
@@ -142,65 +147,125 @@ Module.register("MMM-CombinedMap", {
incidentsLayer.addTo(this.map); incidentsLayer.addTo(this.map);
}, },
initRadarLayers: function() { fetchRadarCapabilities: function() {
// Fetch available radar timestamps from DWD // Parse WMS GetCapabilities to find available time range
this.fetchRadarTimestamps(); const capsUrl = "https://maps.dwd.de/geoserver/dwd/wms?service=WMS&version=1.3.0&request=GetCapabilities";
fetch(capsUrl)
.then(response => response.text())
.then(xml => {
const parser = new DOMParser();
const doc = parser.parseFromString(xml, "text/xml");
// Find Niederschlagsradar layer and its time dimension
const layers = doc.querySelectorAll("Layer");
for (const layer of layers) {
const nameEl = layer.querySelector("Name");
if (nameEl && nameEl.textContent === "Niederschlagsradar") {
const dimEl = layer.querySelector("Dimension[name='time']");
if (dimEl) {
this.parseTimeDimension(dimEl.textContent);
}
break;
}
}
})
.catch(err => {
Log.error("MMM-CombinedMap: Failed to fetch WMS capabilities:", err);
// Fallback: generate timestamps based on current time
this.generateFallbackTimestamps();
});
}, },
fetchRadarTimestamps: function() { parseTimeDimension: function(dimStr) {
// DWD WMS GetCapabilities to find available times // Format: "2026-02-10T09:35:00.000Z/2026-02-11T15:15:00.000Z/PT5M"
// For simplicity, we'll generate timestamps for the last 2 hours + 2 hours forecast const parts = dimStr.split("/");
// DWD updates every 5 minutes if (parts.length !== 3) {
this.generateFallbackTimestamps();
return;
}
const startTime = new Date(parts[0]);
const endTime = new Date(parts[1]);
const interval = 5 * 60 * 1000; // PT5M = 5 minutes
const now = new Date(); const now = new Date();
const timestamps = []; const timestamps = [];
// Past 2 hours (every 5 minutes = 24 frames) // Get frames from history (before now)
for (let i = 24; i >= 0; i--) { const historyStart = Math.max(
const t = new Date(now.getTime() - i * 5 * 60 * 1000); startTime.getTime(),
// Round to nearest 5 minutes now.getTime() - this.config.historyFrames * interval
);
// Get frames into forecast (after now)
const forecastEnd = Math.min(
endTime.getTime(),
now.getTime() + this.config.forecastFrames * interval
);
// Round to 5-minute intervals
let t = Math.floor(historyStart / interval) * interval;
while (t <= forecastEnd) {
if (t >= startTime.getTime() && t <= endTime.getTime()) {
timestamps.push(new Date(t));
}
t += interval;
}
this.availableTimestamps = timestamps;
this.createRadarLayers();
},
generateFallbackTimestamps: function() {
// Fallback: generate timestamps without checking capabilities
const now = new Date();
const interval = 5 * 60 * 1000;
const timestamps = [];
// Past hour
for (let i = this.config.historyFrames; i >= 0; i--) {
const t = new Date(now.getTime() - i * interval);
t.setMinutes(Math.floor(t.getMinutes() / 5) * 5, 0, 0); t.setMinutes(Math.floor(t.getMinutes() / 5) * 5, 0, 0);
timestamps.push(t); timestamps.push(t);
} }
// Future 2 hours (nowcast) - every 5 minutes = 24 frames // Future 2 hours
for (let i = 1; i <= 24; i++) { for (let i = 1; i <= this.config.forecastFrames; i++) {
const t = new Date(now.getTime() + i * 5 * 60 * 1000); const t = new Date(now.getTime() + i * interval);
t.setMinutes(Math.floor(t.getMinutes() / 5) * 5, 0, 0); t.setMinutes(Math.floor(t.getMinutes() / 5) * 5, 0, 0);
timestamps.push(t); timestamps.push(t);
} }
this.radarTimestamps = timestamps; this.availableTimestamps = timestamps;
this.createRadarLayers(); this.createRadarLayers();
}, },
createRadarLayers: function() { createRadarLayers: function() {
// Clear existing layers // Clear existing layers
this.radarLayers.forEach(layer => { this.radarLayers.forEach(layer => {
if (this.map.hasLayer(layer.leafletLayer)) { if (this.map && this.map.hasLayer(layer.leafletLayer)) {
this.map.removeLayer(layer.leafletLayer); this.map.removeLayer(layer.leafletLayer);
} }
}); });
this.radarLayers = []; this.radarLayers = [];
// DWD WMS for radar if (!this.availableTimestamps.length) return;
// Using RV (Radar Vorhersage/Forecast) product for nowcast
// and standard radar for past
const now = new Date(); const now = new Date();
this.radarTimestamps.forEach((timestamp, index) => { this.availableTimestamps.forEach((timestamp, index) => {
const isForecast = timestamp > now; const isForecast = timestamp > now;
const timeStr = timestamp.toISOString(); const timeStr = timestamp.toISOString().replace(/\.\d{3}Z$/, ".000Z");
// DWD WMS layer - using Niederschlagsradar (precipitation radar) // DWD WMS layer
const layer = L.tileLayer.wms("https://maps.dwd.de/geoserver/dwd/wms", { const layer = L.tileLayer.wms("https://maps.dwd.de/geoserver/dwd/wms", {
layers: isForecast ? "dwd:RV-Produkt" : "dwd:Niederschlagsradar", layers: "dwd:Niederschlagsradar",
format: "image/png", format: "image/png",
transparent: true, transparent: true,
opacity: 0, // Start hidden opacity: 0, // Start hidden
time: timeStr, time: timeStr,
styles: "", styles: "niederschlagsradar",
version: "1.3.0", version: "1.3.0",
crs: L.CRS.EPSG3857 crs: L.CRS.EPSG3857
}); });
@@ -210,7 +275,8 @@ Module.register("MMM-CombinedMap", {
this.radarLayers.push({ this.radarLayers.push({
leafletLayer: layer, leafletLayer: layer,
timestamp: timestamp, timestamp: timestamp,
isForecast: isForecast isForecast: isForecast,
isLast: index === this.availableTimestamps.length - 1
}); });
}); });
@@ -225,13 +291,23 @@ Module.register("MMM-CombinedMap", {
clearInterval(this.animationTimer); clearInterval(this.animationTimer);
} }
// Show first frame this.currentRadarIndex = 0;
this.showRadarFrame(0); this.showRadarFrame(0);
this.animationTimer = setInterval(() => { const animate = () => {
this.currentRadarIndex = (this.currentRadarIndex + 1) % this.radarLayers.length; const currentLayer = this.radarLayers[this.currentRadarIndex];
this.showRadarFrame(this.currentRadarIndex); const delay = currentLayer && currentLayer.isLast
}, this.config.animationSpeed); ? this.config.animationSpeed + this.config.extraDelayLastFrame
: this.config.animationSpeed;
this.animationTimer = setTimeout(() => {
this.currentRadarIndex = (this.currentRadarIndex + 1) % this.radarLayers.length;
this.showRadarFrame(this.currentRadarIndex);
animate();
}, delay);
};
animate();
}, },
showRadarFrame: function(index) { showRadarFrame: function(index) {
@@ -243,7 +319,7 @@ Module.register("MMM-CombinedMap", {
} }
}); });
// Update timestamp display if needed // Update timestamp display
this.updateTimestampDisplay(this.radarLayers[index]); this.updateTimestampDisplay(this.radarLayers[index]);
}, },
@@ -263,7 +339,7 @@ Module.register("MMM-CombinedMap", {
const diffMinutes = Math.round((time - now) / 60000); const diffMinutes = Math.round((time - now) / 60000);
let label; let label;
if (diffMinutes === 0) { if (Math.abs(diffMinutes) <= 2) {
label = "Now"; label = "Now";
} else if (diffMinutes > 0) { } else if (diffMinutes > 0) {
label = `+${diffMinutes} min`; label = `+${diffMinutes} min`;
@@ -277,12 +353,12 @@ Module.register("MMM-CombinedMap", {
refreshRadar: function() { refreshRadar: function() {
Log.info("MMM-CombinedMap: Refreshing radar data"); Log.info("MMM-CombinedMap: Refreshing radar data");
this.fetchRadarTimestamps(); this.fetchRadarCapabilities();
}, },
suspend: function() { suspend: function() {
if (this.animationTimer) { if (this.animationTimer) {
clearInterval(this.animationTimer); clearTimeout(this.animationTimer);
this.animationTimer = null; this.animationTimer = null;
} }
}, },