molstar
Version:
A comprehensive macromolecular library.
457 lines (456 loc) • 14.4 kB
JavaScript
/**
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Adapted from https://github.com/cheminfo-js/netcdfjs
* MIT License, Copyright (c) 2016 cheminfo
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { IOBuffer } from '../io-buffer';
/**
* Throws a non-valid NetCDF exception if the statement it's true
*/
function notNetcdf(statement, reason) {
if (statement) {
throw new TypeError('Not a valid NetCDF v3.x file: ' + reason);
}
}
/**
* Moves 1, 2, or 3 bytes to next 4-byte boundary
*/
function padding(buffer) {
if ((buffer.offset % 4) !== 0) {
buffer.skip(4 - (buffer.offset % 4));
}
}
/**
* Reads the name
*/
function readName(buffer) {
// Read name
var nameLength = buffer.readUint32();
var name = buffer.readChars(nameLength);
// validate name
// TODO
// Apply padding
padding(buffer);
return name;
}
var types = {
BYTE: 1,
CHAR: 2,
SHORT: 3,
INT: 4,
FLOAT: 5,
DOUBLE: 6
};
/**
* Parse a number into their respective type
*/
function num2str(type) {
switch (Number(type)) {
case types.BYTE:
return 'byte';
case types.CHAR:
return 'char';
case types.SHORT:
return 'short';
case types.INT:
return 'int';
case types.FLOAT:
return 'float';
case types.DOUBLE:
return 'double';
default:
return 'undefined';
}
}
/**
* Parse a number type identifier to his size in bytes
*/
function num2bytes(type) {
switch (Number(type)) {
case types.BYTE:
return 1;
case types.CHAR:
return 1;
case types.SHORT:
return 2;
case types.INT:
return 4;
case types.FLOAT:
return 4;
case types.DOUBLE:
return 8;
default:
return -1;
}
}
/**
* Reverse search of num2str
*/
function str2num(type) {
switch (String(type)) {
case 'byte':
return types.BYTE;
case 'char':
return types.CHAR;
case 'short':
return types.SHORT;
case 'int':
return types.INT;
case 'float':
return types.FLOAT;
case 'double':
return types.DOUBLE;
default:
return -1;
}
}
/**
* Auxiliary function to read numeric data
*/
function readNumber(size, bufferReader) {
if (size !== 1) {
var numbers = new Array(size);
for (var i = 0; i < size; i++) {
numbers[i] = bufferReader();
}
return numbers;
}
else {
return bufferReader();
}
}
/**
* Given a type and a size reads the next element
*/
function readType(buffer, type, size) {
switch (type) {
case types.BYTE:
return buffer.readBytes(size);
case types.CHAR:
return trimNull(buffer.readChars(size));
case types.SHORT:
return readNumber(size, buffer.readInt16.bind(buffer));
case types.INT:
return readNumber(size, buffer.readInt32.bind(buffer));
case types.FLOAT:
return readNumber(size, buffer.readFloat32.bind(buffer));
case types.DOUBLE:
return readNumber(size, buffer.readFloat64.bind(buffer));
default:
notNetcdf(true, 'non valid type ' + type);
return undefined;
}
}
/**
* Removes null terminate value
*/
function trimNull(value) {
if (value.charCodeAt(value.length - 1) === 0) {
return value.substring(0, value.length - 1);
}
return value;
}
// const STREAMING = 4294967295;
/**
* Read data for the given non-record variable
*/
function nonRecord(buffer, variable) {
// variable type
var type = str2num(variable.type);
// size of the data
var size = variable.size / num2bytes(type);
// iterates over the data
var data = new Array(size);
for (var i = 0; i < size; i++) {
data[i] = readType(buffer, type, 1);
}
return data;
}
/**
* Read data for the given record variable
*/
function record(buffer, variable, recordDimension) {
// variable type
var type = str2num(variable.type);
var width = variable.size ? variable.size / num2bytes(type) : 1;
// size of the data
// TODO streaming data
var size = recordDimension.length;
// iterates over the data
var data = new Array(size);
var step = recordDimension.recordStep;
for (var i = 0; i < size; i++) {
var currentOffset = buffer.offset;
data[i] = readType(buffer, type, width);
buffer.seek(currentOffset + step);
}
return data;
}
// Grammar constants
var ZERO = 0;
var NC_DIMENSION = 10;
var NC_VARIABLE = 11;
var NC_ATTRIBUTE = 12;
/**
* Read the header of the file
* Returns object with the fields:
* - `recordDimension`: Number with the length of record dimension
* - `dimensions`: List of dimensions
* - `globalAttributes`: List of global attributes
* - `variables`: List of variables
*/
function header(buffer, version) {
// Length of record dimension
// sum of the varSize's of all the record variables.
var header = { recordDimension: { length: buffer.readUint32() } };
// Version
header.version = version;
// List of dimensions
var dimList = dimensionsList(buffer);
header.recordDimension.id = dimList.recordId;
header.recordDimension.name = dimList.recordName;
header.dimensions = dimList.dimensions;
// List of global attributes
header.globalAttributes = attributesList(buffer);
// List of variables
var variables = variablesList(buffer, dimList.recordId, version);
header.variables = variables.variables;
header.recordDimension.recordStep = variables.recordStep;
return header;
}
/**
* List of dimensions
*/
function dimensionsList(buffer) {
var dimensions, recordId, recordName;
var dimList = buffer.readUint32();
if (dimList === ZERO) {
notNetcdf((buffer.readUint32() !== ZERO), 'wrong empty tag for list of dimensions');
return [];
}
else {
notNetcdf((dimList !== NC_DIMENSION), 'wrong tag for list of dimensions');
// Length of dimensions
var dimensionSize = buffer.readUint32();
dimensions = new Array(dimensionSize);
for (var dim = 0; dim < dimensionSize; dim++) {
// Read name
var name_1 = readName(buffer);
// Read dimension size
var size = buffer.readUint32();
if (size === 0) {
recordId = dim;
recordName = name_1;
}
dimensions[dim] = {
name: name_1,
size: size
};
}
return {
dimensions: dimensions,
recordId: recordId,
recordName: recordName
};
}
}
/**
* List of attributes
*/
function attributesList(buffer) {
var attributes;
var gAttList = buffer.readUint32();
if (gAttList === ZERO) {
notNetcdf((buffer.readUint32() !== ZERO), 'wrong empty tag for list of attributes');
return [];
}
else {
notNetcdf((gAttList !== NC_ATTRIBUTE), 'wrong tag for list of attributes');
// Length of attributes
var attributeSize = buffer.readUint32();
attributes = new Array(attributeSize);
for (var gAtt = 0; gAtt < attributeSize; gAtt++) {
// Read name
var name_2 = readName(buffer);
// Read type
var type = buffer.readUint32();
notNetcdf(((type < 1) || (type > 6)), 'non valid type ' + type);
// Read attribute
var size = buffer.readUint32();
var value = readType(buffer, type, size);
// Apply padding
padding(buffer);
attributes[gAtt] = {
name: name_2,
type: num2str(type),
value: value
};
}
}
return attributes;
}
/**
* List of variables
*/
function variablesList(buffer, recordId, version) {
var varList = buffer.readUint32();
var recordStep = 0;
var variables;
if (varList === ZERO) {
notNetcdf((buffer.readUint32() !== ZERO), 'wrong empty tag for list of variables');
return [];
}
else {
notNetcdf((varList !== NC_VARIABLE), 'wrong tag for list of variables');
// Length of variables
var variableSize = buffer.readUint32();
variables = new Array(variableSize);
for (var v = 0; v < variableSize; v++) {
// Read name
var name_3 = readName(buffer);
// Read dimensionality of the variable
var dimensionality = buffer.readUint32();
// Index into the list of dimensions
var dimensionsIds = new Array(dimensionality);
for (var dim = 0; dim < dimensionality; dim++) {
dimensionsIds[dim] = buffer.readUint32();
}
// Read variables size
var attributes = attributesList(buffer);
// Read type
var type = buffer.readUint32();
notNetcdf(((type < 1) && (type > 6)), 'non valid type ' + type);
// Read variable size
// The 32-bit varSize field is not large enough to contain the
// size of variables that require more than 2^32 - 4 bytes,
// so 2^32 - 1 is used in the varSize field for such variables.
var varSize = buffer.readUint32();
// Read offset
var offset = buffer.readUint32();
if (version === 2) {
notNetcdf((offset > 0), 'offsets larger than 4GB not supported');
offset = buffer.readUint32();
}
// Count amount of record variables
if (dimensionsIds[0] === recordId) {
recordStep += varSize;
}
variables[v] = {
name: name_3,
dimensions: dimensionsIds,
attributes: attributes,
type: num2str(type),
size: varSize,
offset: offset,
record: (dimensionsIds[0] === recordId)
};
}
}
return {
variables: variables,
recordStep: recordStep
};
}
/**
* Reads a NetCDF v3.x file
* https://www.unidata.ucar.edu/software/netcdf/docs/file_format_specifications.html
*/
var NetcdfReader = /** @class */ (function () {
function NetcdfReader(data) {
var buffer = new IOBuffer(data);
buffer.setBigEndian();
// Validate that it's a NetCDF file
notNetcdf((buffer.readChars(3) !== 'CDF'), 'should start with CDF');
// Check the NetCDF format
var version = buffer.readByte();
notNetcdf((version > 2), 'unknown version');
// Read the header
this.header = header(buffer, version);
this.buffer = buffer;
}
Object.defineProperty(NetcdfReader.prototype, "version", {
/**
* Version for the NetCDF format
*/
get: function () {
if (this.header.version === 1) {
return 'classic format';
}
else {
return '64-bit offset format';
}
},
enumerable: false,
configurable: true
});
Object.defineProperty(NetcdfReader.prototype, "recordDimension", {
get: function () {
return this.header.recordDimension;
},
enumerable: false,
configurable: true
});
Object.defineProperty(NetcdfReader.prototype, "dimensions", {
get: function () {
return this.header.dimensions;
},
enumerable: false,
configurable: true
});
Object.defineProperty(NetcdfReader.prototype, "globalAttributes", {
get: function () {
return this.header.globalAttributes;
},
enumerable: false,
configurable: true
});
Object.defineProperty(NetcdfReader.prototype, "variables", {
get: function () {
return this.header.variables;
},
enumerable: false,
configurable: true
});
/**
* Checks if a variable is available
* @param {string|object} variableName - Name of the variable to check
* @return {Boolean} - Variable existence
*/
NetcdfReader.prototype.hasDataVariable = function (variableName) {
return this.header.variables && this.header.variables.findIndex(function (val) { return val.name === variableName; }) !== -1;
};
/**
* Retrieves the data for a given variable
* @param {string|object} variableName - Name of the variable to search or variable object
* @return {Array} - List with the variable values
*/
NetcdfReader.prototype.getDataVariable = function (variableName) {
var _a;
var variable;
if (typeof variableName === 'string') {
// search the variable
variable = (_a = this.header.variables) === null || _a === void 0 ? void 0 : _a.find(function (val) { return val.name === variableName; });
}
else {
variable = variableName;
}
// throws if variable not found
if (variable === undefined)
throw new Error('variable not found');
// go to the offset position
this.buffer.seek(variable.offset);
if (variable.record) {
// record variable case
return record(this.buffer, variable, this.header.recordDimension);
}
else {
// non-record variable case
return nonRecord(this.buffer, variable);
}
};
return NetcdfReader;
}());
export { NetcdfReader };