Generate a Base Wind Image From a NOAA Dataset
This section provides the steps for generating a base wind image using a NOAA provider.
You can generate a base image for creating a wind animation dataset
from the NOAA GFS
Dataset website or programmatically through scripts.
- Open the URL NOAA GFS Dataset in your browser.
- Select a Subdirectory for any specified data and time and proceed to the atmos option to work with atmospheric data.
- Set the file type to
gfs.t06z.pgrb2.1p00.f000. - Select the layer type as 10m above the ground.
- Select the UGRD (U-component of wind, eastward velocity) variable, specify the bounding box (bbox) you wish to analyze, and then click Start Download.
- Repeat the previous step for VGRD (V-component of wind,
northward velocity).Note that the velocity grids are downloaded as GRIB files.
- Parse to extract the grid values from these files using GRIB tools (for example, wgrib2).
- Create the base image by combining the grid values for UGRD and VGRD.Note the following for creating the base image:
- The R (Red) channel represents UGRD (x-axis velocities).
- The G (Green) channel represents VGRD (y-axis velocities).
- The velocity values must be scaled to fit within the 0-255
range, where
umin,umax,vmin, andvmaxrepresent the minimum and maximum values for theu(x-axis) andv(y-axis) velocities.These minimum and maximum values are crucial for encoding and decoding the velocity values from the base image, as they will be scaled to fit the 0-255 range for visualization purposes.
Note:
It is important that you note the data range values as these will be required to create the wind animation dataset for the first time in Spatial Studio.
Alternatively, you can generate the base wind image programmatically through code as shown in the following example scripts.The following scripts describe the steps for downloading, parsing grid values, and finally writing them into an image format for use in visualization.
download.sh
#!/bin/bash GFS_DATE="20240719" GFS_TIME="00"; # 00, 06, 12, 18 RES="1p00" # 0p25, 0p50 or 1p00 BBOX="leftlon=0&rightlon=360&toplat=90&bottomlat=-90" LEVEL="lev_10_m_above_ground=on" GFS_URL="https://nomads.ncep.noaa.gov/cgi-bin/filter_gfs_${RES}.pl?dir=%2Fgfs.${GFS_DATE}%2F${GFS_TIME}%2Fatmos&file=gfs.t06z.pgrb2.1p00.f000&${LEVEL}=on&subregion=${BBOX}" curl "${GFS_URL}&var_UGRD=on" -o utmp.grib curl "${GFS_URL}&var_VGRD=on" -o vtmp.grib grib_set -r -s packingType=grid_simple utmp.grib utmp_out.grib grib_set -r -s packingType=grid_simple vtmp.grib vtmp_out.grib printf "{\"u\":`grib_dump -j utmp_out.grib`,\"v\":`grib_dump -j vtmp_out.grib`}" > tmp.json rm utmp.grib vtmp.grib utmp_out.grib vtmp_out.grib DIR=`dirname $0` node ${DIR}/prepare.js ${1}/${GFS_DATE}${GFS_TIME}prepare.js
const PNG = require('pngjs').PNG; const fs = require('fs'); const data = JSON.parse(fs.readFileSync('tmp.json')); const name = process.argv[2]; const umessage = data.u.messages[0]; const vmessage = data.v.messages[0]; const unpack = (message) => message.reduce((acc, { key, value }) => ({ ...acc, [key]: value }), {}); const u = unpack(umessage); const v = unpack(vmessage); const width = u.Ni; const height = u.Nj; console.log(`Width: ${width}, Height: ${height}`); console.log(`u: min=${u.minimum}, max=${u.maximum}`); console.log(`v: min=${v.minimum}, max=${v.maximum}`); console.log(`u values length: ${u.values.length}, v values length: ${v.values.length}`); const png = new PNG({ colorType: 2, filterType: 4, width: width, height: height }); for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const i = (y * width + x) * 4; const k = y * width + x; if (k < 0 || k >= u.values.length || k >= v.values.length) { console.error(`Index out of bounds at (x: ${x}, y: ${y}) -> k: ${k}`); continue; } const uValue = u.values[k]; const vValue = v.values[k]; if (isNaN(uValue) || isNaN(vValue)) { console.error(`Invalid value at (x: ${x}, y: ${y}) -> u: ${uValue}, v: ${vValue}`); continue; } png.data[i + 0] = Math.floor(255 * (uValue - u.minimum) / (u.maximum - u.minimum)); png.data[i + 1] = Math.floor(255 * (vValue - v.minimum) / (v.maximum - v.minimum)); png.data[i + 2] = 0; png.data[i + 3] = 255; } } png.pack().pipe(fs.createWriteStream(`/Users/user1/Downloads/build/${name}.png`)); fs.writeFileSync(`/Users/user1/Downloads/build/${name}.json`, JSON.stringify({ source: 'http://nomads.ncep.noaa.gov', date: formatDate(u.dataDate + '', u.dataTime), width: width, height: height, uMin: u.minimum, uMax: u.maximum, vMin: v.minimum, vMax: v.maximum }, null, 2) + '\n'); function formatDate(dateStr, timeStr) { const year = dateStr.slice(0, 4); const month = dateStr.slice(4, 6); const day = dateStr.slice(6, 8); const hour = String(timeStr).padStart(4, '0'); return `${year}-${month}-${day}T${hour.slice(0, 2)}:00:00Z`; }