The weather animation can be used using the "leaflet" JavaScript library.

Required components for using the weather animation (download the required files as .zip, except javascript_vars.js and javascript_vars_rtofs.js, read explanation below:

Useful components to use the weather animation:

 

Options that affect the appearance of the weather animation:

All options can be tested with the configurator and the complete code for displaying the page can be show, cut and paste, see https://weather.openportguide.org/map_leaflet.html. The configurator should be used with Google Chrome!

Option Typ Default Description
displayValues boolean true Determines whether the values are displayed at the mouse position.
velocityType text "Velocity" Used to describe the displayed values at the mouse position.
emptyString text "Unavailable" Appears when there are no mouse position values.
angleConvention text "meteoCW" The "angleConvention" option refers to the convention used to express the wind direction as an angle from north direction in the control. It can be any combination of "bearing" (angle toward which the flow goes) or "meteo" (angle from which the flow comes), and "CW" (angle value increases clock-wise) or "CCW" (angle value increases counter clock-wise). If not given defaults to "meteoCW".
speedUnit text "m/s" Indicates in which unit the speed is displayed at the mouse position. The following values are possible:
"m/s" for meters per second
"km/h" for kilometers per hour
"kt" for knot
"Bft" for Beaufort
minVelocity floating point 0 Velocity at which particle intensity is minimum (m/s)
maxVelocity floating point 10 Velocity at which particle intensity is maximum (m/s)
velocityScale floating point 0.005 Scale for wind velocity (completely arbitrary--this value looks nice)
particleAge Integer 90 max number of frames a particle is drawn before regeneration
lineWidth integer 1 Line width of a drawn particle
particleMultiplier floating point 0.003333 Particle count scalar (completely arbitrary--this values looks nice)
frameRate integer 15 Desired frames per second
colorScale array ["rgb(36,104,180)",   Color scale (first color value corresponds to "minVelocity", last color value corresponds to "maxVelocity", the color values are equally distributed between "minVelocity" and "maxVelocity" and the number of color values is arbitrary.
 "rgb(60,157,194)",  
 "rgb(128,205,193 )",  
 "rgb(151,218,168 )",  
 "rgb(198,231,181)",  
 "rgb(238,247,217)",  
 "rgb(255,238,159)",  
 "rgb(252,217,125)",  
 "rgb(255,182,100)",  
 "rgb(252,150,75)",  
 "rgb(250,122,52)",  
 "rgb(245,64,32)",  
 "rgb(237,45,28)",  
 "rgb(220,24,32)",  
 "rgb(180,0,35)"]  

 

Code Example:

<!DOCTYPE html>
<html>
<head>

<title>Leaflet - Particle animation</title>

<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<!-- Load Leaflet 1.3.1 -->
<link rel="stylesheet" href="/leaflet/leaflet.css" integrity="sha256-NUykZmi4kbsqfyw0XgSwmjUlpqW/u74zu5ibK9DuiSY=" crossorigin=""/>
<script src="/leaflet/leaflet.js" integrity="sha256-VxczYNPPl3vWEQjM566cf3Le5VD0hnABQ1iohWuvfqc=" crossorigin=""></script>

<!-- Load Esri Leaflet 2.1.4 -->
<script src="/esri-leaflet/esri-leaflet.js" integrity="sha256-5QgVwsPW6/SFxy0erdtA6j9XK682s/cOIrUiq8BuYGw=" crossorigin=""></script>

<!-- Load JQuery 3.3.1 -->
<script src="/jquery-3.3.1.min.js" integrity="sha256-oozPintQUive6gzYPN7KIhwY/B+d8+5rPTxI1ZkgaFU=" crossorigin=""></script>

<!--leaflet-velocity-->
<link rel="stylesheet" href="/leaflet-velocity_tkws/leaflet-velocity.css" />
<script src="/leaflet-velocity_tkws/leaflet-velocity.js"></script>
<script src="/leaflet-velocity_tkws/IE_workarounds.js"></script>

<!--for timeslider-->
<script type="text/javascript" src="/leafletTimeDimension/iso8601.min.js"></script>
<script type="text/javascript" src="/leafletTimeDimension/leaflet.timedimension.noLayers.src.js"></script>
<link rel="stylesheet" href="/leafletTimeDimension/leaflet.timedimension.control.min.css" />

<!--load variable values from server-->
<script type="text/javascript" src="/weather/javascript_vars.js"></script>
<script type="text/javascript" src="/weather/javascript_vars_rtofs.js"></script>

<style>
body { margin:0; padding:0; }
#map { position: absolute; top:0; bottom:0; right:0; left:0; z-index:1; }
</style>
</head>
<body>
<div id="map"></div>

<script>
var osmUrl='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
var osmAttrib='Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors';
var OpenStreetMap = new L.tileLayer(osmUrl, {maxZoom: 18, attribution: osmAttrib, opacity: 0.4}),
Topographic = new L.esri.basemapLayer('Topographic', {opacity: 0.4}),
Streets = new L.esri.basemapLayer('Streets', {opacity: 0.4}),
NationalGeographic = new L.esri.basemapLayer('NationalGeographic', {opacity: 0.4}),
Oceans = new L.esri.basemapLayer('Oceans', {opacity: 0.4}),
Gray = new L.esri.basemapLayer('Gray', {opacity: 0.4}),
DarkGray = new L.esri.basemapLayer('DarkGray'),
Imagery = new L.esri.basemapLayer('Imagery'),
ShadedRelief = new L.esri.basemapLayer('ShadedRelief', {opacity: 0.4});


var startTime = new Date(Date.UTC(GFS_server_year, GFS_server_month - 1, GFS_server_day, GFS_server_hour));
var actualTime = new Date(Date.UTC(GFS_server_year, GFS_server_month - 1, GFS_server_day, GFS_server_hour + 6)); //actual time is about 6 hours ahead to the first forecast timestep
var endTime = new Date(Date.UTC(GFS_server_year, GFS_server_month - 1, GFS_server_day, GFS_server_hour + ((GFS_timesteps-1)*GFS_interval)));
var dataTimeInterval = startTime.toISOString() + "/" + endTime.toISOString();
var actualInterval = GFS_interval*2 ; // show only every second available timestep (GFS_interval is "3" hours
var baseIndex = 1; // index of the wind10mArray containing the layer nearest to the actual time (2 if actualIndex==GFS_Index, 1 if actualIndex==GFS_Index*2)
var dataPeriod = "PT" + (actualInterval) + "H";
var wind10mBaseURL = 'weather/wind10m/';
var wind10mBaseName = 'wind10m_{h}h';
var wind10mName = '';
var wind10mArray = [];


var startTimeRTOFS = new Date(Date.UTC(RTOFS_server_year, RTOFS_server_month - 1, RTOFS_server_day, RTOFS_server_hour));
var actualTimeRTOFS = new Date(Date.UTC(RTOFS_server_year, RTOFS_server_month - 1, RTOFS_server_day, RTOFS_server_hour + 6));
var endTimeRTOFS = new Date(Date.UTC(RTOFS_server_year, RTOFS_server_month - 1, RTOFS_server_day, RTOFS_server_hour + ((RTOFS_timesteps-1)*RTOFS_interval)));
var dataTimeIntervalRTOFS = startTimeRTOFS.toISOString() + "/" + endTimeRTOFS.toISOString();
var actualIntervalRTOFS = RTOFS_interval*2 ; // show only every second available timestep
var baseIndexRTOFS = 1; // index of the seaSurfaceCurrent Array containing the layer nearest to the actual time (2 if actualIndex==RTOFS_Index, 1 if actualIndex==RTOFS_Index*2)
var dataPeriodRTOFS = "PT" + (actualIntervalRTOFS) + "H";
var seaSurfaceCurrentBaseURL = 'weather/sea_surface_current/';
var seaSurfaceCurrentBaseName = 'sea_surface_current_{h}h';
var seaSurfaceCurrentName = '';
var seaSurfaceCurrentArray = [];


var map = new L.map('map', {
center: [54.04, 9.07],
zoom: 4,
layers: [Imagery],
timeDimension: true,
timeDimensionOptions: {
timeInterval: dataTimeInterval,
period: dataPeriod,
currentTime: actualTime
},
timeDimensionControl: true,
timeDimensionControlOptions: {
loopButton: false,
limitSliders: false,
playButton: false,
speedSlider: false
}
});

var baseMaps = {
"OpenStreetMap": OpenStreetMap,
"Topographic": Topographic,
"Streets": Streets,
"NationalGeographic": NationalGeographic,
"<span style='color: gray'>Gray</span>": Gray,
"DarkGray": DarkGray,
"Imagery": Imagery,
"ShadedRelief": ShadedRelief,
"Oceans": Oceans,
};

var layerControl = new L.control.layers(baseMaps);
layerControl.addTo(map);

var wind10mLayerGroup = new L.layerGroup([], {});
wind10mArray.length = map.timeDimension._availableTimes.length;

var actualTimeIndex = map.timeDimension._currentTimeIndex;

// load data (u, v grids) from weather.openportguide.de
layerControl.addOverlay(wind10mLayerGroup, 'wind10m');
updateLayer(wind10mArray[actualTimeIndex]);

window.setInterval(function() { //check if time index changed
if (actualTimeIndex != map.timeDimension._currentTimeIndex) {
actualTimeIndex = map.timeDimension._currentTimeIndex;
updateLayer(wind10mArray[actualTimeIndex]);
}
},100);

function updateLayer(Layer){ //updates the actual layer
wind10mLayerGroup.clearLayers();
wind10mName = wind10mBaseName.replace(/{h}/g, (actualTimeIndex - baseIndex) * actualInterval);

$.getJSON(wind10mBaseURL + wind10mName + ".json", function (data) {
this[wind10mName] = L.velocityLayer({
displayValues: true,
displayOptions: {
velocityType: "Wind",
emptyString: "No wind data",
angleConvention: "bearingCW",
speedUnit: "Bft"
},
data: data,
minVelocity: 0,
maxVelocity: 30,
velocityScale: 0.002,
particleAge: 90,
lineWidth: 1,
particleMultiplier: 0.0033,
frameRate: 15,
colorScale: ["#2468b4", "#3c9dc2", "#80cdc1", "#97daa8", "#c6e7b5", "#eef7d9", "#ffee9f", "#fcd97d", "#ffb664", "#fc964b", "#fa7034", "#f54020", "#ed2d1c", "#dc1820", "#b40023"]
});

wind10mLayerGroup.addLayer(this[wind10mName]);
wind10mArray[actualTimeIndex] = wind10mLayerGroup.getLayer(wind10mLayerGroup.getLayerId(this[wind10mName]));
wind10mLayerGroup.addTo(map);
});
}
</script>
</body>
</html>

 

Hints:

If base maps with bright colors, e.g. Openstreetmap be used as a background, the weather animation is hard to see.
In this case, the opacity of the base layer can simply be reduced and the image darkens due to the black background, such as:
var OpenStreetMap = new L.tileLayer(osmUrl, {maxZoom: 18, attribution: osmAttrib, opacity: 0.4});

 

There is a big difference in which browser is used, so here's a note on compatibility with the various browsers:

Browser Recommendation Remarks
Chrome very good The animation runs on PCs of all performance classes with a very low resource consumption.
Opera good The animation runs on PCs of all performance classes with a reasonable resource consumption.
Firefox rather not recommended The animation runs smoothly on powerful machines, even if the mouse is moved over the map. However, the CPU load is up to 10 times higher than when viewing with Chrome.
Edge not recommended The animation does not run smoothly even on powerful PCs when the mouse is moved on the map.
Internet Explorer not recommended The animation does not run smoothly even on powerful PCs when the mouse is moved on the map.
Also, because of the incompatibility with some of the newer components of Java, the animation is only executable with additional code (see above). Since the Internet Explorer is not developed further, its use is to be seen only as absolute emergency solution.