/**
* 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);
}