UNPKG

jeotiff

Version:
262 lines (234 loc) 8.67 kB
"use strict"; var globals = require("./globals.js"); var GeoTIFFImage = require("./geotiffimage.js"); var DataView64 = require("./dataview64.js"); var fieldTypes = globals.fieldTypes, fieldTagNames = globals.fieldTagNames, arrayFields = globals.arrayFields, geoKeyNames = globals.geoKeyNames; /** * The abstraction for a whole GeoTIFF file. * @constructor * @param {ArrayBuffer} rawData the raw data stream of the file as an ArrayBuffer. * @param {Object} [options] further options. * @param {Boolean} [options.cache=false] whether or not decoded tiles shall be cached. */ function GeoTIFF(rawData, options) { this.dataView = new DataView64(rawData); options = options || {}; this.cache = options.cache || false; var BOM = this.dataView.getUint16(0, 0); if (BOM === 0x4949) { this.littleEndian = true; } else if (BOM === 0x4D4D) { this.littleEndian = false; } else { throw new TypeError("Invalid byte order value."); } var magicNumber = this.dataView.getUint16(2, this.littleEndian); if (this.dataView.getUint16(2, this.littleEndian) === 42) { this.bigTiff = false; } else if (magicNumber === 43) { this.bigTiff = true; var offsetBytesize = this.dataView.getUint16(4, this.littleEndian); if (offsetBytesize !== 8) { throw new Error("Unsupported offset byte-size."); } } else { throw new TypeError("Invalid magic number."); } this.fileDirectories = this.parseFileDirectories( this.getOffset((this.bigTiff) ? 8 : 4) ); } GeoTIFF.prototype = { getOffset: function(offset) { if (this.bigTiff) { return this.dataView.getUint64(offset, this.littleEndian); } return this.dataView.getUint32(offset, this.littleEndian); }, getFieldTypeLength: function(fieldType) { switch (fieldType) { case fieldTypes.BYTE: case fieldTypes.ASCII: case fieldTypes.SBYTE: case fieldTypes.UNDEFINED: return 1; case fieldTypes.SHORT: case fieldTypes.SSHORT: return 2; case fieldTypes.LONG: case fieldTypes.SLONG: case fieldTypes.FLOAT: return 4; case fieldTypes.RATIONAL: case fieldTypes.SRATIONAL: case fieldTypes.DOUBLE: case fieldTypes.LONG8: case fieldTypes.SLONG8: case fieldTypes.IFD8: return 8; default: throw new RangeError("Invalid field type: " + fieldType); } }, getValues: function(fieldType, count, offset) { var values = null; var readMethod = null; var fieldTypeLength = this.getFieldTypeLength(fieldType); var i; switch (fieldType) { case fieldTypes.BYTE: case fieldTypes.ASCII: case fieldTypes.UNDEFINED: values = new Uint8Array(count); readMethod = this.dataView.getUint8; break; case fieldTypes.SBYTE: values = new Int8Array(count); readMethod = this.dataView.getInt8; break; case fieldTypes.SHORT: values = new Uint16Array(count); readMethod = this.dataView.getUint16; break; case fieldTypes.SSHORT: values = new Int16Array(count); readMethod = this.dataView.getInt16; break; case fieldTypes.LONG: values = new Uint32Array(count); readMethod = this.dataView.getUint32; break; case fieldTypes.SLONG: values = new Int32Array(count); readMethod = this.dataView.getInt32; break; case fieldTypes.LONG8: case fieldTypes.IFD8: values = new Array(count); readMethod = this.dataView.getUint64; break; case fieldTypes.SLONG8: values = new Array(count); readMethod = this.dataView.getInt64; break; case fieldTypes.RATIONAL: values = new Uint32Array(count*2); readMethod = this.dataView.getUint32; break; case fieldTypes.SRATIONAL: values = new Int32Array(count*2); readMethod = this.dataView.getInt32; break; case fieldTypes.FLOAT: values = new Float32Array(count); readMethod = this.dataView.getFloat32; break; case fieldTypes.DOUBLE: values = new Float64Array(count); readMethod = this.dataView.getFloat64; break; default: throw new RangeError("Invalid field type: " + fieldType); } // normal fields if (!(fieldType === fieldTypes.RATIONAL || fieldType === fieldTypes.SRATIONAL)) { for (i=0; i < count; ++i) { values[i] = readMethod.call( this.dataView, offset + (i*fieldTypeLength), this.littleEndian ); } } // RATIONAL or SRATIONAL else { for (i=0; i < count; i+=2) { values[i] = readMethod.call( this.dataView, offset + (i*fieldTypeLength), this.littleEndian ); values[i+1] = readMethod.call( this.dataView, offset + (i*fieldTypeLength + 4), this.littleEndian ); } } if (fieldType === fieldTypes.ASCII) { return String.fromCharCode.apply(null, values); } return values; }, getFieldValues: function(fieldTag, fieldType, typeCount, valueOffset) { var fieldValues; var fieldTypeLength = this.getFieldTypeLength(fieldType); if (fieldTypeLength * typeCount <= (this.bigTiff ? 8 : 4)) { fieldValues = this.getValues(fieldType, typeCount, valueOffset); } else { var actualOffset = this.getOffset(valueOffset); fieldValues = this.getValues(fieldType, typeCount, actualOffset); } if (typeCount === 1 && arrayFields.indexOf(fieldTag) === -1 && !(fieldType === fieldTypes.RATIONAL || fieldType === fieldTypes.SRATIONAL)) { return fieldValues[0]; } return fieldValues; }, parseGeoKeyDirectory: function(fileDirectory) { var rawGeoKeyDirectory = fileDirectory.GeoKeyDirectory; if (!rawGeoKeyDirectory) { return null; } var geoKeyDirectory = {}; for (var i = 4; i < rawGeoKeyDirectory[3] * 4; i += 4) { var key = geoKeyNames[rawGeoKeyDirectory[i]], location = (rawGeoKeyDirectory[i+1]) ? (fieldTagNames[rawGeoKeyDirectory[i+1]]) : null, count = rawGeoKeyDirectory[i+2], offset = rawGeoKeyDirectory[i+3]; var value = null; if (!location) { value = offset; } else { value = fileDirectory[location]; if (typeof value === "undefined" || value === null) { throw new Error("Could not get value of geoKey '" + key + "'."); } else if (typeof value === "string") { value = value.substring(offset, offset + count - 1); } else if (value.subarray) { value = value.subarray(offset, offset + count - 1); } } geoKeyDirectory[key] = value; } return geoKeyDirectory; }, parseFileDirectories: function(byteOffset) { var nextIFDByteOffset = byteOffset; var fileDirectories = []; while (nextIFDByteOffset !== 0x00000000) { var numDirEntries = this.bigTiff ? this.dataView.getUint64(nextIFDByteOffset, this.littleEndian) : this.dataView.getUint16(nextIFDByteOffset, this.littleEndian); var fileDirectory = {}; var i = nextIFDByteOffset + (this.bigTiff ? 8 : 2); for (var entryCount = 0; entryCount < numDirEntries; i += (this.bigTiff ? 20 : 12), ++entryCount) { var fieldTag = this.dataView.getUint16(i, this.littleEndian); var fieldType = this.dataView.getUint16(i + 2, this.littleEndian); var typeCount = this.bigTiff ? this.dataView.getUint64(i + 4, this.littleEndian): this.dataView.getUint32(i + 4, this.littleEndian); fileDirectory[fieldTagNames[fieldTag]] = this.getFieldValues( fieldTag, fieldType, typeCount, i + (this.bigTiff ? 12 : 8) ); } fileDirectories.push([ fileDirectory, this.parseGeoKeyDirectory(fileDirectory) ]); nextIFDByteOffset = this.getOffset(i); } return fileDirectories; }, /** * Get the n-th internal subfile a an image. By default, the first is returned. * * @param {Number} [index=0] the index of the image to return. * @returns {GeoTIFFImage} the image at the given index */ getImage: function(index) { index = index || 0; var fileDirectoryAndGeoKey = this.fileDirectories[index]; if (!fileDirectoryAndGeoKey) { throw new RangeError("Invalid image index"); } return new GeoTIFFImage(fileDirectoryAndGeoKey[0], fileDirectoryAndGeoKey[1], this.dataView, this.littleEndian, this.cache); }, /** * Returns the count of the internal subfiles. * * @returns {Number} the number of internal subfile images */ getImageCount: function() { return this.fileDirectories.length; } }; module.exports = GeoTIFF;