geotiffimage.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. /** @module geotiffimage */
  7. const float16_1 = require("@petamoriken/float16");
  8. const get_attribute_js_1 = __importDefault(require("xml-utils/get-attribute.js"));
  9. const find_tags_by_name_js_1 = __importDefault(require("xml-utils/find-tags-by-name.js"));
  10. const globals_js_1 = require("./globals.js");
  11. const rgb_js_1 = require("./rgb.js");
  12. const index_js_1 = require("./compression/index.js");
  13. const resample_js_1 = require("./resample.js");
  14. /**
  15. * @typedef {Object} ReadRasterOptions
  16. * @property {Array<number>} [window=whole window] the subset to read data from in pixels.
  17. * @property {Array<number>} [bbox=whole image] the subset to read data from in
  18. * geographical coordinates.
  19. * @property {Array<number>} [samples=all samples] the selection of samples to read from. Default is all samples.
  20. * @property {boolean} [interleave=false] whether the data shall be read
  21. * in one single array or separate
  22. * arrays.
  23. * @property {Pool} [pool=null] The optional decoder pool to use.
  24. * @property {number} [width] The desired width of the output. When the width is not the
  25. * same as the images, resampling will be performed.
  26. * @property {number} [height] The desired height of the output. When the width is not the
  27. * same as the images, resampling will be performed.
  28. * @property {string} [resampleMethod='nearest'] The desired resampling method.
  29. * @property {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
  30. * to be aborted
  31. * @property {number|number[]} [fillValue] The value to use for parts of the image
  32. * outside of the images extent. When multiple
  33. * samples are requested, an array of fill values
  34. * can be passed.
  35. */
  36. /** @typedef {import("./geotiff.js").TypedArray} TypedArray */
  37. /** @typedef {import("./geotiff.js").ReadRasterResult} ReadRasterResult */
  38. function sum(array, start, end) {
  39. let s = 0;
  40. for (let i = start; i < end; ++i) {
  41. s += array[i];
  42. }
  43. return s;
  44. }
  45. function arrayForType(format, bitsPerSample, size) {
  46. switch (format) {
  47. case 1: // unsigned integer data
  48. if (bitsPerSample <= 8) {
  49. return new Uint8Array(size);
  50. }
  51. else if (bitsPerSample <= 16) {
  52. return new Uint16Array(size);
  53. }
  54. else if (bitsPerSample <= 32) {
  55. return new Uint32Array(size);
  56. }
  57. break;
  58. case 2: // twos complement signed integer data
  59. if (bitsPerSample === 8) {
  60. return new Int8Array(size);
  61. }
  62. else if (bitsPerSample === 16) {
  63. return new Int16Array(size);
  64. }
  65. else if (bitsPerSample === 32) {
  66. return new Int32Array(size);
  67. }
  68. break;
  69. case 3: // floating point data
  70. switch (bitsPerSample) {
  71. case 16:
  72. case 32:
  73. return new Float32Array(size);
  74. case 64:
  75. return new Float64Array(size);
  76. default:
  77. break;
  78. }
  79. break;
  80. default:
  81. break;
  82. }
  83. throw Error('Unsupported data format/bitsPerSample');
  84. }
  85. function needsNormalization(format, bitsPerSample) {
  86. if ((format === 1 || format === 2) && bitsPerSample <= 32 && bitsPerSample % 8 === 0) {
  87. return false;
  88. }
  89. else if (format === 3 && (bitsPerSample === 16 || bitsPerSample === 32 || bitsPerSample === 64)) {
  90. return false;
  91. }
  92. return true;
  93. }
  94. function normalizeArray(inBuffer, format, planarConfiguration, samplesPerPixel, bitsPerSample, tileWidth, tileHeight) {
  95. // const inByteArray = new Uint8Array(inBuffer);
  96. const view = new DataView(inBuffer);
  97. const outSize = planarConfiguration === 2
  98. ? tileHeight * tileWidth
  99. : tileHeight * tileWidth * samplesPerPixel;
  100. const samplesToTransfer = planarConfiguration === 2
  101. ? 1 : samplesPerPixel;
  102. const outArray = arrayForType(format, bitsPerSample, outSize);
  103. // let pixel = 0;
  104. const bitMask = parseInt('1'.repeat(bitsPerSample), 2);
  105. if (format === 1) { // unsigned integer
  106. // translation of https://github.com/OSGeo/gdal/blob/master/gdal/frmts/gtiff/geotiff.cpp#L7337
  107. let pixelBitSkip;
  108. // let sampleBitOffset = 0;
  109. if (planarConfiguration === 1) {
  110. pixelBitSkip = samplesPerPixel * bitsPerSample;
  111. // sampleBitOffset = (samplesPerPixel - 1) * bitsPerSample;
  112. }
  113. else {
  114. pixelBitSkip = bitsPerSample;
  115. }
  116. // Bits per line rounds up to next byte boundary.
  117. let bitsPerLine = tileWidth * pixelBitSkip;
  118. if ((bitsPerLine & 7) !== 0) {
  119. bitsPerLine = (bitsPerLine + 7) & (~7);
  120. }
  121. for (let y = 0; y < tileHeight; ++y) {
  122. const lineBitOffset = y * bitsPerLine;
  123. for (let x = 0; x < tileWidth; ++x) {
  124. const pixelBitOffset = lineBitOffset + (x * samplesToTransfer * bitsPerSample);
  125. for (let i = 0; i < samplesToTransfer; ++i) {
  126. const bitOffset = pixelBitOffset + (i * bitsPerSample);
  127. const outIndex = (((y * tileWidth) + x) * samplesToTransfer) + i;
  128. const byteOffset = Math.floor(bitOffset / 8);
  129. const innerBitOffset = bitOffset % 8;
  130. if (innerBitOffset + bitsPerSample <= 8) {
  131. outArray[outIndex] = (view.getUint8(byteOffset) >> (8 - bitsPerSample) - innerBitOffset) & bitMask;
  132. }
  133. else if (innerBitOffset + bitsPerSample <= 16) {
  134. outArray[outIndex] = (view.getUint16(byteOffset) >> (16 - bitsPerSample) - innerBitOffset) & bitMask;
  135. }
  136. else if (innerBitOffset + bitsPerSample <= 24) {
  137. const raw = (view.getUint16(byteOffset) << 8) | (view.getUint8(byteOffset + 2));
  138. outArray[outIndex] = (raw >> (24 - bitsPerSample) - innerBitOffset) & bitMask;
  139. }
  140. else {
  141. outArray[outIndex] = (view.getUint32(byteOffset) >> (32 - bitsPerSample) - innerBitOffset) & bitMask;
  142. }
  143. // let outWord = 0;
  144. // for (let bit = 0; bit < bitsPerSample; ++bit) {
  145. // if (inByteArray[bitOffset >> 3]
  146. // & (0x80 >> (bitOffset & 7))) {
  147. // outWord |= (1 << (bitsPerSample - 1 - bit));
  148. // }
  149. // ++bitOffset;
  150. // }
  151. // outArray[outIndex] = outWord;
  152. // outArray[pixel] = outWord;
  153. // pixel += 1;
  154. }
  155. // bitOffset = bitOffset + pixelBitSkip - bitsPerSample;
  156. }
  157. }
  158. }
  159. else if (format === 3) { // floating point
  160. // Float16 is handled elsewhere
  161. // normalize 16/24 bit floats to 32 bit floats in the array
  162. // console.time();
  163. // if (bitsPerSample === 16) {
  164. // for (let byte = 0, outIndex = 0; byte < inBuffer.byteLength; byte += 2, ++outIndex) {
  165. // outArray[outIndex] = getFloat16(view, byte);
  166. // }
  167. // }
  168. // console.timeEnd()
  169. }
  170. return outArray.buffer;
  171. }
  172. /**
  173. * GeoTIFF sub-file image.
  174. */
  175. class GeoTIFFImage {
  176. /**
  177. * @constructor
  178. * @param {Object} fileDirectory The parsed file directory
  179. * @param {Object} geoKeys The parsed geo-keys
  180. * @param {DataView} dataView The DataView for the underlying file.
  181. * @param {Boolean} littleEndian Whether the file is encoded in little or big endian
  182. * @param {Boolean} cache Whether or not decoded tiles shall be cached
  183. * @param {import('./source/basesource').BaseSource} source The datasource to read from
  184. */
  185. constructor(fileDirectory, geoKeys, dataView, littleEndian, cache, source) {
  186. this.fileDirectory = fileDirectory;
  187. this.geoKeys = geoKeys;
  188. this.dataView = dataView;
  189. this.littleEndian = littleEndian;
  190. this.tiles = cache ? {} : null;
  191. this.isTiled = !fileDirectory.StripOffsets;
  192. const planarConfiguration = fileDirectory.PlanarConfiguration;
  193. this.planarConfiguration = (typeof planarConfiguration === 'undefined') ? 1 : planarConfiguration;
  194. if (this.planarConfiguration !== 1 && this.planarConfiguration !== 2) {
  195. throw new Error('Invalid planar configuration.');
  196. }
  197. this.source = source;
  198. }
  199. /**
  200. * Returns the associated parsed file directory.
  201. * @returns {Object} the parsed file directory
  202. */
  203. getFileDirectory() {
  204. return this.fileDirectory;
  205. }
  206. /**
  207. * Returns the associated parsed geo keys.
  208. * @returns {Object} the parsed geo keys
  209. */
  210. getGeoKeys() {
  211. return this.geoKeys;
  212. }
  213. /**
  214. * Returns the width of the image.
  215. * @returns {Number} the width of the image
  216. */
  217. getWidth() {
  218. return this.fileDirectory.ImageWidth;
  219. }
  220. /**
  221. * Returns the height of the image.
  222. * @returns {Number} the height of the image
  223. */
  224. getHeight() {
  225. return this.fileDirectory.ImageLength;
  226. }
  227. /**
  228. * Returns the number of samples per pixel.
  229. * @returns {Number} the number of samples per pixel
  230. */
  231. getSamplesPerPixel() {
  232. return typeof this.fileDirectory.SamplesPerPixel !== 'undefined'
  233. ? this.fileDirectory.SamplesPerPixel : 1;
  234. }
  235. /**
  236. * Returns the width of each tile.
  237. * @returns {Number} the width of each tile
  238. */
  239. getTileWidth() {
  240. return this.isTiled ? this.fileDirectory.TileWidth : this.getWidth();
  241. }
  242. /**
  243. * Returns the height of each tile.
  244. * @returns {Number} the height of each tile
  245. */
  246. getTileHeight() {
  247. if (this.isTiled) {
  248. return this.fileDirectory.TileLength;
  249. }
  250. if (typeof this.fileDirectory.RowsPerStrip !== 'undefined') {
  251. return Math.min(this.fileDirectory.RowsPerStrip, this.getHeight());
  252. }
  253. return this.getHeight();
  254. }
  255. getBlockWidth() {
  256. return this.getTileWidth();
  257. }
  258. getBlockHeight(y) {
  259. if (this.isTiled || (y + 1) * this.getTileHeight() <= this.getHeight()) {
  260. return this.getTileHeight();
  261. }
  262. else {
  263. return this.getHeight() - (y * this.getTileHeight());
  264. }
  265. }
  266. /**
  267. * Calculates the number of bytes for each pixel across all samples. Only full
  268. * bytes are supported, an exception is thrown when this is not the case.
  269. * @returns {Number} the bytes per pixel
  270. */
  271. getBytesPerPixel() {
  272. let bytes = 0;
  273. for (let i = 0; i < this.fileDirectory.BitsPerSample.length; ++i) {
  274. bytes += this.getSampleByteSize(i);
  275. }
  276. return bytes;
  277. }
  278. getSampleByteSize(i) {
  279. if (i >= this.fileDirectory.BitsPerSample.length) {
  280. throw new RangeError(`Sample index ${i} is out of range.`);
  281. }
  282. return Math.ceil(this.fileDirectory.BitsPerSample[i] / 8);
  283. }
  284. getReaderForSample(sampleIndex) {
  285. const format = this.fileDirectory.SampleFormat
  286. ? this.fileDirectory.SampleFormat[sampleIndex] : 1;
  287. const bitsPerSample = this.fileDirectory.BitsPerSample[sampleIndex];
  288. switch (format) {
  289. case 1: // unsigned integer data
  290. if (bitsPerSample <= 8) {
  291. return DataView.prototype.getUint8;
  292. }
  293. else if (bitsPerSample <= 16) {
  294. return DataView.prototype.getUint16;
  295. }
  296. else if (bitsPerSample <= 32) {
  297. return DataView.prototype.getUint32;
  298. }
  299. break;
  300. case 2: // twos complement signed integer data
  301. if (bitsPerSample <= 8) {
  302. return DataView.prototype.getInt8;
  303. }
  304. else if (bitsPerSample <= 16) {
  305. return DataView.prototype.getInt16;
  306. }
  307. else if (bitsPerSample <= 32) {
  308. return DataView.prototype.getInt32;
  309. }
  310. break;
  311. case 3:
  312. switch (bitsPerSample) {
  313. case 16:
  314. return function (offset, littleEndian) {
  315. return (0, float16_1.getFloat16)(this, offset, littleEndian);
  316. };
  317. case 32:
  318. return DataView.prototype.getFloat32;
  319. case 64:
  320. return DataView.prototype.getFloat64;
  321. default:
  322. break;
  323. }
  324. break;
  325. default:
  326. break;
  327. }
  328. throw Error('Unsupported data format/bitsPerSample');
  329. }
  330. getSampleFormat(sampleIndex = 0) {
  331. return this.fileDirectory.SampleFormat
  332. ? this.fileDirectory.SampleFormat[sampleIndex] : 1;
  333. }
  334. getBitsPerSample(sampleIndex = 0) {
  335. return this.fileDirectory.BitsPerSample[sampleIndex];
  336. }
  337. getArrayForSample(sampleIndex, size) {
  338. const format = this.getSampleFormat(sampleIndex);
  339. const bitsPerSample = this.getBitsPerSample(sampleIndex);
  340. return arrayForType(format, bitsPerSample, size);
  341. }
  342. /**
  343. * Returns the decoded strip or tile.
  344. * @param {Number} x the strip or tile x-offset
  345. * @param {Number} y the tile y-offset (0 for stripped images)
  346. * @param {Number} sample the sample to get for separated samples
  347. * @param {import("./geotiff").Pool|import("./geotiff").BaseDecoder} poolOrDecoder the decoder or decoder pool
  348. * @param {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
  349. * to be aborted
  350. * @returns {Promise.<ArrayBuffer>}
  351. */
  352. async getTileOrStrip(x, y, sample, poolOrDecoder, signal) {
  353. const numTilesPerRow = Math.ceil(this.getWidth() / this.getTileWidth());
  354. const numTilesPerCol = Math.ceil(this.getHeight() / this.getTileHeight());
  355. let index;
  356. const { tiles } = this;
  357. if (this.planarConfiguration === 1) {
  358. index = (y * numTilesPerRow) + x;
  359. }
  360. else if (this.planarConfiguration === 2) {
  361. index = (sample * numTilesPerRow * numTilesPerCol) + (y * numTilesPerRow) + x;
  362. }
  363. let offset;
  364. let byteCount;
  365. if (this.isTiled) {
  366. offset = this.fileDirectory.TileOffsets[index];
  367. byteCount = this.fileDirectory.TileByteCounts[index];
  368. }
  369. else {
  370. offset = this.fileDirectory.StripOffsets[index];
  371. byteCount = this.fileDirectory.StripByteCounts[index];
  372. }
  373. const slice = (await this.source.fetch([{ offset, length: byteCount }], signal))[0];
  374. let request;
  375. if (tiles === null || !tiles[index]) {
  376. // resolve each request by potentially applying array normalization
  377. request = (async () => {
  378. let data = await poolOrDecoder.decode(this.fileDirectory, slice);
  379. const sampleFormat = this.getSampleFormat();
  380. const bitsPerSample = this.getBitsPerSample();
  381. if (needsNormalization(sampleFormat, bitsPerSample)) {
  382. data = normalizeArray(data, sampleFormat, this.planarConfiguration, this.getSamplesPerPixel(), bitsPerSample, this.getTileWidth(), this.getBlockHeight(y));
  383. }
  384. return data;
  385. })();
  386. // set the cache
  387. if (tiles !== null) {
  388. tiles[index] = request;
  389. }
  390. }
  391. else {
  392. // get from the cache
  393. request = tiles[index];
  394. }
  395. // cache the tile request
  396. return { x, y, sample, data: await request };
  397. }
  398. /**
  399. * Internal read function.
  400. * @private
  401. * @param {Array} imageWindow The image window in pixel coordinates
  402. * @param {Array} samples The selected samples (0-based indices)
  403. * @param {TypedArray|TypedArray[]} valueArrays The array(s) to write into
  404. * @param {Boolean} interleave Whether or not to write in an interleaved manner
  405. * @param {import("./geotiff").Pool|AbstractDecoder} poolOrDecoder the decoder or decoder pool
  406. * @param {number} width the width of window to be read into
  407. * @param {number} height the height of window to be read into
  408. * @param {number} resampleMethod the resampling method to be used when interpolating
  409. * @param {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
  410. * to be aborted
  411. * @returns {Promise<ReadRasterResult>}
  412. */
  413. async _readRaster(imageWindow, samples, valueArrays, interleave, poolOrDecoder, width, height, resampleMethod, signal) {
  414. const tileWidth = this.getTileWidth();
  415. const tileHeight = this.getTileHeight();
  416. const imageWidth = this.getWidth();
  417. const imageHeight = this.getHeight();
  418. const minXTile = Math.max(Math.floor(imageWindow[0] / tileWidth), 0);
  419. const maxXTile = Math.min(Math.ceil(imageWindow[2] / tileWidth), Math.ceil(imageWidth / tileWidth));
  420. const minYTile = Math.max(Math.floor(imageWindow[1] / tileHeight), 0);
  421. const maxYTile = Math.min(Math.ceil(imageWindow[3] / tileHeight), Math.ceil(imageHeight / tileHeight));
  422. const windowWidth = imageWindow[2] - imageWindow[0];
  423. let bytesPerPixel = this.getBytesPerPixel();
  424. const srcSampleOffsets = [];
  425. const sampleReaders = [];
  426. for (let i = 0; i < samples.length; ++i) {
  427. if (this.planarConfiguration === 1) {
  428. srcSampleOffsets.push(sum(this.fileDirectory.BitsPerSample, 0, samples[i]) / 8);
  429. }
  430. else {
  431. srcSampleOffsets.push(0);
  432. }
  433. sampleReaders.push(this.getReaderForSample(samples[i]));
  434. }
  435. const promises = [];
  436. const { littleEndian } = this;
  437. for (let yTile = minYTile; yTile < maxYTile; ++yTile) {
  438. for (let xTile = minXTile; xTile < maxXTile; ++xTile) {
  439. for (let sampleIndex = 0; sampleIndex < samples.length; ++sampleIndex) {
  440. const si = sampleIndex;
  441. const sample = samples[sampleIndex];
  442. if (this.planarConfiguration === 2) {
  443. bytesPerPixel = this.getSampleByteSize(sampleIndex);
  444. }
  445. const promise = this.getTileOrStrip(xTile, yTile, sample, poolOrDecoder, signal).then((tile) => {
  446. const buffer = tile.data;
  447. const dataView = new DataView(buffer);
  448. const blockHeight = this.getBlockHeight(tile.y);
  449. const firstLine = tile.y * tileHeight;
  450. const firstCol = tile.x * tileWidth;
  451. const lastLine = firstLine + blockHeight;
  452. const lastCol = (tile.x + 1) * tileWidth;
  453. const reader = sampleReaders[si];
  454. const ymax = Math.min(blockHeight, blockHeight - (lastLine - imageWindow[3]), imageHeight - firstLine);
  455. const xmax = Math.min(tileWidth, tileWidth - (lastCol - imageWindow[2]), imageWidth - firstCol);
  456. for (let y = Math.max(0, imageWindow[1] - firstLine); y < ymax; ++y) {
  457. for (let x = Math.max(0, imageWindow[0] - firstCol); x < xmax; ++x) {
  458. const pixelOffset = ((y * tileWidth) + x) * bytesPerPixel;
  459. const value = reader.call(dataView, pixelOffset + srcSampleOffsets[si], littleEndian);
  460. let windowCoordinate;
  461. if (interleave) {
  462. windowCoordinate = ((y + firstLine - imageWindow[1]) * windowWidth * samples.length)
  463. + ((x + firstCol - imageWindow[0]) * samples.length)
  464. + si;
  465. valueArrays[windowCoordinate] = value;
  466. }
  467. else {
  468. windowCoordinate = ((y + firstLine - imageWindow[1]) * windowWidth) + x + firstCol - imageWindow[0];
  469. valueArrays[si][windowCoordinate] = value;
  470. }
  471. }
  472. }
  473. });
  474. promises.push(promise);
  475. }
  476. }
  477. }
  478. await Promise.all(promises);
  479. if ((width && (imageWindow[2] - imageWindow[0]) !== width)
  480. || (height && (imageWindow[3] - imageWindow[1]) !== height)) {
  481. let resampled;
  482. if (interleave) {
  483. resampled = (0, resample_js_1.resampleInterleaved)(valueArrays, imageWindow[2] - imageWindow[0], imageWindow[3] - imageWindow[1], width, height, samples.length, resampleMethod);
  484. }
  485. else {
  486. resampled = (0, resample_js_1.resample)(valueArrays, imageWindow[2] - imageWindow[0], imageWindow[3] - imageWindow[1], width, height, resampleMethod);
  487. }
  488. resampled.width = width;
  489. resampled.height = height;
  490. return resampled;
  491. }
  492. valueArrays.width = width || imageWindow[2] - imageWindow[0];
  493. valueArrays.height = height || imageWindow[3] - imageWindow[1];
  494. return valueArrays;
  495. }
  496. /**
  497. * Reads raster data from the image. This function reads all selected samples
  498. * into separate arrays of the correct type for that sample or into a single
  499. * combined array when `interleave` is set. When provided, only a subset
  500. * of the raster is read for each sample.
  501. *
  502. * @param {ReadRasterOptions} [options={}] optional parameters
  503. * @returns {Promise<ReadRasterResult>} the decoded arrays as a promise
  504. */
  505. async readRasters({ window: wnd, samples = [], interleave, pool = null, width, height, resampleMethod, fillValue, signal, } = {}) {
  506. const imageWindow = wnd || [0, 0, this.getWidth(), this.getHeight()];
  507. // check parameters
  508. if (imageWindow[0] > imageWindow[2] || imageWindow[1] > imageWindow[3]) {
  509. throw new Error('Invalid subsets');
  510. }
  511. const imageWindowWidth = imageWindow[2] - imageWindow[0];
  512. const imageWindowHeight = imageWindow[3] - imageWindow[1];
  513. const numPixels = imageWindowWidth * imageWindowHeight;
  514. const samplesPerPixel = this.getSamplesPerPixel();
  515. if (!samples || !samples.length) {
  516. for (let i = 0; i < samplesPerPixel; ++i) {
  517. samples.push(i);
  518. }
  519. }
  520. else {
  521. for (let i = 0; i < samples.length; ++i) {
  522. if (samples[i] >= samplesPerPixel) {
  523. return Promise.reject(new RangeError(`Invalid sample index '${samples[i]}'.`));
  524. }
  525. }
  526. }
  527. let valueArrays;
  528. if (interleave) {
  529. const format = this.fileDirectory.SampleFormat
  530. ? Math.max.apply(null, this.fileDirectory.SampleFormat) : 1;
  531. const bitsPerSample = Math.max.apply(null, this.fileDirectory.BitsPerSample);
  532. valueArrays = arrayForType(format, bitsPerSample, numPixels * samples.length);
  533. if (fillValue) {
  534. valueArrays.fill(fillValue);
  535. }
  536. }
  537. else {
  538. valueArrays = [];
  539. for (let i = 0; i < samples.length; ++i) {
  540. const valueArray = this.getArrayForSample(samples[i], numPixels);
  541. if (Array.isArray(fillValue) && i < fillValue.length) {
  542. valueArray.fill(fillValue[i]);
  543. }
  544. else if (fillValue && !Array.isArray(fillValue)) {
  545. valueArray.fill(fillValue);
  546. }
  547. valueArrays.push(valueArray);
  548. }
  549. }
  550. const poolOrDecoder = pool || await (0, index_js_1.getDecoder)(this.fileDirectory);
  551. const result = await this._readRaster(imageWindow, samples, valueArrays, interleave, poolOrDecoder, width, height, resampleMethod, signal);
  552. return result;
  553. }
  554. /**
  555. * Reads raster data from the image as RGB. The result is always an
  556. * interleaved typed array.
  557. * Colorspaces other than RGB will be transformed to RGB, color maps expanded.
  558. * When no other method is applicable, the first sample is used to produce a
  559. * grayscale image.
  560. * When provided, only a subset of the raster is read for each sample.
  561. *
  562. * @param {Object} [options] optional parameters
  563. * @param {Array<number>} [options.window] the subset to read data from in pixels.
  564. * @param {boolean} [options.interleave=true] whether the data shall be read
  565. * in one single array or separate
  566. * arrays.
  567. * @param {import("./geotiff").Pool} [options.pool=null] The optional decoder pool to use.
  568. * @param {number} [options.width] The desired width of the output. When the width is no the
  569. * same as the images, resampling will be performed.
  570. * @param {number} [options.height] The desired height of the output. When the width is no the
  571. * same as the images, resampling will be performed.
  572. * @param {string} [options.resampleMethod='nearest'] The desired resampling method.
  573. * @param {boolean} [options.enableAlpha=false] Enable reading alpha channel if present.
  574. * @param {AbortSignal} [options.signal] An AbortSignal that may be signalled if the request is
  575. * to be aborted
  576. * @returns {Promise<ReadRasterResult>} the RGB array as a Promise
  577. */
  578. async readRGB({ window, interleave = true, pool = null, width, height, resampleMethod, enableAlpha = false, signal } = {}) {
  579. const imageWindow = window || [0, 0, this.getWidth(), this.getHeight()];
  580. // check parameters
  581. if (imageWindow[0] > imageWindow[2] || imageWindow[1] > imageWindow[3]) {
  582. throw new Error('Invalid subsets');
  583. }
  584. const pi = this.fileDirectory.PhotometricInterpretation;
  585. if (pi === globals_js_1.photometricInterpretations.RGB) {
  586. let s = [0, 1, 2];
  587. if ((!(this.fileDirectory.ExtraSamples === globals_js_1.ExtraSamplesValues.Unspecified)) && enableAlpha) {
  588. s = [];
  589. for (let i = 0; i < this.fileDirectory.BitsPerSample.length; i += 1) {
  590. s.push(i);
  591. }
  592. }
  593. return this.readRasters({
  594. window,
  595. interleave,
  596. samples: s,
  597. pool,
  598. width,
  599. height,
  600. resampleMethod,
  601. signal,
  602. });
  603. }
  604. let samples;
  605. switch (pi) {
  606. case globals_js_1.photometricInterpretations.WhiteIsZero:
  607. case globals_js_1.photometricInterpretations.BlackIsZero:
  608. case globals_js_1.photometricInterpretations.Palette:
  609. samples = [0];
  610. break;
  611. case globals_js_1.photometricInterpretations.CMYK:
  612. samples = [0, 1, 2, 3];
  613. break;
  614. case globals_js_1.photometricInterpretations.YCbCr:
  615. case globals_js_1.photometricInterpretations.CIELab:
  616. samples = [0, 1, 2];
  617. break;
  618. default:
  619. throw new Error('Invalid or unsupported photometric interpretation.');
  620. }
  621. const subOptions = {
  622. window: imageWindow,
  623. interleave: true,
  624. samples,
  625. pool,
  626. width,
  627. height,
  628. resampleMethod,
  629. signal,
  630. };
  631. const { fileDirectory } = this;
  632. const raster = await this.readRasters(subOptions);
  633. const max = 2 ** this.fileDirectory.BitsPerSample[0];
  634. let data;
  635. switch (pi) {
  636. case globals_js_1.photometricInterpretations.WhiteIsZero:
  637. data = (0, rgb_js_1.fromWhiteIsZero)(raster, max);
  638. break;
  639. case globals_js_1.photometricInterpretations.BlackIsZero:
  640. data = (0, rgb_js_1.fromBlackIsZero)(raster, max);
  641. break;
  642. case globals_js_1.photometricInterpretations.Palette:
  643. data = (0, rgb_js_1.fromPalette)(raster, fileDirectory.ColorMap);
  644. break;
  645. case globals_js_1.photometricInterpretations.CMYK:
  646. data = (0, rgb_js_1.fromCMYK)(raster);
  647. break;
  648. case globals_js_1.photometricInterpretations.YCbCr:
  649. data = (0, rgb_js_1.fromYCbCr)(raster);
  650. break;
  651. case globals_js_1.photometricInterpretations.CIELab:
  652. data = (0, rgb_js_1.fromCIELab)(raster);
  653. break;
  654. default:
  655. throw new Error('Unsupported photometric interpretation.');
  656. }
  657. // if non-interleaved data is requested, we must split the channels
  658. // into their respective arrays
  659. if (!interleave) {
  660. const red = new Uint8Array(data.length / 3);
  661. const green = new Uint8Array(data.length / 3);
  662. const blue = new Uint8Array(data.length / 3);
  663. for (let i = 0, j = 0; i < data.length; i += 3, ++j) {
  664. red[j] = data[i];
  665. green[j] = data[i + 1];
  666. blue[j] = data[i + 2];
  667. }
  668. data = [red, green, blue];
  669. }
  670. data.width = raster.width;
  671. data.height = raster.height;
  672. return data;
  673. }
  674. /**
  675. * Returns an array of tiepoints.
  676. * @returns {Object[]}
  677. */
  678. getTiePoints() {
  679. if (!this.fileDirectory.ModelTiepoint) {
  680. return [];
  681. }
  682. const tiePoints = [];
  683. for (let i = 0; i < this.fileDirectory.ModelTiepoint.length; i += 6) {
  684. tiePoints.push({
  685. i: this.fileDirectory.ModelTiepoint[i],
  686. j: this.fileDirectory.ModelTiepoint[i + 1],
  687. k: this.fileDirectory.ModelTiepoint[i + 2],
  688. x: this.fileDirectory.ModelTiepoint[i + 3],
  689. y: this.fileDirectory.ModelTiepoint[i + 4],
  690. z: this.fileDirectory.ModelTiepoint[i + 5],
  691. });
  692. }
  693. return tiePoints;
  694. }
  695. /**
  696. * Returns the parsed GDAL metadata items.
  697. *
  698. * If sample is passed to null, dataset-level metadata will be returned.
  699. * Otherwise only metadata specific to the provided sample will be returned.
  700. *
  701. * @param {number} [sample=null] The sample index.
  702. * @returns {Object}
  703. */
  704. getGDALMetadata(sample = null) {
  705. const metadata = {};
  706. if (!this.fileDirectory.GDAL_METADATA) {
  707. return null;
  708. }
  709. const string = this.fileDirectory.GDAL_METADATA;
  710. let items = (0, find_tags_by_name_js_1.default)(string, 'Item');
  711. if (sample === null) {
  712. items = items.filter((item) => (0, get_attribute_js_1.default)(item, 'sample') === undefined);
  713. }
  714. else {
  715. items = items.filter((item) => Number((0, get_attribute_js_1.default)(item, 'sample')) === sample);
  716. }
  717. for (let i = 0; i < items.length; ++i) {
  718. const item = items[i];
  719. metadata[(0, get_attribute_js_1.default)(item, 'name')] = item.inner;
  720. }
  721. return metadata;
  722. }
  723. /**
  724. * Returns the GDAL nodata value
  725. * @returns {number|null}
  726. */
  727. getGDALNoData() {
  728. if (!this.fileDirectory.GDAL_NODATA) {
  729. return null;
  730. }
  731. const string = this.fileDirectory.GDAL_NODATA;
  732. return Number(string.substring(0, string.length - 1));
  733. }
  734. /**
  735. * Returns the image origin as a XYZ-vector. When the image has no affine
  736. * transformation, then an exception is thrown.
  737. * @returns {Array<number>} The origin as a vector
  738. */
  739. getOrigin() {
  740. const tiePoints = this.fileDirectory.ModelTiepoint;
  741. const modelTransformation = this.fileDirectory.ModelTransformation;
  742. if (tiePoints && tiePoints.length === 6) {
  743. return [
  744. tiePoints[3],
  745. tiePoints[4],
  746. tiePoints[5],
  747. ];
  748. }
  749. if (modelTransformation) {
  750. return [
  751. modelTransformation[3],
  752. modelTransformation[7],
  753. modelTransformation[11],
  754. ];
  755. }
  756. throw new Error('The image does not have an affine transformation.');
  757. }
  758. /**
  759. * Returns the image resolution as a XYZ-vector. When the image has no affine
  760. * transformation, then an exception is thrown.
  761. * @param {GeoTIFFImage} [referenceImage=null] A reference image to calculate the resolution from
  762. * in cases when the current image does not have the
  763. * required tags on its own.
  764. * @returns {Array<number>} The resolution as a vector
  765. */
  766. getResolution(referenceImage = null) {
  767. const modelPixelScale = this.fileDirectory.ModelPixelScale;
  768. const modelTransformation = this.fileDirectory.ModelTransformation;
  769. if (modelPixelScale) {
  770. return [
  771. modelPixelScale[0],
  772. -modelPixelScale[1],
  773. modelPixelScale[2],
  774. ];
  775. }
  776. if (modelTransformation) {
  777. return [
  778. modelTransformation[0],
  779. modelTransformation[5],
  780. modelTransformation[10],
  781. ];
  782. }
  783. if (referenceImage) {
  784. const [refResX, refResY, refResZ] = referenceImage.getResolution();
  785. return [
  786. refResX * referenceImage.getWidth() / this.getWidth(),
  787. refResY * referenceImage.getHeight() / this.getHeight(),
  788. refResZ * referenceImage.getWidth() / this.getWidth(),
  789. ];
  790. }
  791. throw new Error('The image does not have an affine transformation.');
  792. }
  793. /**
  794. * Returns whether or not the pixels of the image depict an area (or point).
  795. * @returns {Boolean} Whether the pixels are a point
  796. */
  797. pixelIsArea() {
  798. return this.geoKeys.GTRasterTypeGeoKey === 1;
  799. }
  800. /**
  801. * Returns the image bounding box as an array of 4 values: min-x, min-y,
  802. * max-x and max-y. When the image has no affine transformation, then an
  803. * exception is thrown.
  804. * @returns {Array<number>} The bounding box
  805. */
  806. getBoundingBox() {
  807. const origin = this.getOrigin();
  808. const resolution = this.getResolution();
  809. const x1 = origin[0];
  810. const y1 = origin[1];
  811. const x2 = x1 + (resolution[0] * this.getWidth());
  812. const y2 = y1 + (resolution[1] * this.getHeight());
  813. return [
  814. Math.min(x1, x2),
  815. Math.min(y1, y2),
  816. Math.max(x1, x2),
  817. Math.max(y1, y2),
  818. ];
  819. }
  820. }
  821. exports.default = GeoTIFFImage;
  822. //# sourceMappingURL=geotiffimage.js.map