UNPKG

geotiff

Version:

GeoTIFF image decoding in JavaScript

936 lines (796 loc) 34.5 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); var _regenerator = require('babel-runtime/regenerator'); var _regenerator2 = _interopRequireDefault(_regenerator); var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _globals = require('./globals'); var _rgb = require('./rgb'); var _compression = require('./compression'); var _resample = require('./resample'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /* eslint max-len: ["error", { "code": 120 }] */ function sum(array, start, end) { var s = 0; for (var i = start; i < end; ++i) { s += array[i]; } return s; } function arrayForType(format, bitsPerSample, size) { switch (format) { case 1: // unsigned integer data switch (bitsPerSample) { case 8: return new Uint8Array(size); case 16: return new Uint16Array(size); case 32: return new Uint32Array(size); default: break; } break; case 2: // twos complement signed integer data switch (bitsPerSample) { case 8: return new Int8Array(size); case 16: return new Int16Array(size); case 32: return new Int32Array(size); default: break; } break; case 3: // floating point data switch (bitsPerSample) { case 32: return new Float32Array(size); case 64: return new Float64Array(size); default: break; } break; default: break; } throw Error('Unsupported data format/bitsPerSample'); } /** * GeoTIFF sub-file image. */ var GeoTIFFImage = function () { /** * @constructor * @param {Object} fileDirectory The parsed file directory * @param {Object} geoKeys The parsed geo-keys * @param {DataView} dataView The DataView for the underlying file. * @param {Boolean} littleEndian Whether the file is encoded in little or big endian * @param {Boolean} cache Whether or not decoded tiles shall be cached * @param {Source} source The datasource to read from */ function GeoTIFFImage(fileDirectory, geoKeys, dataView, littleEndian, cache, source) { (0, _classCallCheck3.default)(this, GeoTIFFImage); this.fileDirectory = fileDirectory; this.geoKeys = geoKeys; this.dataView = dataView; this.littleEndian = littleEndian; this.tiles = cache ? {} : null; this.isTiled = !fileDirectory.StripOffsets; var planarConfiguration = fileDirectory.PlanarConfiguration; this.planarConfiguration = typeof planarConfiguration === 'undefined' ? 1 : planarConfiguration; if (this.planarConfiguration !== 1 && this.planarConfiguration !== 2) { throw new Error('Invalid planar configuration.'); } this.source = source; } /** * Returns the associated parsed file directory. * @returns {Object} the parsed file directory */ (0, _createClass3.default)(GeoTIFFImage, [{ key: 'getFileDirectory', value: function getFileDirectory() { return this.fileDirectory; } /** * Returns the associated parsed geo keys. * @returns {Object} the parsed geo keys */ }, { key: 'getGeoKeys', value: function getGeoKeys() { return this.geoKeys; } /** * Returns the width of the image. * @returns {Number} the width of the image */ }, { key: 'getWidth', value: function getWidth() { return this.fileDirectory.ImageWidth; } /** * Returns the height of the image. * @returns {Number} the height of the image */ }, { key: 'getHeight', value: function getHeight() { return this.fileDirectory.ImageLength; } /** * Returns the number of samples per pixel. * @returns {Number} the number of samples per pixel */ }, { key: 'getSamplesPerPixel', value: function getSamplesPerPixel() { return this.fileDirectory.SamplesPerPixel; } /** * Returns the width of each tile. * @returns {Number} the width of each tile */ }, { key: 'getTileWidth', value: function getTileWidth() { return this.isTiled ? this.fileDirectory.TileWidth : this.getWidth(); } /** * Returns the height of each tile. * @returns {Number} the height of each tile */ }, { key: 'getTileHeight', value: function getTileHeight() { return this.isTiled ? this.fileDirectory.TileLength : this.fileDirectory.RowsPerStrip; } /** * Calculates the number of bytes for each pixel across all samples. Only full * bytes are supported, an exception is thrown when this is not the case. * @returns {Number} the bytes per pixel */ }, { key: 'getBytesPerPixel', value: function getBytesPerPixel() { var bitsPerSample = 0; for (var i = 0; i < this.fileDirectory.BitsPerSample.length; ++i) { var bits = this.fileDirectory.BitsPerSample[i]; if (bits % 8 !== 0) { throw new Error('Sample bit-width of ' + bits + ' is not supported.'); } else if (bits !== this.fileDirectory.BitsPerSample[0]) { throw new Error('Differing size of samples in a pixel are not supported.'); } bitsPerSample += bits; } return bitsPerSample / 8; } }, { key: 'getSampleByteSize', value: function getSampleByteSize(i) { if (i >= this.fileDirectory.BitsPerSample.length) { throw new RangeError('Sample index ' + i + ' is out of range.'); } var bits = this.fileDirectory.BitsPerSample[i]; if (bits % 8 !== 0) { throw new Error('Sample bit-width of ' + bits + ' is not supported.'); } return bits / 8; } }, { key: 'getReaderForSample', value: function getReaderForSample(sampleIndex) { var format = this.fileDirectory.SampleFormat ? this.fileDirectory.SampleFormat[sampleIndex] : 1; var bitsPerSample = this.fileDirectory.BitsPerSample[sampleIndex]; switch (format) { case 1: // unsigned integer data switch (bitsPerSample) { case 8: return DataView.prototype.getUint8; case 16: return DataView.prototype.getUint16; case 32: return DataView.prototype.getUint32; default: break; } break; case 2: // twos complement signed integer data switch (bitsPerSample) { case 8: return DataView.prototype.getInt8; case 16: return DataView.prototype.getInt16; case 32: return DataView.prototype.getInt32; default: break; } break; case 3: switch (bitsPerSample) { case 32: return DataView.prototype.getFloat32; case 64: return DataView.prototype.getFloat64; default: break; } break; default: break; } throw Error('Unsupported data format/bitsPerSample'); } }, { key: 'getArrayForSample', value: function getArrayForSample(sampleIndex, size) { var format = this.fileDirectory.SampleFormat ? this.fileDirectory.SampleFormat[sampleIndex] : 1; var bitsPerSample = this.fileDirectory.BitsPerSample[sampleIndex]; return arrayForType(format, bitsPerSample, size); } /** * Returns the decoded strip or tile. * @param {Number} x the strip or tile x-offset * @param {Number} y the tile y-offset (0 for stripped images) * @param {Number} sample the sample to get for separated samples * @param {Pool|AbstractDecoder} poolOrDecoder the decoder or decoder pool * @returns {Promise.<ArrayBuffer>} */ }, { key: 'getTileOrStrip', value: function () { var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(x, y, sample, poolOrDecoder) { var numTilesPerRow, numTilesPerCol, index, tiles, offset, byteCount, slice, request; return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: numTilesPerRow = Math.ceil(this.getWidth() / this.getTileWidth()); numTilesPerCol = Math.ceil(this.getHeight() / this.getTileHeight()); index = void 0; tiles = this.tiles; if (this.planarConfiguration === 1) { index = y * numTilesPerRow + x; } else if (this.planarConfiguration === 2) { index = sample * numTilesPerRow * numTilesPerCol + y * numTilesPerRow + x; } offset = void 0; byteCount = void 0; if (this.isTiled) { offset = this.fileDirectory.TileOffsets[index]; byteCount = this.fileDirectory.TileByteCounts[index]; } else { offset = this.fileDirectory.StripOffsets[index]; byteCount = this.fileDirectory.StripByteCounts[index]; } _context.next = 10; return this.source.fetch(offset, byteCount); case 10: slice = _context.sent; // either use the provided pool or decoder to decode the data request = void 0; if (tiles === null) { request = poolOrDecoder.decode(this.fileDirectory, slice); } else if (!tiles[index]) { request = poolOrDecoder.decode(this.fileDirectory, slice); tiles[index] = request; } _context.t0 = x; _context.t1 = y; _context.t2 = sample; _context.next = 18; return request; case 18: _context.t3 = _context.sent; return _context.abrupt('return', { x: _context.t0, y: _context.t1, sample: _context.t2, data: _context.t3 }); case 20: case 'end': return _context.stop(); } } }, _callee, this); })); function getTileOrStrip(_x, _x2, _x3, _x4) { return _ref.apply(this, arguments); } return getTileOrStrip; }() /** * Internal read function. * @private * @param {Array} imageWindow The image window in pixel coordinates * @param {Array} samples The selected samples (0-based indices) * @param {TypedArray[]|TypedArray} valueArrays The array(s) to write into * @param {Boolean} interleave Whether or not to write in an interleaved manner * @param {Pool} pool The decoder pool * @returns {Promise<TypedArray[]>|Promise<TypedArray>} */ }, { key: '_readRaster', value: function () { var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(imageWindow, samples, valueArrays, interleave, poolOrDecoder, width, height, resampleMethod) { var _this = this; var tileWidth, tileHeight, minXTile, maxXTile, minYTile, maxYTile, windowWidth, bytesPerPixel, srcSampleOffsets, sampleReaders, i, promises, littleEndian, yTile, xTile, _loop, sampleIndex, resampled; return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: tileWidth = this.getTileWidth(); tileHeight = this.getTileHeight(); minXTile = Math.max(Math.floor(imageWindow[0] / tileWidth), 0); maxXTile = Math.min(Math.ceil(imageWindow[2] / tileWidth), Math.ceil(this.getWidth() / this.getTileWidth())); minYTile = Math.max(Math.floor(imageWindow[1] / tileHeight), 0); maxYTile = Math.min(Math.ceil(imageWindow[3] / tileHeight), Math.ceil(this.getHeight() / this.getTileHeight())); windowWidth = imageWindow[2] - imageWindow[0]; bytesPerPixel = this.getBytesPerPixel(); srcSampleOffsets = []; sampleReaders = []; for (i = 0; i < samples.length; ++i) { if (this.planarConfiguration === 1) { srcSampleOffsets.push(sum(this.fileDirectory.BitsPerSample, 0, samples[i]) / 8); } else { srcSampleOffsets.push(0); } sampleReaders.push(this.getReaderForSample(samples[i])); } promises = []; littleEndian = this.littleEndian; for (yTile = minYTile; yTile < maxYTile; ++yTile) { for (xTile = minXTile; xTile < maxXTile; ++xTile) { _loop = function _loop(sampleIndex) { var si = sampleIndex; var sample = samples[sampleIndex]; if (_this.planarConfiguration === 2) { bytesPerPixel = _this.getSampleByteSize(sample); } var promise = _this.getTileOrStrip(xTile, yTile, sample, poolOrDecoder); promises.push(promise); promise.then(function (tile) { var buffer = tile.data; var dataView = new DataView(buffer); var firstLine = tile.y * tileHeight; var firstCol = tile.x * tileWidth; var lastLine = (tile.y + 1) * tileHeight; var lastCol = (tile.x + 1) * tileWidth; var reader = sampleReaders[si]; var ymax = Math.min(tileHeight, tileHeight - (lastLine - imageWindow[3])); var xmax = Math.min(tileWidth, tileWidth - (lastCol - imageWindow[2])); for (var y = Math.max(0, imageWindow[1] - firstLine); y < ymax; ++y) { for (var x = Math.max(0, imageWindow[0] - firstCol); x < xmax; ++x) { var pixelOffset = (y * tileWidth + x) * bytesPerPixel; var value = reader.call(dataView, pixelOffset + srcSampleOffsets[si], littleEndian); var windowCoordinate = void 0; if (interleave) { windowCoordinate = (y + firstLine - imageWindow[1]) * windowWidth * samples.length + (x + firstCol - imageWindow[0]) * samples.length + si; valueArrays[windowCoordinate] = value; } else { windowCoordinate = (y + firstLine - imageWindow[1]) * windowWidth + x + firstCol - imageWindow[0]; valueArrays[si][windowCoordinate] = value; } } } }); }; for (sampleIndex = 0; sampleIndex < samples.length; ++sampleIndex) { _loop(sampleIndex); } } } _context2.next = 16; return Promise.all(promises); case 16: if (!(width && imageWindow[2] - imageWindow[0] !== width || height && imageWindow[3] - imageWindow[1] !== height)) { _context2.next = 22; break; } resampled = void 0; if (interleave) { resampled = (0, _resample.resampleInterleaved)(valueArrays, imageWindow[2] - imageWindow[0], imageWindow[3] - imageWindow[1], width, height, samples.length, resampleMethod); } else { resampled = (0, _resample.resample)(valueArrays, imageWindow[2] - imageWindow[0], imageWindow[3] - imageWindow[1], width, height, resampleMethod); } resampled.width = width; resampled.height = height; return _context2.abrupt('return', resampled); case 22: valueArrays.width = width || imageWindow[2] - imageWindow[0]; valueArrays.height = height || imageWindow[3] - imageWindow[1]; return _context2.abrupt('return', valueArrays); case 25: case 'end': return _context2.stop(); } } }, _callee2, this); })); function _readRaster(_x5, _x6, _x7, _x8, _x9, _x10, _x11, _x12) { return _ref2.apply(this, arguments); } return _readRaster; }() /** * Reads raster data from the image. This function reads all selected samples * into separate arrays of the correct type for that sample or into a single * combined array when `interleave` is set. When provided, only a subset * of the raster is read for each sample. * * @param {Object} [options] optional parameters * @param {Array} [options.window=whole image] the subset to read data from. * @param {Array} [options.samples=all samples] the selection of samples to read from. * @param {Boolean} [options.interleave=false] whether the data shall be read * in one single array or separate * arrays. * @param {Number} [pool=null] The optional decoder pool to use. * @param {number} [width] The desired width of the output. When the width is no the * same as the images, resampling will be performed. * @param {number} [height] The desired height of the output. When the width is no the * same as the images, resampling will be performed. * @param {string} [resampleMethod='nearest'] The desired resampling method. * @param {number|number[]} [fillValue] The value to use for parts of the image * outside of the images extent. When multiple * samples are requested, an array of fill values * can be passed. * @returns {Promise.<(TypedArray|TypedArray[])>} the decoded arrays as a promise */ }, { key: 'readRasters', value: function () { var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3() { var _ref4 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, wnd = _ref4.window, _ref4$samples = _ref4.samples, samples = _ref4$samples === undefined ? [] : _ref4$samples, interleave = _ref4.interleave, _ref4$pool = _ref4.pool, pool = _ref4$pool === undefined ? null : _ref4$pool, width = _ref4.width, height = _ref4.height, resampleMethod = _ref4.resampleMethod, fillValue = _ref4.fillValue; var imageWindow, imageWindowWidth, imageWindowHeight, numPixels, i, _i, valueArrays, format, bitsPerSample, _i2, valueArray, poolOrDecoder, result; return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: imageWindow = wnd || [0, 0, this.getWidth(), this.getHeight()]; // check parameters if (!(imageWindow[0] > imageWindow[2] || imageWindow[1] > imageWindow[3])) { _context3.next = 3; break; } throw new Error('Invalid subsets'); case 3: imageWindowWidth = imageWindow[2] - imageWindow[0]; imageWindowHeight = imageWindow[3] - imageWindow[1]; numPixels = imageWindowWidth * imageWindowHeight; if (!(!samples || !samples.length)) { _context3.next = 10; break; } for (i = 0; i < this.fileDirectory.SamplesPerPixel; ++i) { samples.push(i); } _context3.next = 17; break; case 10: _i = 0; case 11: if (!(_i < samples.length)) { _context3.next = 17; break; } if (!(samples[_i] >= this.fileDirectory.SamplesPerPixel)) { _context3.next = 14; break; } return _context3.abrupt('return', Promise.reject(new RangeError('Invalid sample index \'' + samples[_i] + '\'.'))); case 14: ++_i; _context3.next = 11; break; case 17: valueArrays = void 0; if (interleave) { format = this.fileDirectory.SampleFormat ? Math.max.apply(null, this.fileDirectory.SampleFormat) : 1; bitsPerSample = Math.max.apply(null, this.fileDirectory.BitsPerSample); valueArrays = arrayForType(format, bitsPerSample, numPixels * samples.length); if (fillValue) { valueArrays.fill(fillValue); } } else { valueArrays = []; for (_i2 = 0; _i2 < samples.length; ++_i2) { valueArray = this.getArrayForSample(samples[_i2], numPixels); if (Array.isArray(fillValue) && _i2 < fillValue.length) { valueArray.fill(fillValue[_i2]); } else if (fillValue && !Array.isArray(fillValue)) { valueArray.fill(fillValue); } valueArrays.push(valueArray); } } poolOrDecoder = pool || (0, _compression.getDecoder)(this.fileDirectory); _context3.next = 22; return this._readRaster(imageWindow, samples, valueArrays, interleave, poolOrDecoder, width, height, resampleMethod); case 22: result = _context3.sent; return _context3.abrupt('return', result); case 24: case 'end': return _context3.stop(); } } }, _callee3, this); })); function readRasters() { return _ref3.apply(this, arguments); } return readRasters; }() /** * Reads raster data from the image as RGB. The result is always an * interleaved typed array. * Colorspaces other than RGB will be transformed to RGB, color maps expanded. * When no other method is applicable, the first sample is used to produce a * greayscale image. * When provided, only a subset of the raster is read for each sample. * * @param {Object} [options] optional parameters * @param {Array} [options.window=whole image] the subset to read data from. * @param {Number} [pool=null] The optional decoder pool to use. * @param {number} [width] The desired width of the output. When the width is no the * same as the images, resampling will be performed. * @param {number} [height] The desired height of the output. When the width is no the * same as the images, resampling will be performed. * @param {string} [resampleMethod='nearest'] The desired resampling method. * @returns {Promise.<TypedArray|TypedArray[]>} the RGB array as a Promise */ }, { key: 'readRGB', value: function () { var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4() { var _ref6 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, window = _ref6.window, _ref6$pool = _ref6.pool, pool = _ref6$pool === undefined ? null : _ref6$pool, width = _ref6.width, height = _ref6.height, resampleMethod = _ref6.resampleMethod; var imageWindow, pi, samples, subOptions, fileDirectory, raster, max, data; return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: imageWindow = window || [0, 0, this.getWidth(), this.getHeight()]; // check parameters if (!(imageWindow[0] > imageWindow[2] || imageWindow[1] > imageWindow[3])) { _context4.next = 3; break; } throw new Error('Invalid subsets'); case 3: pi = this.fileDirectory.PhotometricInterpretation; if (!(pi === _globals.photometricInterpretations.RGB)) { _context4.next = 6; break; } return _context4.abrupt('return', this.readRasters({ window: window, interleave: true, samples: [0, 1, 2], pool: pool })); case 6: samples = void 0; _context4.t0 = pi; _context4.next = _context4.t0 === _globals.photometricInterpretations.WhiteIsZero ? 10 : _context4.t0 === _globals.photometricInterpretations.BlackIsZero ? 10 : _context4.t0 === _globals.photometricInterpretations.Palette ? 10 : _context4.t0 === _globals.photometricInterpretations.CMYK ? 12 : _context4.t0 === _globals.photometricInterpretations.YCbCr ? 14 : _context4.t0 === _globals.photometricInterpretations.CIELab ? 14 : 16; break; case 10: samples = [0]; return _context4.abrupt('break', 17); case 12: samples = [0, 1, 2, 3]; return _context4.abrupt('break', 17); case 14: samples = [0, 1, 2]; return _context4.abrupt('break', 17); case 16: throw new Error('Invalid or unsupported photometric interpretation.'); case 17: subOptions = { window: imageWindow, interleave: true, samples: samples, pool: pool, width: width, height: height, resampleMethod: resampleMethod }; fileDirectory = this.fileDirectory; _context4.next = 21; return this.readRasters(subOptions); case 21: raster = _context4.sent; max = Math.pow(2, this.fileDirectory.BitsPerSample[0]); data = void 0; _context4.t1 = pi; _context4.next = _context4.t1 === _globals.photometricInterpretations.WhiteIsZero ? 27 : _context4.t1 === _globals.photometricInterpretations.BlackIsZero ? 29 : _context4.t1 === _globals.photometricInterpretations.Palette ? 31 : _context4.t1 === _globals.photometricInterpretations.CMYK ? 33 : _context4.t1 === _globals.photometricInterpretations.YCbCr ? 35 : _context4.t1 === _globals.photometricInterpretations.CIELab ? 37 : 39; break; case 27: data = (0, _rgb.fromWhiteIsZero)(raster, max); return _context4.abrupt('break', 40); case 29: data = (0, _rgb.fromBlackIsZero)(raster, max); return _context4.abrupt('break', 40); case 31: data = (0, _rgb.fromPalette)(raster, fileDirectory.ColorMap); return _context4.abrupt('break', 40); case 33: data = (0, _rgb.fromCMYK)(raster); return _context4.abrupt('break', 40); case 35: data = (0, _rgb.fromYCbCr)(raster); return _context4.abrupt('break', 40); case 37: data = (0, _rgb.fromCIELab)(raster); return _context4.abrupt('break', 40); case 39: throw new Error('Unsupported photometric interpretation.'); case 40: data.width = raster.width; data.height = raster.height; return _context4.abrupt('return', data); case 43: case 'end': return _context4.stop(); } } }, _callee4, this); })); function readRGB() { return _ref5.apply(this, arguments); } return readRGB; }() /** * Returns an array of tiepoints. * @returns {Object[]} */ }, { key: 'getTiePoints', value: function getTiePoints() { if (!this.fileDirectory.ModelTiepoint) { return []; } var tiePoints = []; for (var i = 0; i < this.fileDirectory.ModelTiepoint.length; i += 6) { tiePoints.push({ i: this.fileDirectory.ModelTiepoint[i], j: this.fileDirectory.ModelTiepoint[i + 1], k: this.fileDirectory.ModelTiepoint[i + 2], x: this.fileDirectory.ModelTiepoint[i + 3], y: this.fileDirectory.ModelTiepoint[i + 4], z: this.fileDirectory.ModelTiepoint[i + 5] }); } return tiePoints; } /** * Returns the parsed GDAL metadata items. * @returns {Object} */ }, { key: 'getGDALMetadata', value: function getGDALMetadata() { var metadata = {}; if (!this.fileDirectory.GDAL_METADATA) { return null; } var string = this.fileDirectory.GDAL_METADATA; var xmlDom = (0, _globals.parseXml)(string.substring(0, string.length - 1)); var result = xmlDom.evaluate('GDALMetadata/Item', xmlDom, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); for (var i = 0; i < result.snapshotLength; ++i) { var node = result.snapshotItem(i); metadata[node.getAttribute('name')] = node.textContent; } return metadata; } /** * Returns the image origin as a XYZ-vector. When the image has no affine * transformation, then an exception is thrown. * @returns {Array} The origin as a vector */ }, { key: 'getOrigin', value: function getOrigin() { var tiePoints = this.fileDirectory.ModelTiepoint; var modelTransformation = this.fileDirectory.ModelTransformation; if (tiePoints && tiePoints.length === 6) { return [tiePoints[3], tiePoints[4], tiePoints[5]]; } else if (modelTransformation) { return [modelTransformation[3], modelTransformation[7], modelTransformation[11]]; } throw new Error('The image does not have an affine transformation.'); } /** * Returns the image resolution as a XYZ-vector. When the image has no affine * transformation, then an exception is thrown. * @param {GeoTIFFImage} [referenceImage=null] A reference image to calculate the resolution from * in cases when the current image does not have the * required tags on its own. * @returns {Array} The resolution as a vector */ }, { key: 'getResolution', value: function getResolution() { var referenceImage = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; var modelPixelScale = this.fileDirectory.ModelPixelScale; var modelTransformation = this.fileDirectory.ModelTransformation; if (modelPixelScale) { return [modelPixelScale[0], -modelPixelScale[1], modelPixelScale[2]]; } else if (modelTransformation) { return [modelTransformation[0], modelTransformation[5], modelTransformation[10]]; } if (referenceImage) { var _referenceImage$getRe = referenceImage.getResolution(), _referenceImage$getRe2 = (0, _slicedToArray3.default)(_referenceImage$getRe, 3), refResX = _referenceImage$getRe2[0], refResY = _referenceImage$getRe2[1], refResZ = _referenceImage$getRe2[2]; return [refResX * referenceImage.getWidth() / this.getWidth(), refResY * referenceImage.getHeight() / this.getHeight(), refResZ * referenceImage.getWidth() / this.getWidth()]; } throw new Error('The image does not have an affine transformation.'); } /** * Returns whether or not the pixels of the image depict an area (or point). * @returns {Boolean} Whether the pixels are a point */ }, { key: 'pixelIsArea', value: function pixelIsArea() { return this.geoKeys.GTRasterTypeGeoKey === 1; } /** * Returns the image bounding box as an array of 4 values: min-x, min-y, * max-x and max-y. When the image has no affine transformation, then an * exception is thrown. * @returns {Array} The bounding box */ }, { key: 'getBoundingBox', value: function getBoundingBox() { var origin = this.getOrigin(); var resolution = this.getResolution(); var x1 = origin[0]; var y1 = origin[1]; var x2 = x1 + resolution[0] * this.getWidth(); var y2 = y1 + resolution[1] * this.getHeight(); return [Math.min(x1, x2), Math.min(y1, y2), Math.max(x1, x2), Math.max(y1, y2)]; } }]); return GeoTIFFImage; }(); exports.default = GeoTIFFImage;