Source: WindDataProvider.js

/**
 * Provides Wind Data Vectors from JSON file
 *
 * @alias WindDataProvider
 * @constructor
 *
 * @param {String} json The wind data file.
 */
function WindDataProvider(json) {
    this._ready = false;
    this._jsonFile = json;

    var that = this;

    this.createBuilder().then(function(result){
        that.createGrid(result);
    });

    this._readyPromise = Cesium.when.defer();
}

Cesium.defineProperties(WindDataProvider.prototype, {

        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof WindDataProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        ready : {
            get : function() {
                return this._ready;
            }
        },
        /**
         * Gets a promise that resolves to true when the provider is ready for use.
         * @memberof WindDataProvider.prototype
         * @type {Promise.<Boolean>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._readyPromise;
            }
        }
});

/**
 * Modulo calculation, that makes sure the returned value is between [0, n)
 * (Ref: https://github.com/cambecc/earth)
 * @private
 * @param {number} a number to be calculated
 * @param {number} n number for modulo calculation
 */
WindDataProvider.prototype.floorMod = function (a, n) {
    var f = a - n * Math.floor(a / n);
    // HACK: when a is extremely close to an n transition, f can be equal to n. This is bad because f must be
    //       within range [0, n). Check for this corner case. Example: a:=-1e-16, n:=10. What is the proper fix?
    return f === n ? 0 : f;
}

/**
 * Checks whether x is valid (not null and not undefined)
 * @private
 * @param x value to be checked
 */
WindDataProvider.prototype.isValue = function (x) {
    return x !== null && x !== undefined;
}

/**
 * Loads json for wind data
 * @private
 */
WindDataProvider.prototype.createBuilder = function() {
    var result=Cesium.when.defer();
    Cesium.loadJson(this._jsonFile).then(function(jsonData) {
        var uData = jsonData[0].data, vData = jsonData[1].data;
        result.resolve({
            header: jsonData[0].header,
            data: function(i) {
                return [uData[i], vData[i]];
            }
        });

    }, function() {
        console.log("Error loading json file: " + this._jsonFile);
    });
    return result;
}

/**
 * Interpolates vector bilinear for accessing wind data
 * (Ref: https://github.com/cambecc/earth)
 * @private
 * @return wind in u, v direction and length of (u,v)
 */
WindDataProvider.prototype.bilinearInterpolateVector = function(x, y, g00, g10, g01, g11) {
    var rx = (1 - x);
    var ry = (1 - y);
    var a = rx * ry,  b = x * ry,  c = rx * y,  d = x * y;
    var u = g00[0] * a + g10[0] * b + g01[0] * c + g11[0] * d;
    var v = g00[1] * a + g10[1] * b + g01[1] * c + g11[1] * d;
    return [u, v, Math.sqrt(u * u + v * v)];
}

/**
 * Returns the data source of a given header
 * (Ref: https://github.com/cambecc/earth)
 * @param {Object} header header of json file
 * @return {String} data source as String
 */
WindDataProvider.prototype.dataSource = function(header) {
    // noinspection FallthroughInSwitchStatementJS
    switch (header.center || header.centerName) {
        case -3:
            return "OSCAR / Earth & Space Research";
        case 7:
        case "US National Weather Service, National Centres for Environmental Prediction (NCEP)":
            return "GFS / NCEP / US National Weather Service";
        default:
            return header.centerName;
    }
}

/**
 * creates the grid and adds interpolate function
 * @private
 * @param {Object} builder Builder of a json file
 */
WindDataProvider.prototype.createGrid = function(builder) {
        // From https://github.com/cambecc/earth

        console.log("Creating grid...")

        var header = builder.header;
        var λ0 = header.lo1, φ0 = header.la1;  // the grid's origin (e.g., 0.0E, 90.0N)
        var Δλ = header.dx, Δφ = header.dy;    // distance between grid points (e.g., 2.5 deg lon, 2.5 deg lat)
        var ni = header.nx, nj = header.ny;    // number of grid points W-E and N-S (e.g., 144 x 73)
        var date = new Date(header.refTime);
        date.setHours(date.getHours() + header.forecastTime);

        // Scan mode 0 assumed. Longitude increases from λ0, and latitude decreases from φ0.
        // http://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_table3-4.shtml
        var grid = [], p = 0;
        var isContinuous = Math.floor(ni * Δλ) >= 360;
        for (var j = 0; j < nj; j++) {
            var row = [];
            for (var i = 0; i < ni; i++, p++) {
                row[i] = builder.data(p);
            }
            if (isContinuous) {
                // For wrapped grids, duplicate first column as last column to simplify interpolation logic
                row.push(row[0]);
            }
            grid[j] = row;
        }

        /**
         * Returns the wind data for a given point (including interpolation)
         * (Ref: https://github.com/cambecc/earth)
         * @param {number} λ longitude in degree
         * @param {number} φ latitude in degree (-90,90)
         * @return {list} list with u, v and length
         */
        this.interpolate = function(λ, φ) {
            //console.log("Interpolating:"+ λ +","+ φ);
            if (φ >= 90) φ =89.9;
            if (φ <= -90) φ =-89.9;
            var i = this.floorMod(λ - λ0, 360) / Δλ;  // calculate longitude index in wrapped range [0, 360)
            var j = (φ0 - φ) / Δφ;                 // calculate latitude index in direction +90 to -90

            //         1      2           After converting λ and φ to fractional grid indexes i and j, we find the
            //        fi  i   ci          four points "G" that enclose point (i, j). These points are at the four
            //         | =1.4 |           corners specified by the floor and ceiling of i and j. For example, given
            //      ---G--|---G--- fj 8   i = 1.4 and j = 8.3, the four surrounding grid points are (1, 8), (2, 8),
            //    j ___|_ .   |           (1, 9) and (2, 9).
            //  =8.3   |      |
            //      ---G------G--- cj 9   Note that for wrapped grids, the first column is duplicated as the last
            //         |      |           column, so the index ci can be used without taking a modulo.

            var fi = Math.floor(i), ci = fi + 1;
            var fj = Math.floor(j), cj = fj + 1;

            var row;
            if ((row = grid[fj])) {
                var g00 = row[fi];
                var g10 = row[ci];
                if (this.isValue(g00) && this.isValue(g10) && (row = grid[cj])) {
                    var g01 = row[fi];
                    var g11 = row[ci];
                    if (this.isValue(g01) && this.isValue(g11)) {
                        // All four points found, so interpolate the value.
                        return this.bilinearInterpolateVector(i - fi, j - fj, g00, g10, g01, g11);
                    }
                }
            }
            console.log("cannot interpolate: " + λ + "," + φ + ": " + fi + " " + ci + " " + fj + " " + cj);
            return null;
        }
        this._source = this.dataSource(header);
        this._date = date;
        this.forEachPoint = function(cb) {
            for (var j = 0; j < nj; j++) {
                var row = grid[j] || [];
                for (var i = 0; i < ni; i++) {
                    cb(this.floorMod(180 + λ0 + i * Δλ, 360) - 180, φ0 - j * Δφ, row[i]);
                }
            }
        }

        console.log("Ready");
        this._ready = true;
        this._readyPromise.resolve(true);
    }