grib-js
Version:
Full JavaScript GRIB2 parser
641 lines (574 loc) • 22.1 kB
JavaScript
var tables = require('./tables');
var BinaryDataView = require('./BinaryDataView');
exports.parseDataView = function(dataView) {
var msgs = [], offset,
binary = new BinaryDataView(dataView),
indicator, sections;
// GRIB messages start with 'GRIB'
for(offset=0; offset <= dataView.byteLength-4; offset++) {
if(binary.read('uint32', offset) == 0x47524942) {
// This is the start of a GRIB message, parse the indicator section
binary.seek(offset);
indicator = parseIndicator(binary);
// FIXME: support >4GiB GRIBS
if(indicator.byteLength.hi !== 0) {
throw new Error('GRIB messages longer than 4GiB are not yet supported');
}
// If we found a message, parse it into sections and add to messages
sections = [indicator, ...parseSections(binary, indicator)];
msgs.push(new Grib2Message(sections));
// Move to last byte of message (the offset++ in the for loop will move us to next one)
offset += indicator.byteLength.lo - 1;
}
}
return msgs;
}
var sectionKeys = [
'indicator', 'identification', 'localUse',
'grid', 'product', 'representation', 'bitMap',
'data',
];
// An object representing a GRIB message
var Grib2Message = function(sections) {
var currentField = { }, sectionIdx, section, key;
this.fields = [];
for(sectionIdx in sections) {
section = sections[sectionIdx];
// Is this the identification?
if(section.number == 1) {
// Move fields from identification to this object.
for(key in section.contents) { this[key] = section.contents[key]; }
} else {
// Otherwise, add to the current field
currentField[sectionKeys[section.number]] = section.contents;
}
if(section.number == 7) {
// This is the data section, the last one in a repeated set
this.fields.push(new Field(currentField));
}
}
}
var Field = function(sections) {
var key;
// Move fields from sections to this object
for(key in sections) { this[key] = sections[key]; }
}
// Parse a GRIB 2 Indicator (0) section
var parseIndicator = function(binary) {
var rv = {};
rv.magic = binary.read('uint32');
if(rv.magic != 0x47524942) { throw new Error('Invalid magic number for indicator: ' + rv.magic); }
rv.reserved = binary.readArray('uint8', 2);
rv.discipline = binary.read('uint8')
rv.edition = binary.read('uint8');
if(rv.edition != 2) { throw new Error('Unknown GRIB edition: ' + rv.edition); }
rv.byteLength = binary.read('uint64');
// for display
rv.number = 0;
rv.contents = {
length: rv.byteLength.lo,
edition: rv.edition,
discipline: tables.lookup(
tables.tables.Discipline,
rv.discipline
),
}
return rv;
};
// Parse sections until end marker
var parseSections = function(binary, indicator) {
var rv = [], section;
// While we're not at a section 8 marker
while(binary.read('uint32', binary.tell()) != 0x37373737) {
// Parse a section
section = parseSection(binary, indicator, rv);
rv.push(section);
}
// Seek past the '7777' marker
binary.skip(4);
return rv;
}
// Parse a single section and advance binary to the section's end
var parseSection = function(binary, indicator, sections) {
var section = {}, startOffset = binary.tell(),
sectionParseFunc;
section.byteLength = binary.read('uint32'),
section.number = binary.read('uint8');
// Do we have a parse function for this section?
sectionParseFunc = sectionParsers[section.number];
if(!sectionParseFunc) {
// ... no, copy bytes verbatim
section.contents = binary.readBlob(section.byteLength-5);
} else {
// ... yes, make use of it
if (7 == section.number) {
section.contents = sectionParseFunc(binary, sections[3]);
} else {
section.contents = sectionParseFunc(binary, section.byteLength, indicator);
}
}
// Seek to end of section
binary.seek(startOffset + section.byteLength);
return section;
}
var sectionParsers = {
1: function(binary) {
var rv = {};
rv.originatingCenter =tables.lookup(
tables.tables.Center,
binary.read('uint16')
);
rv.originatingSubCenter = binary.read('uint16');
rv.masterTablesVersion = binary.read('uint8');
rv.localTablesVersion = binary.read('uint8');
rv.referenceTimeSignificance = tables.lookup(
tables.tables.ReferenceTimeSignificance,
binary.read('uint8'));
rv.referenceTime = new Date(
binary.read('uint16'), // year
binary.read('uint8') - 1, // month
binary.read('uint8'), // day
binary.read('uint8'), // hour
binary.read('uint8'), // minute
binary.read('uint8') // second
);
rv.productionStatus = tables.lookup(
tables.tables.ProductionStatus,
binary.read('uint8'));
rv.type = tables.lookup(
tables.tables.Type,
binary.read('uint8'));
return rv;
},
3: function(binary) {
var rv = {};
rv.source = binary.read('uint8');
rv.dataPointCount = binary.read('uint32');
rv.pointCountOctets = binary.read('uint8');
rv.pointCountInterpretation = binary.read('uint8');
rv.templateNumber = binary.read('uint16');
// Parse grid definition
var gridDefnParser = gridParsers[rv.templateNumber];
if(!gridDefnParser) {
console.warn('Unknown grid definiton template: ' + rv.templateNumber);
return rv;
}
rv.definition = gridDefnParser(binary);
// FIXME: point counts
return rv;
},
4: function(binary, byteLength, indicator) { // product
var rv = {}
rv.coordinateValues = binary.read('uint16');
rv.templateNumber = tables.lookup(
tables.tables.ProductDefinition,
binary.read('uint16')
);
if (dataProductTemplateParsers[rv.templateNumber.value]) {
rv.details = dataProductTemplateParsers[rv.templateNumber.value](binary, indicator);
}
else
{
// unknown.
rv.data = binary.readBlob(byteLength-5-4-2);
}
return rv;
},
5: function(binary, byteLength) { // representation
var rv = {}
rv.dataPointCount = binary.read('uint32');
rv.templateNumber = tables.lookup(
tables.tables.DataRepresentationTemplateNumber,
binary.read('uint16'));
if(dataRepresentationTemplateParsers[rv.templateNumber.value]) {
rv.details = dataRepresentationTemplateParsers[rv.templateNumber.value](binary);
}
else
{
// unknown.
rv.data = binary.readBlob(byteLength-5-4-2);
}
return rv;
},
6: function(binary, byteLength, indicator, section5) { // bitMap
var rv = {};
rv.indicator = tables.lookup(
tables.tables.BitMapIndicator,
binary.read('uint8'));
if(rv.indicator == 0) {
// has bitmap data.
rv.data = "NOT IMPLEMENTED. YOUR DATA IS HERE";
// will look something like:
///rv.data = binary.read(['blob', byteLength-5-1]);
}
return rv;
},
7: function(binary, section5) { // data
// Complex packing and spatial differencing
if (3 == section5.contents.templateNumber.value) {
// values from section 5
const mvm = section5.contents.details.missingValueManagementUsed.value
const R = section5.contents.details.referenceValue
const DD = 10**section5.contents.details.decimalScaleFactor
const EE = 2**section5.contents.details.binaryScaleFactor
const ref_val = R / DD
const NG = section5.contents.details.numberOfGroupsOfDataValues
const orderSpatial = section5.contents.details.orderOfSpatialDifferencing.value
const descriptorSpatial = section5.contents.details.numberOfOctetsExtraDescriptors
const nb = section5.contents.details.bitsPerValue
const nbgw = section5.contents.details.numberOfBitsUsedForTheGroupWidths
const nbsgl = section5.contents.details.numberOfBitsForScaledGroupLengths
const len_inc = section5.contents.details.lengthIncrementForTheGroupLengths
const len_last = section5.contents.details.trueLengthOfLastGroup
const totalNPoints = section5.contents.dataPointCount
const referenceGroupWidths = section5.contents.details.referenceForGroupWidths
const referenceGroupLength = section5.contents.details.referenceForGroupLengths
// [6-ww] 1st values of undifferenced scaled values and minimums
let gribSigned = 'grib8';
if (2 == descriptorSpatial) {
gribSigned = 'grib16'
}
const ival1 = binary.read(gribSigned);
const ival2 = binary.read(gribSigned);
const minsd = binary.read(gribSigned);
// [ww +1]-xx Get reference values for groups (X1's)
let X1 = []
for (let i = 0; i < NG; i++) {
X1.push(binary.view.getUnsigned(nb))
}
binary.incrByte();
// [xx +1 ]-yy Get number of bits used to encode each group
let NB = []
for (let i = 0; i < NG; i++) {
NB.push(binary.view.getUnsigned(nbgw))
}
for (let i = 0; i < NG; i++) {
NB[i] += referenceGroupWidths;
}
binary.incrByte();
// [yy +1 ]-zz Get the scaled group lengths using formula
// Ln = ref + Kn * len_inc, where n = 1-NG,
// ref = referenceGroupLength, and len_inc = lengthIncrement
let L = []
for (let i = 0; i < NG; i++) {
L.push(binary.view.getUnsigned(nbsgl))
}
binary.incrByte();
let totalL = 0;
for (let i = 0; i < NG; i++) {
L[i] = L[i] * len_inc + referenceGroupLength
totalL += L[i];
}
totalL -= L[NG - 1];
totalL += len_last
L[NG - 1] = len_last
if (totalL != totalNPoints) {
return
}
// [zz +1 ]-nn get X2 values and calculate the results Y using formula
// formula used to create values, Y * 10**D = R + (X1 + X2) * 2**E
// Y = (R + (X1 + X2) * (2 ** E) ) / (10 ** D)]
// WHERE:
// Y = THE VALUE WE ARE UNPACKING
// R = THE REFERENCE VALUE (FIRST ORDER MINIMA)
// X1 = THE PACKED VALUE
// X2 = THE SECOND ORDER MINIMA
// E = THE BINARY SCALE FACTOR
// D = THE DECIMAL SCALE FACTOR
let count = 0;
let data = [];
for (let i = 0; i < NG; i++) {
if (NB[i] != 0) {
for (let j = 0; j < L[i]; j++) {
data[count++] = binary.view.getUnsigned(NB[i]) + X1[i];
}
} else {
for (let j = 0; j < L[i]; j++) {
data[count++] = X1[i];
}
}
} // end for i
binary.incrByte();
data[0] = ival1;
data[1] = ival2;
let itemp;
itemp = totalNPoints;
for (let i = 2; i < itemp; i++) {
data[i] += minsd;
data[i] = data[i] + 2 * data[i - 1] - data[i - 2];
}
// formula used to create values, Y * 10**D = R + (X1 + X2) * 2**E
// Y = (R + (X1 + X2) * (2 ** E) ) / (10 ** D)]
// WHERE:
// Y = THE VALUE WE ARE UNPACKING
// R = THE REFERENCE VALUE (FIRST ORDER MINIMA)
// X1 = THE PACKED VALUE
// X2 = THE SECOND ORDER MINIMA
// E = THE BINARY SCALE FACTOR
// D = THE DECIMAL SCALE FACTOR
for (let i = 0; i < data.length; i++) {
data[i] = (R + (data[i] * EE)) / DD;
}
return data
}
}
};
// Parser for scale factor / scaled value. See 92.1.12
var parseScaledValue = function(binary) {
var scale = binary.read('uint8'), value = binary.read('uint32');
return value * Math.pow(10, -scale);
}
// Parser for basic angle
var parseBasicAngle = function(binary) {
var basicAngle = binary.read('uint32'), basicAngleSub = binary.read('uint32');
basicAngle = ((basicAngle == 0) || (basicAngle == 0xffffffff)) ? 1 : basicAngle;
basicAngleSub = ((basicAngleSub == 0) || (basicAngleSub == 0xffffffff)) ? 1e6 : basicAngleSub;
return basicAngle / basicAngleSub;
}
var dataRepresentationTemplateParsers = {
// Grid point data - simple packing
0 : function(binary) {
var rv = {}
rv.name = "Grid point data - simple packing";
rv.referenceValue = binary.read('float32');
rv.binaryScaleFactor = binary.read('int16');
rv.decimalScaleFactor = binary.read('int16');
rv.numberOfBitsUsed = binary.read('uint8');
rv.originalType = binary.read('uint8');
return rv;
},
// Grid point data - complex packing
3 : function(binary) {
var rv = {}
rv.name = "Grid point data - complex packing and spatial differencing";
rv.referenceValue = binary.read('float32');
rv.binaryScaleFactor = binary.read('int16');
rv.decimalScaleFactor = binary.read('int16');
rv.bitsPerValue = binary.read('uint8');
rv.typeOfOriginalField = tables.lookup(
tables.tables.DataRepresentationFiedValueType,
binary.read('uint8')
)
rv.groupSplittingMethodUsed = tables.lookup(
tables.tables.DataRepresentationGroupSplitting,
binary.read('uint8')
)
rv.missingValueManagementUsed = tables.lookup(
tables.tables.DataRepresentationMissingValueManagement,
binary.read('uint8')
)
rv.primaryMissingValueSubstitute = binary.read('uint32')
rv.secondaryMissingValueSubstitute = binary.read('uint32')
rv.numberOfGroupsOfDataValues = binary.read('uint32')
rv.referenceForGroupWidths = binary.read('uint8')
rv.numberOfBitsUsedForTheGroupWidths = binary.read('uint8')
rv.referenceForGroupLengths = binary.read('uint32')
rv.lengthIncrementForTheGroupLengths = binary.read('uint8')
rv.trueLengthOfLastGroup = binary.read('uint32')
rv.numberOfBitsForScaledGroupLengths = binary.read('uint8')
rv.orderOfSpatialDifferencing = tables.lookup(
tables.tables.DataRepresentationOrder,
binary.read('uint8')
)
rv.numberOfOctetsExtraDescriptors = binary.read('uint8')
return rv;
}
}
var dataProductTemplateParsers = {
// Analysis or forecast at a horizontal level or in
// a horizontal layer at a point in time
0 : function(binary, indicator) {
var rv = {}
var category = categoryByDiscipline[indicator.contents.discipline.value];
if (category) {
rv.category = category(binary);
}
else {
rv.category = { value: binary.read('int8') };
}
category = parameterByCategory[indicator.contents.discipline.value];
if (category) {
var parameter = category[rv.category.value];
if (parameter) {
rv.parameter = parameter(binary)
}
}
if (!rv.hasOwnProperty("parameter")) {
rv.parameter = { value: binary.read('int8') };
}
rv.generatingProcessType = tables.lookup(
tables.tables.GeneratingProcessType,
binary.read('uint8')
)
rv.backgroundGeneratingProcess = binary.read('uint8')
rv.generatingProcessIdentified = tables.lookup(
tables.tables.GeneratingProcessOrModelFromCenter7,
binary.read('uint8')
)
rv.hours = binary.read('uint16')
rv.minutes = binary.read('uint8')
rv.timeRange = tables.lookup(
tables.tables.TimeRange,
binary.read('uint8')
)
rv.forecastTime = binary.read('uint32')
rv.surfaceType = tables.lookup(
tables.tables.SurfaceType,
binary.read('uint8')
)
rv.surfaceTypeScale = binary.read('uint8')
rv.surfaceTypeValue = binary.read('uint32')
rv.secondSurfaceType = tables.lookup(
tables.tables.SurfaceType,
binary.read('uint8')
)
rv.secondSurfaceTypeScale = binary.read('uint8')
rv.secondSurfaceTypeValue = binary.read('uint32')
return rv;
}
}
const categoryByDiscipline = {
0 : (binary) => tables.lookup(
tables.tables.MeteorologicalProducts,
binary.read('int8')
)
}
const parameterByCategory = {
0 : {
2 :(binary) => tables.lookup(
tables.tables.MeteorologicalProductsMomentumParameter,
binary.read('int8')
),
6 :(binary) => tables.lookup(
tables.tables.MeteorologicalProductsCloudsCategory,
binary.read('int8')
),
}
}
var gridParsers = {
// Grid Definition Template 3.0: Latitude/longitude (or equidistant cylindrical, or Plate Carree)
0: function(binary) { var rv = {}
rv.name = 'Latitude/longitude (or equidistant cylindrical, or Plate Carree)';
rv.earthShape = tables.lookup(tables.tables.EarthShape, binary.read('uint8'));
rv.sphericalRadius = parseScaledValue(binary);
rv.majorAxis = parseScaledValue(binary);
rv.minorAxis = parseScaledValue(binary);
rv.ni = binary.read('uint32'); rv.nj = binary.read('uint32');
rv.basicAngle = parseBasicAngle(binary);
rv.la1 = binary.read('grib32'); rv.lo1 = binary.read('grib32');
rv.resolutionAndComponentFlags = binary.read('uint8');
rv.la2 = binary.read('grib32'); rv.lo2 = binary.read('grib32');
rv.di = binary.read('int32'); rv.dj = binary.read('int32');
rv.scanningMode = binary.read('uint8');
var scale = rv.basicAngle;
rv.la1 *= scale; rv.lo1 *= scale;
rv.la2 *= scale; rv.lo2 *= scale;
rv.di *= scale; rv.dj *= scale;
return rv;
},
// Grid Definition Template 3.10: Mercator
10: function(binary) { var rv={};
rv.name = 'Mercator';
rv.earthShape = tables.lookup(tables.tables.EarthShape, binary.read('uint8'));
rv.sphericalRadius = parseScaledValue(binary);
rv.majorAxis = parseScaledValue(binary);
rv.minorAxis = parseScaledValue(binary);
rv.ni = binary.read('uint32'); rv.nj = binary.read('int32');
rv.la1 = binary.read('int32'); rv.lo1 = binary.read('int32');
rv.resolutionAndComponentFlags = binary.read('uint8');
rv.lad = binary.read('int32');
rv.la2 = binary.read('int32'); rv.lo2 = binary.read('int32');
rv.scanningMode = binary.read('uint8');
rv.gridOrientation = binary.read('uint32');
rv.di = binary.read('int32'); rv.dj = binary.read('int32');
var scale = 1e-6;
rv.la1 *= scale; rv.lo1 *= scale;
rv.lad *= scale;
rv.la2 *= scale; rv.lo2 *= scale;
return rv;
},
// Grid Definition Template 3.20: Polar stereographic projection
20: function(binary) { var rv={};
rv.name = 'Polar stereographic projection';
rv.earthShape = tables.lookup(tables.tables.EarthShape, binary.read('uint8'));
rv.sphericalRadius = parseScaledValue(binary);
rv.majorAxis = parseScaledValue(binary);
rv.minorAxis = parseScaledValue(binary);
rv.nx = binary.read('uint32'); rv.ny = binary.read('uint32');
rv.la1 = binary.read('int32'); rv.lo1 = binary.read('int32');
rv.resolutionAndComponentFlags = binary.read('uint8');
rv.lad = binary.read('int32'); rv.lov = binary.read('int32');
rv.dx = binary.read('int32'); rv.dy = binary.read('int32');
rv.projectionCenter = binary.read('uint8');
rv.scanningMode = binary.read('uint8');
var scale = 1e-6;
rv.la1 *= scale; rv.lo1 *= scale;
rv.lad *= scale; rv.lov *= scale;
return rv;
},
// Grid Definition Template 3.30: Lambert conformal
30: function(binary) { var rv={};
rv.name = 'Polar stereographic projection';
rv.earthShape = tables.lookup(tables.tables.EarthShape, binary.read('uint8'));
rv.sphericalRadius = parseScaledValue(binary);
rv.majorAxis = parseScaledValue(binary);
rv.minorAxis = parseScaledValue(binary);
rv.nx = binary.read('uint32'); rv.ny = binary.read('uint32');
rv.la1 = binary.read('int32'); rv.lo1 = binary.read('int32');
rv.resolutionAndComponentFlags = binary.read('uint8');
rv.lad = binary.read('int32'); rv.lov = binary.read('int32');
rv.dx = binary.read('int32'); rv.dy = binary.read('int32');
rv.projectionCenter = binary.read('uint8');
rv.scanningMode = binary.read('uint8');
rv.latin1 = binary.read('uint32');rv.latin2 = binary.read('uint32');
rv.laSouthPole = binary.read('uint32');rv.loSouthPole = binary.read('uint32');
var scale = 1e-6;
rv.la1 *= scale; rv.lo1 *= scale;
rv.lad *= scale; rv.lov *= scale;
//rv.latin1 *= scale; rv.latin1 *= scale;
//rv.laSouthPole *= scale; rv.loSouthPole *= scale;
return rv;
},
// Grid Definition Template 3.40: Gaussian latitude/longitude
40: function(binary) { var rv={};
rv.name = 'Gaussian latitude/longitude';
rv.earthShape = tables.lookup(tables.tables.EarthShape, binary.read('uint8'));
rv.sphericalRadius = parseScaledValue(binary);
rv.majorAxis = parseScaledValue(binary);
rv.minorAxis = parseScaledValue(binary);
rv.earthShape = binary.read('uint8');
rv.ni = binary.read('uint32');rv.nj = binary.read('uint32');
rv.basicAngle = binary.read('uint32');
rv.la1 = binary.read('int32');rv.lo1 = binary.read('int32');
rv.resolutionAndComponentFlags = binary.read('uint8');
rv.la2 = binary.read('int32');rv.lo2 = binary.read('int32');
rv.di = binary.read('int32');
rv.n = binary.read('uint32');
rv.scanningMode = binary.read('uint8');
var basicAngle = ((rv.basicAngle == 0) || (rv.basicAngle == 0xffffffff)) ? 1 : rv.basicAngle;
var scale = 1e-6/basicAngle;
rv.la1 *= scale; rv.lo1 *= scale;
rv.la2 *= scale; rv.lo2 *= scale;
rv.di *= scale;
return rv;
},
// Grid Definition Template 3.90: Space view perspective or orthographic
// FIXME: implement properly
90: function(binary) { var rv={};
rv.name = 'Space view perspective or orthographic';
rv.earthShape = tables.lookup(tables.tables.EarthShape, binary.read('uint8'));
rv.sphericalRadius = parseScaledValue(binary);
rv.majorAxis = parseScaledValue(binary);
rv.minorAxis = parseScaledValue(binary);
rv.nx = binary.read('uint32');rv.ny = binary.read('uint32');
rv.basicAngle = binary.read('uint32');
rv.lap = binary.read('int32');rv.lop = binary.read('int32');
rv.resolutionAndComponentFlags = binary.read('uint8');
rv.dx = binary.read('uint32');rv.dy = binary.read('uint32');
rv.Xp = binary.read('uint32');rv.Yp = binary.read('uint32');
rv.scanningMode = binary.read('uint8');
var scale = 1e-6;
rv.lap *= scale; rv.lop *= scale;
return rv;
},
};