Drawing wind barbs
SVG example from the arrows and barbs section.
The data is taken from the GFS model, following these steps:
- Download at a link like this one (dates may not be available): http://www.ftp.ncep.noaa.gov/data/nccf/com/gfs/prod/gfs.2016112400/
- Running gdalinfo gfs.t00z.sfluxgrbf00.grib2, see that the bands 50 and 51 are UGRD and VGRD at 10-HTGL (surface wind)
- Run gdal_translate -b 50 -b 51 -projwin 17 50 47 29 gfs.t00z.sfluxgrbf00.grib2 /tmp/gfs.tiff
- Note that the geotiff-js library has some problems with compression
- This image, taken from weatheronline, shows that the barbs are coherent with other source images
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="geotiff.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var width = 680,
height = 500,
barbSize = 30;
var projection = d3.geoConicConformal()
.rotate([-33, -5])
.center([0, 34.83158])
.scale(2000)
.translate([width / 2, height / 2]);
var path = d3.geoPath()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.request("gfs.tiff")
.responseType('arraybuffer')
.get(function(error, tiffData){
d3.json("world-110m.json", function(error, topojsonData) {
var countries = topojson.feature(topojsonData, topojsonData.objects.countries);
svg.insert("path", ".map")
.datum(countries)
.attr("d", path)
.style("fill", "#ccc")
.style("stroke", "#777");
var tiff = GeoTIFF.parse(tiffData.response);
var image = tiff.getImage();
var rasters = image.readRasters();
var tiepoint = image.getTiePoints()[0];
var pixelScale = image.getFileDirectory().ModelPixelScale;
var geoTransform = [tiepoint.x, pixelScale[0], 0, tiepoint.y, 0, -1*pixelScale[1]];
var uData = new Array(image.getHeight());
var vData = new Array(image.getHeight());
var spdData = new Array(image.getHeight());
for (var j = 0; j<image.getHeight(); j++){
uData[j] = new Array(image.getWidth());
vData[j] = new Array(image.getWidth());
spdData[j] = new Array(image.getWidth());
for (var i = 0; i<image.getWidth(); i++){
uData[j][i] = rasters[0][i + j*image.getWidth()];
vData[j][i] = rasters[1][i + j*image.getWidth()];
spdData[j][i] = 1.943844492 * Math.sqrt(uData[j][i]*uData[j][i] + vData[j][i]*vData[j][i]);
}
}
var xPos = d3.range(barbSize, width, barbSize);
var yPos = d3.range(barbSize, height, barbSize);
xPos.forEach(function(x){
yPos.forEach(function(y){
var coords = projection.invert([x,y]);
var px = Math.round((coords[0] - geoTransform[0]) / geoTransform[1]);
var py = Math.round((coords[1] - geoTransform[3]) / geoTransform[5]);
var angle = (180/Math.PI) * Math.atan2(-vData[py][px],uData[py][px]);
var spd5 = Math.round(spdData[py][px]/5);
var spd10 = Math.floor(spd5/2);
spd5 = spd5%2;
var spd50 = Math.floor(spd10/5);
spd10 = spd10%5;
var g = svg.append("g")
.style("fill", "#444")
.style("stroke", "#444")
.attr("transform", "translate("+x+", "+y+")rotate("+angle+")");
var pos = -barbSize/2;
var separation = 2.5;
for(var i=0; i<spd50; i++){
g.append("path")
.attr("d", "M"+pos+",0L"+(pos+barbSize/8)+","+(barbSize/4)+"L"+(pos+barbSize/4)+",0Z");
pos = pos + barbSize/4 + separation;
}
for(var i=0; i<spd10; i++){
g.append("line")
.attr("x1", pos)
.attr("y1", 0)
.attr("x2", pos)
.attr("y2", barbSize/3);
pos = pos + separation
}
if(spd5==1){
if (pos == -barbSize/2){
pos = pos + separation
}
g.append("line")
.attr("x1", pos)
.attr("y1", 0)
.attr("x2", pos)
.attr("y2", barbSize/6);
}
if(spd5==0 && spd10== 0 && spd50==0){
g.append("circle")
.attr("r", 4)
.attr("cx", 0)
.attr("cy", 0)
.style("fill", "None");
} else {
g.append("line")
.attr("x1", -barbSize/2)
.attr("y1", 0)
.attr("x2", barbSize/2)
.attr("y2", 0);
}
});
});
});
});
</script>
</body>