geotiff.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. "use strict";
  2. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
  3. if (k2 === undefined) k2 = k;
  4. Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
  5. }) : (function(o, m, k, k2) {
  6. if (k2 === undefined) k2 = k;
  7. o[k2] = m[k];
  8. }));
  9. var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
  10. Object.defineProperty(o, "default", { enumerable: true, value: v });
  11. }) : function(o, v) {
  12. o["default"] = v;
  13. });
  14. var __importStar = (this && this.__importStar) || function (mod) {
  15. if (mod && mod.__esModule) return mod;
  16. var result = {};
  17. if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
  18. __setModuleDefault(result, mod);
  19. return result;
  20. };
  21. var __importDefault = (this && this.__importDefault) || function (mod) {
  22. return (mod && mod.__esModule) ? mod : { "default": mod };
  23. };
  24. Object.defineProperty(exports, "__esModule", { value: true });
  25. exports.GeoTIFFImage = exports.Pool = exports.writeArrayBuffer = exports.fromUrls = exports.fromBlob = exports.fromFile = exports.fromArrayBuffer = exports.fromUrl = exports.MultiGeoTIFF = exports.GeoTIFF = exports.setLogger = exports.addDecoder = exports.getDecoder = exports.BaseDecoder = exports.rgb = exports.globals = void 0;
  26. /** @module geotiff */
  27. const geotiffimage_js_1 = __importDefault(require("./geotiffimage.js"));
  28. exports.GeoTIFFImage = geotiffimage_js_1.default;
  29. const dataview64_js_1 = __importDefault(require("./dataview64.js"));
  30. const dataslice_js_1 = __importDefault(require("./dataslice.js"));
  31. const pool_js_1 = __importDefault(require("./pool.js"));
  32. exports.Pool = pool_js_1.default;
  33. const remote_js_1 = require("./source/remote.js");
  34. const arraybuffer_js_1 = require("./source/arraybuffer.js");
  35. const filereader_js_1 = require("./source/filereader.js");
  36. const file_js_1 = require("./source/file.js");
  37. const globals_js_1 = require("./globals.js");
  38. const geotiffwriter_js_1 = require("./geotiffwriter.js");
  39. const globals = __importStar(require("./globals.js"));
  40. exports.globals = globals;
  41. const rgb = __importStar(require("./rgb.js"));
  42. exports.rgb = rgb;
  43. const index_js_1 = require("./compression/index.js");
  44. Object.defineProperty(exports, "getDecoder", { enumerable: true, get: function () { return index_js_1.getDecoder; } });
  45. Object.defineProperty(exports, "addDecoder", { enumerable: true, get: function () { return index_js_1.addDecoder; } });
  46. const logging_js_1 = require("./logging.js");
  47. Object.defineProperty(exports, "setLogger", { enumerable: true, get: function () { return logging_js_1.setLogger; } });
  48. var basedecoder_js_1 = require("./compression/basedecoder.js");
  49. Object.defineProperty(exports, "BaseDecoder", { enumerable: true, get: function () { return __importDefault(basedecoder_js_1).default; } });
  50. /**
  51. * @typedef {Uint8Array | Int8Array | Uint16Array | Int16Array | Uint32Array | Int32Array | Float32Array | Float64Array}
  52. * TypedArray
  53. */
  54. /**
  55. * @typedef {{ height:number, width: number }} Dimensions
  56. */
  57. /**
  58. * The autogenerated docs are a little confusing here. The effective type is:
  59. *
  60. * `TypedArray & { height: number; width: number}`
  61. * @typedef {TypedArray & Dimensions} TypedArrayWithDimensions
  62. */
  63. /**
  64. * The autogenerated docs are a little confusing here. The effective type is:
  65. *
  66. * `TypedArray[] & { height: number; width: number}`
  67. * @typedef {TypedArray[] & Dimensions} TypedArrayArrayWithDimensions
  68. */
  69. /**
  70. * The autogenerated docs are a little confusing here. The effective type is:
  71. *
  72. * `(TypedArray | TypedArray[]) & { height: number; width: number}`
  73. * @typedef {TypedArrayWithDimensions | TypedArrayArrayWithDimensions} ReadRasterResult
  74. */
  75. function getFieldTypeLength(fieldType) {
  76. switch (fieldType) {
  77. case globals_js_1.fieldTypes.BYTE:
  78. case globals_js_1.fieldTypes.ASCII:
  79. case globals_js_1.fieldTypes.SBYTE:
  80. case globals_js_1.fieldTypes.UNDEFINED:
  81. return 1;
  82. case globals_js_1.fieldTypes.SHORT:
  83. case globals_js_1.fieldTypes.SSHORT:
  84. return 2;
  85. case globals_js_1.fieldTypes.LONG:
  86. case globals_js_1.fieldTypes.SLONG:
  87. case globals_js_1.fieldTypes.FLOAT:
  88. case globals_js_1.fieldTypes.IFD:
  89. return 4;
  90. case globals_js_1.fieldTypes.RATIONAL:
  91. case globals_js_1.fieldTypes.SRATIONAL:
  92. case globals_js_1.fieldTypes.DOUBLE:
  93. case globals_js_1.fieldTypes.LONG8:
  94. case globals_js_1.fieldTypes.SLONG8:
  95. case globals_js_1.fieldTypes.IFD8:
  96. return 8;
  97. default:
  98. throw new RangeError(`Invalid field type: ${fieldType}`);
  99. }
  100. }
  101. function parseGeoKeyDirectory(fileDirectory) {
  102. const rawGeoKeyDirectory = fileDirectory.GeoKeyDirectory;
  103. if (!rawGeoKeyDirectory) {
  104. return null;
  105. }
  106. const geoKeyDirectory = {};
  107. for (let i = 4; i <= rawGeoKeyDirectory[3] * 4; i += 4) {
  108. const key = globals_js_1.geoKeyNames[rawGeoKeyDirectory[i]];
  109. const location = (rawGeoKeyDirectory[i + 1])
  110. ? (globals_js_1.fieldTagNames[rawGeoKeyDirectory[i + 1]]) : null;
  111. const count = rawGeoKeyDirectory[i + 2];
  112. const offset = rawGeoKeyDirectory[i + 3];
  113. let value = null;
  114. if (!location) {
  115. value = offset;
  116. }
  117. else {
  118. value = fileDirectory[location];
  119. if (typeof value === 'undefined' || value === null) {
  120. throw new Error(`Could not get value of geoKey '${key}'.`);
  121. }
  122. else if (typeof value === 'string') {
  123. value = value.substring(offset, offset + count - 1);
  124. }
  125. else if (value.subarray) {
  126. value = value.subarray(offset, offset + count);
  127. if (count === 1) {
  128. value = value[0];
  129. }
  130. }
  131. }
  132. geoKeyDirectory[key] = value;
  133. }
  134. return geoKeyDirectory;
  135. }
  136. function getValues(dataSlice, fieldType, count, offset) {
  137. let values = null;
  138. let readMethod = null;
  139. const fieldTypeLength = getFieldTypeLength(fieldType);
  140. switch (fieldType) {
  141. case globals_js_1.fieldTypes.BYTE:
  142. case globals_js_1.fieldTypes.ASCII:
  143. case globals_js_1.fieldTypes.UNDEFINED:
  144. values = new Uint8Array(count);
  145. readMethod = dataSlice.readUint8;
  146. break;
  147. case globals_js_1.fieldTypes.SBYTE:
  148. values = new Int8Array(count);
  149. readMethod = dataSlice.readInt8;
  150. break;
  151. case globals_js_1.fieldTypes.SHORT:
  152. values = new Uint16Array(count);
  153. readMethod = dataSlice.readUint16;
  154. break;
  155. case globals_js_1.fieldTypes.SSHORT:
  156. values = new Int16Array(count);
  157. readMethod = dataSlice.readInt16;
  158. break;
  159. case globals_js_1.fieldTypes.LONG:
  160. case globals_js_1.fieldTypes.IFD:
  161. values = new Uint32Array(count);
  162. readMethod = dataSlice.readUint32;
  163. break;
  164. case globals_js_1.fieldTypes.SLONG:
  165. values = new Int32Array(count);
  166. readMethod = dataSlice.readInt32;
  167. break;
  168. case globals_js_1.fieldTypes.LONG8:
  169. case globals_js_1.fieldTypes.IFD8:
  170. values = new Array(count);
  171. readMethod = dataSlice.readUint64;
  172. break;
  173. case globals_js_1.fieldTypes.SLONG8:
  174. values = new Array(count);
  175. readMethod = dataSlice.readInt64;
  176. break;
  177. case globals_js_1.fieldTypes.RATIONAL:
  178. values = new Uint32Array(count * 2);
  179. readMethod = dataSlice.readUint32;
  180. break;
  181. case globals_js_1.fieldTypes.SRATIONAL:
  182. values = new Int32Array(count * 2);
  183. readMethod = dataSlice.readInt32;
  184. break;
  185. case globals_js_1.fieldTypes.FLOAT:
  186. values = new Float32Array(count);
  187. readMethod = dataSlice.readFloat32;
  188. break;
  189. case globals_js_1.fieldTypes.DOUBLE:
  190. values = new Float64Array(count);
  191. readMethod = dataSlice.readFloat64;
  192. break;
  193. default:
  194. throw new RangeError(`Invalid field type: ${fieldType}`);
  195. }
  196. // normal fields
  197. if (!(fieldType === globals_js_1.fieldTypes.RATIONAL || fieldType === globals_js_1.fieldTypes.SRATIONAL)) {
  198. for (let i = 0; i < count; ++i) {
  199. values[i] = readMethod.call(dataSlice, offset + (i * fieldTypeLength));
  200. }
  201. }
  202. else { // RATIONAL or SRATIONAL
  203. for (let i = 0; i < count; i += 2) {
  204. values[i] = readMethod.call(dataSlice, offset + (i * fieldTypeLength));
  205. values[i + 1] = readMethod.call(dataSlice, offset + ((i * fieldTypeLength) + 4));
  206. }
  207. }
  208. if (fieldType === globals_js_1.fieldTypes.ASCII) {
  209. return new TextDecoder('utf-8').decode(values);
  210. }
  211. return values;
  212. }
  213. /**
  214. * Data class to store the parsed file directory, geo key directory and
  215. * offset to the next IFD
  216. */
  217. class ImageFileDirectory {
  218. constructor(fileDirectory, geoKeyDirectory, nextIFDByteOffset) {
  219. this.fileDirectory = fileDirectory;
  220. this.geoKeyDirectory = geoKeyDirectory;
  221. this.nextIFDByteOffset = nextIFDByteOffset;
  222. }
  223. }
  224. /**
  225. * Error class for cases when an IFD index was requested, that does not exist
  226. * in the file.
  227. */
  228. class GeoTIFFImageIndexError extends Error {
  229. constructor(index) {
  230. super(`No image at index ${index}`);
  231. this.index = index;
  232. }
  233. }
  234. class GeoTIFFBase {
  235. /**
  236. * (experimental) Reads raster data from the best fitting image. This function uses
  237. * the image with the lowest resolution that is still a higher resolution than the
  238. * requested resolution.
  239. * When specified, the `bbox` option is translated to the `window` option and the
  240. * `resX` and `resY` to `width` and `height` respectively.
  241. * Then, the [readRasters]{@link GeoTIFFImage#readRasters} method of the selected
  242. * image is called and the result returned.
  243. * @see GeoTIFFImage.readRasters
  244. * @param {import('./geotiffimage').ReadRasterOptions} [options={}] optional parameters
  245. * @returns {Promise<ReadRasterResult>} the decoded array(s), with `height` and `width`, as a promise
  246. */
  247. async readRasters(options = {}) {
  248. const { window: imageWindow, width, height } = options;
  249. let { resX, resY, bbox } = options;
  250. const firstImage = await this.getImage();
  251. let usedImage = firstImage;
  252. const imageCount = await this.getImageCount();
  253. const imgBBox = firstImage.getBoundingBox();
  254. if (imageWindow && bbox) {
  255. throw new Error('Both "bbox" and "window" passed.');
  256. }
  257. // if width/height is passed, transform it to resolution
  258. if (width || height) {
  259. // if we have an image window (pixel coordinates), transform it to a BBox
  260. // using the origin/resolution of the first image.
  261. if (imageWindow) {
  262. const [oX, oY] = firstImage.getOrigin();
  263. const [rX, rY] = firstImage.getResolution();
  264. bbox = [
  265. oX + (imageWindow[0] * rX),
  266. oY + (imageWindow[1] * rY),
  267. oX + (imageWindow[2] * rX),
  268. oY + (imageWindow[3] * rY),
  269. ];
  270. }
  271. // if we have a bbox (or calculated one)
  272. const usedBBox = bbox || imgBBox;
  273. if (width) {
  274. if (resX) {
  275. throw new Error('Both width and resX passed');
  276. }
  277. resX = (usedBBox[2] - usedBBox[0]) / width;
  278. }
  279. if (height) {
  280. if (resY) {
  281. throw new Error('Both width and resY passed');
  282. }
  283. resY = (usedBBox[3] - usedBBox[1]) / height;
  284. }
  285. }
  286. // if resolution is set or calculated, try to get the image with the worst acceptable resolution
  287. if (resX || resY) {
  288. const allImages = [];
  289. for (let i = 0; i < imageCount; ++i) {
  290. const image = await this.getImage(i);
  291. const { SubfileType: subfileType, NewSubfileType: newSubfileType } = image.fileDirectory;
  292. if (i === 0 || subfileType === 2 || newSubfileType & 1) {
  293. allImages.push(image);
  294. }
  295. }
  296. allImages.sort((a, b) => a.getWidth() - b.getWidth());
  297. for (let i = 0; i < allImages.length; ++i) {
  298. const image = allImages[i];
  299. const imgResX = (imgBBox[2] - imgBBox[0]) / image.getWidth();
  300. const imgResY = (imgBBox[3] - imgBBox[1]) / image.getHeight();
  301. usedImage = image;
  302. if ((resX && resX > imgResX) || (resY && resY > imgResY)) {
  303. break;
  304. }
  305. }
  306. }
  307. let wnd = imageWindow;
  308. if (bbox) {
  309. const [oX, oY] = firstImage.getOrigin();
  310. const [imageResX, imageResY] = usedImage.getResolution(firstImage);
  311. wnd = [
  312. Math.round((bbox[0] - oX) / imageResX),
  313. Math.round((bbox[1] - oY) / imageResY),
  314. Math.round((bbox[2] - oX) / imageResX),
  315. Math.round((bbox[3] - oY) / imageResY),
  316. ];
  317. wnd = [
  318. Math.min(wnd[0], wnd[2]),
  319. Math.min(wnd[1], wnd[3]),
  320. Math.max(wnd[0], wnd[2]),
  321. Math.max(wnd[1], wnd[3]),
  322. ];
  323. }
  324. return usedImage.readRasters({ ...options, window: wnd });
  325. }
  326. }
  327. /**
  328. * @typedef {Object} GeoTIFFOptions
  329. * @property {boolean} [cache=false] whether or not decoded tiles shall be cached.
  330. */
  331. /**
  332. * The abstraction for a whole GeoTIFF file.
  333. * @augments GeoTIFFBase
  334. */
  335. class GeoTIFF extends GeoTIFFBase {
  336. /**
  337. * @constructor
  338. * @param {*} source The datasource to read from.
  339. * @param {boolean} littleEndian Whether the image uses little endian.
  340. * @param {boolean} bigTiff Whether the image uses bigTIFF conventions.
  341. * @param {number} firstIFDOffset The numeric byte-offset from the start of the image
  342. * to the first IFD.
  343. * @param {GeoTIFFOptions} [options] further options.
  344. */
  345. constructor(source, littleEndian, bigTiff, firstIFDOffset, options = {}) {
  346. super();
  347. this.source = source;
  348. this.littleEndian = littleEndian;
  349. this.bigTiff = bigTiff;
  350. this.firstIFDOffset = firstIFDOffset;
  351. this.cache = options.cache || false;
  352. this.ifdRequests = [];
  353. this.ghostValues = null;
  354. }
  355. async getSlice(offset, size) {
  356. const fallbackSize = this.bigTiff ? 4048 : 1024;
  357. return new dataslice_js_1.default((await this.source.fetch([{
  358. offset,
  359. length: typeof size !== 'undefined' ? size : fallbackSize,
  360. }]))[0], offset, this.littleEndian, this.bigTiff);
  361. }
  362. /**
  363. * Instructs to parse an image file directory at the given file offset.
  364. * As there is no way to ensure that a location is indeed the start of an IFD,
  365. * this function must be called with caution (e.g only using the IFD offsets from
  366. * the headers or other IFDs).
  367. * @param {number} offset the offset to parse the IFD at
  368. * @returns {Promise<ImageFileDirectory>} the parsed IFD
  369. */
  370. async parseFileDirectoryAt(offset) {
  371. const entrySize = this.bigTiff ? 20 : 12;
  372. const offsetSize = this.bigTiff ? 8 : 2;
  373. let dataSlice = await this.getSlice(offset);
  374. const numDirEntries = this.bigTiff
  375. ? dataSlice.readUint64(offset)
  376. : dataSlice.readUint16(offset);
  377. // if the slice does not cover the whole IFD, request a bigger slice, where the
  378. // whole IFD fits: num of entries + n x tag length + offset to next IFD
  379. const byteSize = (numDirEntries * entrySize) + (this.bigTiff ? 16 : 6);
  380. if (!dataSlice.covers(offset, byteSize)) {
  381. dataSlice = await this.getSlice(offset, byteSize);
  382. }
  383. const fileDirectory = {};
  384. // loop over the IFD and create a file directory object
  385. let i = offset + (this.bigTiff ? 8 : 2);
  386. for (let entryCount = 0; entryCount < numDirEntries; i += entrySize, ++entryCount) {
  387. const fieldTag = dataSlice.readUint16(i);
  388. const fieldType = dataSlice.readUint16(i + 2);
  389. const typeCount = this.bigTiff
  390. ? dataSlice.readUint64(i + 4)
  391. : dataSlice.readUint32(i + 4);
  392. let fieldValues;
  393. let value;
  394. const fieldTypeLength = getFieldTypeLength(fieldType);
  395. const valueOffset = i + (this.bigTiff ? 12 : 8);
  396. // check whether the value is directly encoded in the tag or refers to a
  397. // different external byte range
  398. if (fieldTypeLength * typeCount <= (this.bigTiff ? 8 : 4)) {
  399. fieldValues = getValues(dataSlice, fieldType, typeCount, valueOffset);
  400. }
  401. else {
  402. // resolve the reference to the actual byte range
  403. const actualOffset = dataSlice.readOffset(valueOffset);
  404. const length = getFieldTypeLength(fieldType) * typeCount;
  405. // check, whether we actually cover the referenced byte range; if not,
  406. // request a new slice of bytes to read from it
  407. if (dataSlice.covers(actualOffset, length)) {
  408. fieldValues = getValues(dataSlice, fieldType, typeCount, actualOffset);
  409. }
  410. else {
  411. const fieldDataSlice = await this.getSlice(actualOffset, length);
  412. fieldValues = getValues(fieldDataSlice, fieldType, typeCount, actualOffset);
  413. }
  414. }
  415. // unpack single values from the array
  416. if (typeCount === 1 && globals_js_1.arrayFields.indexOf(fieldTag) === -1
  417. && !(fieldType === globals_js_1.fieldTypes.RATIONAL || fieldType === globals_js_1.fieldTypes.SRATIONAL)) {
  418. value = fieldValues[0];
  419. }
  420. else {
  421. value = fieldValues;
  422. }
  423. // write the tags value to the file directly
  424. fileDirectory[globals_js_1.fieldTagNames[fieldTag]] = value;
  425. }
  426. const geoKeyDirectory = parseGeoKeyDirectory(fileDirectory);
  427. const nextIFDByteOffset = dataSlice.readOffset(offset + offsetSize + (entrySize * numDirEntries));
  428. return new ImageFileDirectory(fileDirectory, geoKeyDirectory, nextIFDByteOffset);
  429. }
  430. async requestIFD(index) {
  431. // see if we already have that IFD index requested.
  432. if (this.ifdRequests[index]) {
  433. // attach to an already requested IFD
  434. return this.ifdRequests[index];
  435. }
  436. else if (index === 0) {
  437. // special case for index 0
  438. this.ifdRequests[index] = this.parseFileDirectoryAt(this.firstIFDOffset);
  439. return this.ifdRequests[index];
  440. }
  441. else if (!this.ifdRequests[index - 1]) {
  442. // if the previous IFD was not yet loaded, load that one first
  443. // this is the recursive call.
  444. try {
  445. this.ifdRequests[index - 1] = this.requestIFD(index - 1);
  446. }
  447. catch (e) {
  448. // if the previous one already was an index error, rethrow
  449. // with the current index
  450. if (e instanceof GeoTIFFImageIndexError) {
  451. throw new GeoTIFFImageIndexError(index);
  452. }
  453. // rethrow anything else
  454. throw e;
  455. }
  456. }
  457. // if the previous IFD was loaded, we can finally fetch the one we are interested in.
  458. // we need to wrap this in an IIFE, otherwise this.ifdRequests[index] would be delayed
  459. this.ifdRequests[index] = (async () => {
  460. const previousIfd = await this.ifdRequests[index - 1];
  461. if (previousIfd.nextIFDByteOffset === 0) {
  462. throw new GeoTIFFImageIndexError(index);
  463. }
  464. return this.parseFileDirectoryAt(previousIfd.nextIFDByteOffset);
  465. })();
  466. return this.ifdRequests[index];
  467. }
  468. /**
  469. * Get the n-th internal subfile of an image. By default, the first is returned.
  470. *
  471. * @param {number} [index=0] the index of the image to return.
  472. * @returns {Promise<GeoTIFFImage>} the image at the given index
  473. */
  474. async getImage(index = 0) {
  475. const ifd = await this.requestIFD(index);
  476. return new geotiffimage_js_1.default(ifd.fileDirectory, ifd.geoKeyDirectory, this.dataView, this.littleEndian, this.cache, this.source);
  477. }
  478. /**
  479. * Returns the count of the internal subfiles.
  480. *
  481. * @returns {Promise<number>} the number of internal subfile images
  482. */
  483. async getImageCount() {
  484. let index = 0;
  485. // loop until we run out of IFDs
  486. let hasNext = true;
  487. while (hasNext) {
  488. try {
  489. await this.requestIFD(index);
  490. ++index;
  491. }
  492. catch (e) {
  493. if (e instanceof GeoTIFFImageIndexError) {
  494. hasNext = false;
  495. }
  496. else {
  497. throw e;
  498. }
  499. }
  500. }
  501. return index;
  502. }
  503. /**
  504. * Get the values of the COG ghost area as a parsed map.
  505. * See https://gdal.org/drivers/raster/cog.html#header-ghost-area for reference
  506. * @returns {Promise<Object>} the parsed ghost area or null, if no such area was found
  507. */
  508. async getGhostValues() {
  509. const offset = this.bigTiff ? 16 : 8;
  510. if (this.ghostValues) {
  511. return this.ghostValues;
  512. }
  513. const detectionString = 'GDAL_STRUCTURAL_METADATA_SIZE=';
  514. const heuristicAreaSize = detectionString.length + 100;
  515. let slice = await this.getSlice(offset, heuristicAreaSize);
  516. if (detectionString === getValues(slice, globals_js_1.fieldTypes.ASCII, detectionString.length, offset)) {
  517. const valuesString = getValues(slice, globals_js_1.fieldTypes.ASCII, heuristicAreaSize, offset);
  518. const firstLine = valuesString.split('\n')[0];
  519. const metadataSize = Number(firstLine.split('=')[1].split(' ')[0]) + firstLine.length;
  520. if (metadataSize > heuristicAreaSize) {
  521. slice = await this.getSlice(offset, metadataSize);
  522. }
  523. const fullString = getValues(slice, globals_js_1.fieldTypes.ASCII, metadataSize, offset);
  524. this.ghostValues = {};
  525. fullString
  526. .split('\n')
  527. .filter((line) => line.length > 0)
  528. .map((line) => line.split('='))
  529. .forEach(([key, value]) => {
  530. this.ghostValues[key] = value;
  531. });
  532. }
  533. return this.ghostValues;
  534. }
  535. /**
  536. * Parse a (Geo)TIFF file from the given source.
  537. *
  538. * @param {*} source The source of data to parse from.
  539. * @param {GeoTIFFOptions} [options] Additional options.
  540. * @param {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
  541. * to be aborted
  542. */
  543. static async fromSource(source, options, signal) {
  544. const headerData = (await source.fetch([{ offset: 0, length: 1024 }], signal))[0];
  545. const dataView = new dataview64_js_1.default(headerData);
  546. const BOM = dataView.getUint16(0, 0);
  547. let littleEndian;
  548. if (BOM === 0x4949) {
  549. littleEndian = true;
  550. }
  551. else if (BOM === 0x4D4D) {
  552. littleEndian = false;
  553. }
  554. else {
  555. throw new TypeError('Invalid byte order value.');
  556. }
  557. const magicNumber = dataView.getUint16(2, littleEndian);
  558. let bigTiff;
  559. if (magicNumber === 42) {
  560. bigTiff = false;
  561. }
  562. else if (magicNumber === 43) {
  563. bigTiff = true;
  564. const offsetByteSize = dataView.getUint16(4, littleEndian);
  565. if (offsetByteSize !== 8) {
  566. throw new Error('Unsupported offset byte-size.');
  567. }
  568. }
  569. else {
  570. throw new TypeError('Invalid magic number.');
  571. }
  572. const firstIFDOffset = bigTiff
  573. ? dataView.getUint64(8, littleEndian)
  574. : dataView.getUint32(4, littleEndian);
  575. return new GeoTIFF(source, littleEndian, bigTiff, firstIFDOffset, options);
  576. }
  577. /**
  578. * Closes the underlying file buffer
  579. * N.B. After the GeoTIFF has been completely processed it needs
  580. * to be closed but only if it has been constructed from a file.
  581. */
  582. close() {
  583. if (typeof this.source.close === 'function') {
  584. return this.source.close();
  585. }
  586. return false;
  587. }
  588. }
  589. exports.GeoTIFF = GeoTIFF;
  590. exports.default = GeoTIFF;
  591. /**
  592. * Wrapper for GeoTIFF files that have external overviews.
  593. * @augments GeoTIFFBase
  594. */
  595. class MultiGeoTIFF extends GeoTIFFBase {
  596. /**
  597. * Construct a new MultiGeoTIFF from a main and several overview files.
  598. * @param {GeoTIFF} mainFile The main GeoTIFF file.
  599. * @param {GeoTIFF[]} overviewFiles An array of overview files.
  600. */
  601. constructor(mainFile, overviewFiles) {
  602. super();
  603. this.mainFile = mainFile;
  604. this.overviewFiles = overviewFiles;
  605. this.imageFiles = [mainFile].concat(overviewFiles);
  606. this.fileDirectoriesPerFile = null;
  607. this.fileDirectoriesPerFileParsing = null;
  608. this.imageCount = null;
  609. }
  610. async parseFileDirectoriesPerFile() {
  611. const requests = [this.mainFile.parseFileDirectoryAt(this.mainFile.firstIFDOffset)]
  612. .concat(this.overviewFiles.map((file) => file.parseFileDirectoryAt(file.firstIFDOffset)));
  613. this.fileDirectoriesPerFile = await Promise.all(requests);
  614. return this.fileDirectoriesPerFile;
  615. }
  616. /**
  617. * Get the n-th internal subfile of an image. By default, the first is returned.
  618. *
  619. * @param {number} [index=0] the index of the image to return.
  620. * @returns {Promise<GeoTIFFImage>} the image at the given index
  621. */
  622. async getImage(index = 0) {
  623. await this.getImageCount();
  624. await this.parseFileDirectoriesPerFile();
  625. let visited = 0;
  626. let relativeIndex = 0;
  627. for (let i = 0; i < this.imageFiles.length; i++) {
  628. const imageFile = this.imageFiles[i];
  629. for (let ii = 0; ii < this.imageCounts[i]; ii++) {
  630. if (index === visited) {
  631. const ifd = await imageFile.requestIFD(relativeIndex);
  632. return new geotiffimage_js_1.default(ifd.fileDirectory, ifd.geoKeyDirectory, imageFile.dataView, imageFile.littleEndian, imageFile.cache, imageFile.source);
  633. }
  634. visited++;
  635. relativeIndex++;
  636. }
  637. relativeIndex = 0;
  638. }
  639. throw new RangeError('Invalid image index');
  640. }
  641. /**
  642. * Returns the count of the internal subfiles.
  643. *
  644. * @returns {Promise<number>} the number of internal subfile images
  645. */
  646. async getImageCount() {
  647. if (this.imageCount !== null) {
  648. return this.imageCount;
  649. }
  650. const requests = [this.mainFile.getImageCount()]
  651. .concat(this.overviewFiles.map((file) => file.getImageCount()));
  652. this.imageCounts = await Promise.all(requests);
  653. this.imageCount = this.imageCounts.reduce((count, ifds) => count + ifds, 0);
  654. return this.imageCount;
  655. }
  656. }
  657. exports.MultiGeoTIFF = MultiGeoTIFF;
  658. /**
  659. * Creates a new GeoTIFF from a remote URL.
  660. * @param {string} url The URL to access the image from
  661. * @param {object} [options] Additional options to pass to the source.
  662. * See {@link makeRemoteSource} for details.
  663. * @param {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
  664. * to be aborted
  665. * @returns {Promise<GeoTIFF>} The resulting GeoTIFF file.
  666. */
  667. async function fromUrl(url, options = {}, signal) {
  668. return GeoTIFF.fromSource((0, remote_js_1.makeRemoteSource)(url, options), signal);
  669. }
  670. exports.fromUrl = fromUrl;
  671. /**
  672. * Construct a new GeoTIFF from an
  673. * [ArrayBuffer]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer}.
  674. * @param {ArrayBuffer} arrayBuffer The data to read the file from.
  675. * @param {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
  676. * to be aborted
  677. * @returns {Promise<GeoTIFF>} The resulting GeoTIFF file.
  678. */
  679. async function fromArrayBuffer(arrayBuffer, signal) {
  680. return GeoTIFF.fromSource((0, arraybuffer_js_1.makeBufferSource)(arrayBuffer), signal);
  681. }
  682. exports.fromArrayBuffer = fromArrayBuffer;
  683. /**
  684. * Construct a GeoTIFF from a local file path. This uses the node
  685. * [filesystem API]{@link https://nodejs.org/api/fs.html} and is
  686. * not available on browsers.
  687. *
  688. * N.B. After the GeoTIFF has been completely processed it needs
  689. * to be closed but only if it has been constructed from a file.
  690. * @param {string} path The file path to read from.
  691. * @param {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
  692. * to be aborted
  693. * @returns {Promise<GeoTIFF>} The resulting GeoTIFF file.
  694. */
  695. async function fromFile(path, signal) {
  696. return GeoTIFF.fromSource((0, file_js_1.makeFileSource)(path), signal);
  697. }
  698. exports.fromFile = fromFile;
  699. /**
  700. * Construct a GeoTIFF from an HTML
  701. * [Blob]{@link https://developer.mozilla.org/en-US/docs/Web/API/Blob} or
  702. * [File]{@link https://developer.mozilla.org/en-US/docs/Web/API/File}
  703. * object.
  704. * @param {Blob|File} blob The Blob or File object to read from.
  705. * @param {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
  706. * to be aborted
  707. * @returns {Promise<GeoTIFF>} The resulting GeoTIFF file.
  708. */
  709. async function fromBlob(blob, signal) {
  710. return GeoTIFF.fromSource((0, filereader_js_1.makeFileReaderSource)(blob), signal);
  711. }
  712. exports.fromBlob = fromBlob;
  713. /**
  714. * Construct a MultiGeoTIFF from the given URLs.
  715. * @param {string} mainUrl The URL for the main file.
  716. * @param {string[]} overviewUrls An array of URLs for the overview images.
  717. * @param {Object} [options] Additional options to pass to the source.
  718. * See [makeRemoteSource]{@link module:source.makeRemoteSource}
  719. * for details.
  720. * @param {AbortSignal} [signal] An AbortSignal that may be signalled if the request is
  721. * to be aborted
  722. * @returns {Promise<MultiGeoTIFF>} The resulting MultiGeoTIFF file.
  723. */
  724. async function fromUrls(mainUrl, overviewUrls = [], options = {}, signal) {
  725. const mainFile = await GeoTIFF.fromSource((0, remote_js_1.makeRemoteSource)(mainUrl, options), signal);
  726. const overviewFiles = await Promise.all(overviewUrls.map((url) => GeoTIFF.fromSource((0, remote_js_1.makeRemoteSource)(url, options))));
  727. return new MultiGeoTIFF(mainFile, overviewFiles);
  728. }
  729. exports.fromUrls = fromUrls;
  730. /**
  731. * Main creating function for GeoTIFF files.
  732. * @param {(Array)} array of pixel values
  733. * @returns {metadata} metadata
  734. */
  735. function writeArrayBuffer(values, metadata) {
  736. return (0, geotiffwriter_js_1.writeGeotiff)(values, metadata);
  737. }
  738. exports.writeArrayBuffer = writeArrayBuffer;
  739. //# sourceMappingURL=geotiff.js.map