commit e99522350a9d53061e23e586af3a26a7df37bb40
Author: steckbrief <steckbrief@chefmail.de>
Date:   Sun Aug 15 17:32:18 2021 +0200

    adjusted api example from https://github.com/rainviewer/rainviewer-api-example/blob/master/rainviewer-api-example.html

diff --git a/rainviewer.html b/rainviewer.html
new file mode 100644
index 0000000..54a3cd7
--- /dev/null
+++ b/rainviewer.html
@@ -0,0 +1,286 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>RainViewer MAP</title>
+
+    <meta charset="utf-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+    <link rel="stylesheet" href="https://gpslog.famzur.de/js/leaflet/leaflet.css"/>
+    <script src="https://gpslog.famzur.de/js/leaflet/leaflet.js"></script>
+    <style type="text/css">
+        li {
+            list-style: none;
+            display: inline-block;
+        }
+    </style>
+</head>
+<body>
+
+<ul style="text-align:center; position: absolute;top: 0; left: 0; right: 0; height: 50px;">
+    <li>
+        <input type="radio" name="kind" onchange="setKind('radar')">Radar (Past + Future)
+        <input type="radio" name="kind" onchange="setKind('radarPast')">Radar (Past)
+        <input type="radio" name="kind" checked="checked" onchange="setKind('radarFuture')">Radar (Future)
+        <input type="radio" name="kind" onchange="setKind('satellite')">Infrared Satellite
+    </li>
+
+    <li><input type="button" onclick="stop(); showFrame(animationPosition - 1); return;" value="&lt;" /></li>
+    <li><input type="button" onclick="playStop();" value="Play / Stop" /></li>
+    <li><input type="button" onclick="stop(); showFrame(animationPosition + 1); return;" value="&gt;" /></li>
+
+    <li><select id="colors" onchange="setColors(); return;">
+        <option value="0">Black and White Values</option>
+        <option value="1">Original</option>
+        <option value="2" selected="selected">Universal Blue</option>
+        <option value="3">TITAN</option>
+        <option value="4">The Weather Channel</option>
+        <option value="5">Meteored</option>
+        <option value="6">NEXRAD Level-III</option>
+        <option value="7">RAINBOW @ SELEX-SI</option>
+        <option value="8">Dark Sky</option>
+    </select></li>
+</ul>
+
+<div id="timestamp" style="text-align:center; position: absolute;top: 50px; left: 0; right: 0; height: 80px;">FRAME TIME</div>
+
+<div id="mapid" style="position: absolute; top: 80px; left: 0; bottom: 0; right: 0;"></div>
+
+<script>
+    var map = L.map('mapid').setView([48.678154534097324, 9.283021551577976], 12);
+
+    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+        attributions: 'Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors'
+    }).addTo(map);
+    
+    L.marker([48.678154534097324, 9.283021551577976]).addTo(map);
+
+    /**
+     * RainViewer radar animation part
+     * @type {number[]}
+     */
+    var apiData = {};
+    var mapFrames = [];
+    var lastPastFramePosition = -1;
+    var radarLayers = [];
+
+    var optionKind = 'radar'; // can be 'radar' or 'satellite'
+
+    var optionTileSize = 256; // can be 256 or 512.
+    var optionColorScheme = 2; // from 0 to 8. Check the https://rainviewer.com/api/color-schemes.html for additional information
+    var optionSmoothData = 1; // 0 - not smooth, 1 - smooth
+    var optionSnowColors = 1; // 0 - do not show snow colors, 1 - show snow colors
+
+    var animationPosition = 0;
+    var animationTimer = false;
+
+    /**
+     * Load all the available maps frames from RainViewer API
+     */
+    var apiRequest = new XMLHttpRequest();
+    apiRequest.open("GET", "https://api.rainviewer.com/public/weather-maps.json", true);
+    apiRequest.onload = function(e) {
+        // store the API response for re-use purposes in memory
+        apiData = JSON.parse(apiRequest.response);
+        initialize(apiData, optionKind);
+    };
+    apiRequest.send();
+
+    /**
+     * Initialize internal data from the API response and options
+     */
+    function initialize(api, kind) {
+        // remove all already added tiled layers
+        for (var i in radarLayers) {
+            map.removeLayer(radarLayers[i]);
+        }
+        mapFrames = [];
+        radarLayers = [];
+        animationPosition = 0;
+
+        if (!api) {
+            return;
+        }
+        switch (kind) {
+            case 'satellite':
+                if (api.satellite && api.satellite.infrared) {
+                    mapFrames = api.satellite.infrared;
+
+                    lastPastFramePosition = api.satellite.infrared.length - 1;
+                    showFrame(lastPastFramePosition);
+                }
+                break;
+            case 'radar':
+                if (api.radar && api.radar.past) {
+                    mapFrames = api.radar.past;
+                    if (api.radar.nowcast) {
+                        mapFrames = mapFrames.concat(api.radar.nowcast);
+                    }
+
+                    // show the last "past" frame
+                    lastPastFramePosition = api.radar.past.length - 1;
+                    showFrame(lastPastFramePosition);
+                }
+                break;
+            case 'radarPast':
+                if (api.radar && api.radar.past) {
+                    mapFrames = api.radar.past;
+
+                    // show the last "past" frame
+                    lastPastFramePosition = api.radar.past.length - 1;
+                    showFrame(lastPastFramePosition);
+                }
+                break;
+            case 'radarFuture':
+                if (api.radar && api.radar.past) {
+                    mapFrames = mapFrames.concat(api.radar.past[api.radar.past.length - 1]);
+                    if (api.radar.nowcast) {
+                        mapFrames = mapFrames.concat(api.radar.nowcast);
+                    }
+
+                    // show the last "past" frame
+                    lastPastFramePosition = 0;
+                    showFrame(lastPastFramePosition);
+                }
+                break;
+        }
+    }
+
+    /**
+     * Animation functions
+     * @param path - Path to the XYZ tile
+     */
+    function addLayer(frame) {
+        if (!radarLayers[frame.path]) {
+            var colorScheme = optionKind == 'satellite' ? 0 : optionColorScheme;
+            var smooth = optionKind == 'satellite' ? 0 : optionSmoothData;
+            var snow = optionKind == 'satellite' ? 0 : optionSnowColors;
+
+            radarLayers[frame.path] = new L.TileLayer(apiData.host + frame.path + '/' + optionTileSize + '/{z}/{x}/{y}/' + colorScheme + '/' + smooth + '_' + snow + '.png', {
+                tileSize: 256,
+                opacity: 0.001,
+                zIndex: frame.time
+            });
+        }
+        if (!map.hasLayer(radarLayers[frame.path])) {
+            map.addLayer(radarLayers[frame.path]);
+        }
+    }
+
+    /**
+     * Display particular frame of animation for the @position
+     * If preloadOnly parameter is set to true, the frame layer only adds for the tiles preloading purpose
+     * @param position
+     * @param preloadOnly
+     */
+    function changeRadarPosition(position, preloadOnly) {
+        while (position >= mapFrames.length) {
+            position -= mapFrames.length;
+        }
+        while (position < 0) {
+            position += mapFrames.length;
+        }
+
+        var currentFrame = mapFrames[animationPosition];
+        var nextFrame = mapFrames[position];
+
+        addLayer(nextFrame);
+
+        if (preloadOnly) {
+            return;
+        }
+
+        animationPosition = position;
+
+        if (radarLayers[currentFrame.path]) {
+            radarLayers[currentFrame.path].setOpacity(0);
+        }
+        radarLayers[nextFrame.path].setOpacity(100);
+
+
+        var pastOrForecast = nextFrame.time > Date.now() / 1000 ? 'FORECAST' : 'PAST';
+
+        document.getElementById("timestamp").innerHTML = pastOrForecast + ': ' + (new Date(nextFrame.time * 1000)).toString();
+    }
+
+    /**
+     * Check avialability and show particular frame position from the timestamps list
+     */
+    function showFrame(nextPosition) {
+        var preloadingDirection = nextPosition - animationPosition > 0 ? 1 : -1;
+
+        changeRadarPosition(nextPosition);
+
+        // preload next next frame (typically, +1 frame)
+        // if don't do that, the animation will be blinking at the first loop
+        changeRadarPosition(nextPosition + preloadingDirection, true);
+    }
+
+    /**
+     * Stop the animation
+     * Check if the animation timeout is set and clear it.
+     */
+    function stop() {
+        if (animationTimer) {
+            clearTimeout(animationTimer);
+            animationTimer = false;
+            return true;
+        }
+        return false;
+    }
+
+    function play() {
+        showFrame(animationPosition + 1);
+
+        // Main animation driver. Run this function every 500 ms
+        animationTimer = setTimeout(play, 500);
+    }
+
+    function playStop() {
+        if (!stop()) {
+            play();
+        }
+    }
+
+    /**
+     * Change map options
+     */
+    function setKind(kind) {
+        optionKind = kind;
+        initialize(apiData, optionKind);
+    }
+
+
+    function setColors() {
+        var e = document.getElementById('colors');
+        optionColorScheme = e.options[e.selectedIndex].value;
+        initialize(apiData, optionKind);
+    }
+
+
+    /**
+     * Handle arrow keys for navigation between next \ prev frames
+     */
+    document.onkeydown = function (e) {
+        e = e || window.event;
+        switch (e.which || e.keyCode) {
+            case 37: // left
+                stop();
+                showFrame(animationPosition - 1);
+                break;
+
+            case 39: // right
+                stop();
+                showFrame(animationPosition + 1);
+                break;
+
+            default:
+                return; // exit this handler for other keys
+        }
+        e.preventDefault();
+        return false;
+    }
+</script>
+
+</body>
+</html>