s2-tools
Version:
A collection of geospatial tools primarily designed for WGS84, Web Mercator, and S2.
184 lines • 8.98 kB
JavaScript
// TEMPLATE INFO: https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_table3-1.shtml
import { grib2LookupTable32, grib2LookupTable33, grib2LookupTable34 } from './tables';
/**
* Returns a template generator for the given template number
* @param template - template number parse block
* @param section - byte block
* @returns Template generator
*/
export function getGrib2Template3(template, section) {
switch (template) {
case 0:
return grib2Template30(section);
default:
throw new Error(`Template 3.${template} not defined`);
}
}
// TODO: template -> '+proj=tmerc +lat_0=0 +lon_0=75 +k=1 +x_0=500000 +y_0=0 +ellps=IAU76 +units=m +no_defs +type=crs'
/**
* # GRIB2 - GRID DEFINITION TEMPLATE 3.0
*
* ## Latitude/Longitude (or equidistant cylindrical, or Plate Carree)
*
* ## Links
* - [Read more...](https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_temp3-0.shtml)
*
* ## Notes
* - Basic angle of the initial production domain and subdivisions of this basic angle are provided
* to manage cases where the recommended unit of 10-6 degrees is not applicable to describe the
* extreme longitudes and latitudes, and direction increments. For these last six descriptors, the
* unit is equal to the ratio of the basic angle and the subdivisions number. For ordinary cases,
* zero and missing values should be coded, equivalent to respective values of 1 and 106 (10-6
* degrees unit).
* - For data on a quasi-regular grid, in which all the rows or columns do not necessarily have the
* same number of grid points either Ni (octets 31-34) of Nj (octets 35-38) and the corresponding Di
* (octets 64-67) or Dj (octets 68-71) shall be coded with all bits set to 1 (missing). The actual
* number of points along each parallel or meridian shall be coded in the octets immediately following
* the grid definition template (octets [xx+1]-nn), as described in the description of the grid
* definition section.
* - A quasi-regular grid is only defined for appropriate grid scanning modes. Either rows or columns,
* but not both simultaneously, may have variable numbers of points or variable spacing. The first
* point in each row (column) shall be positioned at the meridian (parallel) indicted by octets 47-54.
* The grid points shall be evenly spaced in latitude (longitude).
* A scale value of radius of spherical Earth, or major axis of oblate spheroid Earth is delivered
* from applying appropriate scale factor to the value expressed in meters.
* - It is recommended to use unsigned direction increments.
* - In most cases, multiplying Ni (octets 31-34) by Nj (octets 35-38) yields the total number of
* points in the grid. However, this may not be true if bit 8 of the scanning mode flags (octet 72)
* is set to 1.
* @param section - byte block for template 3.0
* @returns - The parsed template
*/
export function grib2Template30(section) {
const shape = section.getUint8(14);
const basicAngle = section.getUint32(38);
const subdivisions = section.getUint32(42);
const lat1 = section.getInt32(46);
const lat2 = section.getInt32(55);
// build resolution values
const resolutionCode = section.getUint8(54);
// Bit #3 from the left is (resolution >> (8 - 3)) & 1 == (resolution >> 5) & 1
// But commonly people do the simpler approach: "bit #3" means shifting by 2 if reading docs carefully.
// Let’s do it systematically to avoid confusion:
const bit3 = (resolutionCode >> (8 - 3)) & 0x1; // i increments
const bit4 = (resolutionCode >> (8 - 4)) & 0x1; // j increments
const bit5 = (resolutionCode >> (8 - 5)) & 0x1; // vector resolution approach
// build scanMode values
const scanModeCode = section.getUint8(71);
const ratio = basicAngle === 0 ? 1e-6 : basicAngle / subdivisions;
return {
/** Shape of Earth [Table 3.2](https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_table3-2.shtml) */
shape: {
code: shape,
value: grib2LookupTable32[shape],
},
/** Scale Factor of radius of spherical Earth */
radiusScaleFactor: section.getUint8(15),
/** Scale value of radius of spherical Earth */
radiusScaleValue: section.getUint32(16),
/** Scale factor of major axis of oblate spheroid Earth */
majorAxisScaleFactor: section.getUint8(20),
/** Scale value of major axis of oblate spheroid Earth */
majorAxisScaleValue: section.getUint32(21),
/** Scale factor of minor axis of oblate spheroid Earth */
minorAxisScaleFactor: section.getUint8(25),
/** Scale value of minor axis of oblate spheroid Earth */
minorAxisScaleValue: section.getUint32(26),
/** Number of points along a parallel (W-E) */
nx: section.getUint32(30),
/** Number of points along a meridian (N-S) */
ny: section.getUint32(34),
/** Basic angle of the initial production domain */
basicAngle,
/** Subdivisions of basic angle used to define extreme longitudes and latitudes, and direction increments */
subdivisions,
/** Latitude of first grid point */
lat1: (lat1 < 0 ? -(lat1 ^ 0x80000000) : lat1) * ratio,
/** Longitude of first grid point */
lon1: section.getInt32(50) * ratio,
/** Resolution and component flags [Table 3.3](https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_table3-3.shtml) */
resolution: {
code: resolutionCode,
value: {
iDirectionIncrements: { code: bit3, value: grib2LookupTable33[3][bit3] },
jDirectionIncrements: { code: bit4, value: grib2LookupTable33[4][bit4] },
vectorComponentResolution: { code: bit5, value: grib2LookupTable33[5][bit5] },
},
},
/** Latitude of last grid point */
lat2: (lat2 < 0 ? -(lat2 ^ 0x80000000) : lat2) * ratio,
/** Longitude of last grid point */
lon2: section.getInt32(59) * ratio,
/** i direction increment */
dx: section.getInt32(63) * ratio,
/** j direction increment */
dy: section.getInt32(67) * ratio,
/** Scanning mode [Table 3.4](https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_table3-4.shtml) */
scanMode: {
code: scanModeCode,
value: parseScanMode(scanModeCode),
},
/** Grid Units */
gridUnits: 'degrees',
/**
* Convert this section into grid data
* @param transformer - projection transformer
* @returns - grid data
*/
buildGrid: function (transformer) {
// for now let's just follow the most basic scan mode
const { lat1, lat2, lon1, lon2, nx, ny } = this;
// Step sizes for interpolation
const lonStep = (lon2 - lon1) / (nx - 1);
const latStep = (lat2 - lat1) / (ny - 1);
const res = [];
for (let y = 0; y < ny; y++) {
for (let x = 0; x < nx; x++) {
// Interpolate longitude and latitude
const lon = lon1 + x * lonStep;
const lat = lat1 + y * latStep;
// create point
let point = { x: lon, y: lat, m: {} };
// apply transform if provided
if (transformer !== undefined)
point = transformer.forward(point);
res.push(point);
}
}
return res;
},
};
}
/**
* Get all scan mode values describing how to read the data
* @param scanMode - scan mode code
* @returns - parsed scan mode
*/
function parseScanMode(scanMode) {
/**
* For bits #1..8, shift to put that bit in LSB position and mask.
* GRIB2 docs say "Bit 1" is the leftmost bit, so bit #1 is (scanMode >> (8 - 1)) & 1, etc.
* @param bitPos - bit position not index
* @returns - binary value
*/
const getBit = (bitPos) => (scanMode >> (8 - bitPos)) & 0x1;
const bit1 = getBit(1);
const bit2 = getBit(2);
const bit3 = getBit(3);
const bit4 = getBit(4);
const bit5 = getBit(5);
const bit6 = getBit(6);
const bit7 = getBit(7);
const bit8 = getBit(8);
return {
xDir: { code: bit1, value: grib2LookupTable34[1][bit1] },
yDir: { code: bit2, value: grib2LookupTable34[2][bit2] },
adjacentDir: { code: bit3, value: grib2LookupTable34[3][bit3] },
rowDir: { code: bit4, value: grib2LookupTable34[4][bit4] },
rowOffset: { code: bit5, value: grib2LookupTable34[5][bit5] },
pointOffsetX: { code: bit6, value: grib2LookupTable34[6][bit6] },
pointOffsetY: { code: bit7, value: grib2LookupTable34[7][bit7] },
rowGridRule: { code: bit8, value: grib2LookupTable34[8][bit8] },
};
}
//# sourceMappingURL=templates.js.map