@cogeotiff/core 9.3.0 → 9.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -42,14 +42,14 @@ export class TiffImage {
42
42
  *
43
43
  * @param loadGeoTags Whether to load the GeoKeyDirectory and unpack it
44
44
  */
45
- async init(loadGeoTags = true) {
45
+ async init(loadGeoTags = true, options) {
46
46
  const requiredTags = [];
47
47
  ImportantTags.forEach((tag) => {
48
- requiredTags.push(this.fetch(tag));
48
+ requiredTags.push(this.fetch(tag, options));
49
49
  });
50
50
  if (loadGeoTags) {
51
51
  ImportantGeoTags.forEach((tag) => {
52
- requiredTags.push(this.fetch(tag));
52
+ requiredTags.push(this.fetch(tag, options));
53
53
  });
54
54
  }
55
55
  await Promise.all(requiredTags);
@@ -100,18 +100,18 @@ export class TiffImage {
100
100
  *
101
101
  * @param tag tag to fetch
102
102
  */
103
- async fetch(tag) {
103
+ async fetch(tag, options) {
104
104
  const sourceTag = this.tags.get(tag);
105
105
  if (sourceTag == null)
106
106
  return null;
107
107
  if (sourceTag.type === 'inline')
108
108
  return sourceTag.value;
109
109
  if (sourceTag.type === 'lazy')
110
- return fetchLazy(sourceTag, this.tiff);
110
+ return fetchLazy(sourceTag, this.tiff, options);
111
111
  if (sourceTag.isLoaded)
112
112
  return sourceTag.value;
113
113
  if (sourceTag.type === 'offset')
114
- return fetchAllOffsets(this.tiff, sourceTag);
114
+ return fetchAllOffsets(this.tiff, sourceTag, options);
115
115
  throw new Error('Cannot fetch:' + tag);
116
116
  }
117
117
  /**
@@ -138,7 +138,7 @@ export class TiffImage {
138
138
  const tag = this.tags.get(TiffTag.GdalNoData);
139
139
  if (tag == null)
140
140
  return null;
141
- if (tag.value)
141
+ if (tag.value != null)
142
142
  return Number(tag.value);
143
143
  throw new Error('GdalNoData tag is not loaded');
144
144
  }
@@ -398,7 +398,7 @@ export class TiffImage {
398
398
  *
399
399
  * @param index Strip index to read
400
400
  */
401
- async getStrip(index) {
401
+ async getStrip(index, options) {
402
402
  if (this.isTiled())
403
403
  throw new Error('Cannot read stripes, tiff is tiled: ' + index);
404
404
  const byteCounts = this.tags.get(TiffTag.StripByteCounts);
@@ -406,10 +406,10 @@ export class TiffImage {
406
406
  if (index >= byteCounts.count)
407
407
  throw new Error('Cannot read strip, index out of bounds');
408
408
  const [byteCount, offset] = await Promise.all([
409
- getOffset(this.tiff, offsets, index),
410
- getOffset(this.tiff, byteCounts, index),
409
+ getOffset(this.tiff, offsets, index, options),
410
+ getOffset(this.tiff, byteCounts, index, options),
411
411
  ]);
412
- return this.getBytes(byteCount, offset);
412
+ return this.getBytes(byteCount, offset, options);
413
413
  }
414
414
  /** The jpeg header is stored in the IFD, read the JPEG header and adjust the byte array to include it */
415
415
  getJpegHeader(bytes) {
@@ -464,7 +464,7 @@ export class TiffImage {
464
464
  const totalTiles = nxTiles * nyTiles;
465
465
  if (idx >= totalTiles)
466
466
  throw new Error(`Tile index is outside of tile range: ${idx} >= ${totalTiles}`);
467
- const { offset, imageSize } = await this.getTileSize(idx);
467
+ const { offset, imageSize } = await this.getTileSize(idx, options);
468
468
  return this.getBytes(offset, imageSize, options);
469
469
  }
470
470
  /**
@@ -478,7 +478,7 @@ export class TiffImage {
478
478
  *
479
479
  * @returns if the tile exists and has data
480
480
  */
481
- async hasTile(x, y) {
481
+ async hasTile(x, y, options) {
482
482
  const tiles = this.tileSize;
483
483
  const size = this.size;
484
484
  if (tiles == null)
@@ -489,7 +489,7 @@ export class TiffImage {
489
489
  if (x >= nxTiles || y >= nyTiles)
490
490
  return false;
491
491
  const idx = y * nxTiles + x;
492
- const ret = await this.getTileSize(idx);
492
+ const ret = await this.getTileSize(idx, options);
493
493
  return ret.offset > 0;
494
494
  }
495
495
  /**
@@ -500,7 +500,7 @@ export class TiffImage {
500
500
  * @param index index in the tile array
501
501
  * @returns Offset and byteCount for the tile
502
502
  */
503
- async getTileSize(index) {
503
+ async getTileSize(index, options) {
504
504
  // If both the tile offset and tile byte counts are loaded,
505
505
  // we can get the offset and byte count synchronously without needing to fetch any additional data
506
506
  const byteCounts = this.tags.get(TiffTag.TileByteCounts);
@@ -512,29 +512,29 @@ export class TiffImage {
512
512
  // the few bytes leading up to the tile
513
513
  const leaderBytes = this.tiff.options?.tileLeaderByteSize;
514
514
  if (leaderBytes) {
515
- const offset = tileOffset ?? (await getOffset(this.tiff, this.tileOffset, index));
515
+ const offset = tileOffset ?? (await getOffset(this.tiff, this.tileOffset, index, options));
516
516
  // Sparse tiff no data found
517
517
  if (offset === 0)
518
518
  return { offset: 0, imageSize: 0 };
519
519
  // This fetch will generally load in the bytes needed for the image too
520
520
  // provided the image size is less than the size of a chunk
521
- const bytes = await this.tiff.source.fetch(offset - leaderBytes, leaderBytes);
521
+ const bytes = await this.tiff.source.fetch(offset - leaderBytes, leaderBytes, options);
522
522
  return { offset, imageSize: getUint(new DataView(bytes), 0, leaderBytes, this.tiff.isLittleEndian) };
523
523
  }
524
524
  if (byteCounts == null)
525
525
  throw new Error('No tile byte counts found');
526
526
  const [offset, imageSize] = await Promise.all([
527
- tileOffset ?? getOffset(this.tiff, this.tileOffset, index),
528
- tileSize ?? getOffset(this.tiff, byteCounts, index),
527
+ tileOffset ?? getOffset(this.tiff, this.tileOffset, index, options),
528
+ tileSize ?? getOffset(this.tiff, byteCounts, index, options),
529
529
  ]);
530
530
  return { offset, imageSize };
531
531
  }
532
532
  }
533
- function getOffset(tiff, x, index) {
533
+ function getOffset(tiff, x, index, options) {
534
534
  const val = getOffsetSync(tiff, x, index);
535
535
  if (val != null)
536
536
  return Promise.resolve(val);
537
- return getValueAt(tiff, x, index);
537
+ return getValueAt(tiff, x, index, options);
538
538
  }
539
539
  function getOffsetSync(tiff, x, index) {
540
540
  if (index < 0) {
package/build/tiff.js CHANGED
@@ -34,7 +34,7 @@ export class Tiff {
34
34
  }
35
35
  /** Create a tiff and initialize it by reading the tiff headers */
36
36
  static create(source, options = { defaultReadSize: Tiff.DefaultReadSize }) {
37
- return new Tiff(source, options).init();
37
+ return new Tiff(source, options).init(options);
38
38
  }
39
39
  /**
40
40
  * Initialize the tiff loading in the header and all image headers.
@@ -42,10 +42,12 @@ export class Tiff {
42
42
  * This is only required if the Tiff was created with the constructor, if you
43
43
  * used {@link create} this will have already been called.
44
44
  */
45
- init() {
45
+ init(options) {
46
+ if (this.isInitialized)
47
+ return Promise.resolve(this);
46
48
  if (this._initPromise)
47
49
  return this._initPromise;
48
- this._initPromise = this.readHeader();
50
+ this._initPromise = this.readHeader(options);
49
51
  return this._initPromise;
50
52
  }
51
53
  /**
@@ -71,11 +73,11 @@ export class Tiff {
71
73
  return firstImage;
72
74
  }
73
75
  /** Read the Starting header and all Image headers from the source */
74
- async readHeader() {
76
+ async readHeader(options) {
75
77
  if (this.isInitialized)
76
78
  return this;
77
79
  // limit the read to the size of the file if it is known, for small tiffs
78
- const bytes = new DataView(await this.source.fetch(0, getMaxLength(this.source, 0, this.defaultReadSize)));
80
+ const bytes = new DataView(await this.source.fetch(0, getMaxLength(this.source, 0, this.defaultReadSize), options));
79
81
  if (bytes.byteLength === 0)
80
82
  throw new Error('Unable to read empty tiff');
81
83
  bytes.sourceOffset = 0;
@@ -117,13 +119,13 @@ export class Tiff {
117
119
  // Ensure at least 1KB near at the IFD offset is ready for reading
118
120
  // TODO is 1KB enough, most IFD entries are in the order of 100-300 bytes
119
121
  if (!hasBytes(lastView, nextOffsetIfd, 1024)) {
120
- const bytes = await this.source.fetch(nextOffsetIfd, getMaxLength(this.source, nextOffsetIfd, this.defaultReadSize));
122
+ const bytes = await this.source.fetch(nextOffsetIfd, getMaxLength(this.source, nextOffsetIfd, this.defaultReadSize), options);
121
123
  lastView = new DataView(bytes);
122
124
  lastView.sourceOffset = nextOffsetIfd;
123
125
  }
124
126
  nextOffsetIfd = this.readIfd(nextOffsetIfd, lastView);
125
127
  }
126
- await Promise.all(this.images.map((i) => i.init()));
128
+ await Promise.all(this.images.map((i) => i.init(true, options)));
127
129
  this.isInitialized = true;
128
130
  return this;
129
131
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cogeotiff/core",
3
- "version": "9.3.0",
3
+ "version": "9.5.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/blacha/cogeotiff.git",
@@ -17,11 +17,12 @@
17
17
  "scripts": {
18
18
  "test": "node --test \"build/**/*.test.js\""
19
19
  },
20
+ "dependencies": {},
20
21
  "devDependencies": {
21
22
  "@types/node": "^20.0.0"
22
23
  },
23
24
  "publishConfig": {
24
25
  "access": "public"
25
26
  },
26
- "gitHead": "82b82e9d9f8996dfb0cb48827c143e134acc9865"
27
+ "gitHead": "c489ebab2136a779a705bf1dedebbc250e17a747"
27
28
  }
@@ -7,6 +7,9 @@ import { TestFileSource } from '../__benchmark__/source.file.js';
7
7
  import { SourceMemory } from '../__benchmark__/source.memory.js';
8
8
  import { TiffMimeType } from '../const/tiff.mime.js';
9
9
  import { Photometric, TiffTag } from '../const/tiff.tag.id.js';
10
+ import { TiffTagValueType } from '../const/tiff.tag.value.js';
11
+ import type { Tag } from '../read/tiff.tag.js';
12
+ import { TiffImage } from '../tiff.image.js';
10
13
  import { Tiff } from '../tiff.js';
11
14
  import { ByteSize } from '../util/bytes.js';
12
15
 
@@ -214,3 +217,61 @@ describe('CogStrip', () => {
214
217
  assert.equal(stripB?.bytes.byteLength, 152);
215
218
  });
216
219
  });
220
+
221
+ describe('TiffImage.noData', () => {
222
+ /** Create a TiffImage with a specific GdalNoData tag value for testing */
223
+ function imageWithNoData(value: string): TiffImage {
224
+ const tags = new Map<TiffTag, Tag>();
225
+ tags.set(TiffTag.GdalNoData, {
226
+ id: TiffTag.GdalNoData,
227
+ name: 'GdalNoData',
228
+ tagOffset: 0,
229
+ count: 1,
230
+ dataType: TiffTagValueType.Ascii,
231
+ type: 'inline',
232
+ value,
233
+ });
234
+ return new TiffImage(null as unknown as Tiff, 0, tags);
235
+ }
236
+
237
+ it('should return null when GdalNoData tag is absent', () => {
238
+ const img = new TiffImage(null as unknown as Tiff, 0, new Map());
239
+ assert.equal(img.noData, null);
240
+ });
241
+
242
+ it('should parse a negative nodata value', () => {
243
+ const img = imageWithNoData('-9999');
244
+ assert.equal(img.noData, -9999);
245
+ });
246
+
247
+ it('should parse nodata value of "0" (falsy string)', () => {
248
+ const img = imageWithNoData('0');
249
+ assert.equal(img.noData, 0);
250
+ });
251
+
252
+ it('should parse a positive nodata value', () => {
253
+ const img = imageWithNoData('255');
254
+ assert.equal(img.noData, 255);
255
+ });
256
+
257
+ it('should parse a floating point nodata value', () => {
258
+ const img = imageWithNoData('-3.4028234663852886e+38');
259
+ assert.equal(img.noData, -3.4028234663852886e38);
260
+ });
261
+
262
+ it('should throw when lazy tag value is undefined (not yet fetched)', () => {
263
+ const tags = new Map<TiffTag, Tag>();
264
+ tags.set(TiffTag.GdalNoData, {
265
+ id: TiffTag.GdalNoData,
266
+ name: 'GdalNoData',
267
+ tagOffset: 0,
268
+ count: 1,
269
+ dataType: TiffTagValueType.Ascii,
270
+ type: 'lazy',
271
+ dataOffset: 0,
272
+ value: undefined,
273
+ });
274
+ const img = new TiffImage(null as unknown as Tiff, 0, tags);
275
+ assert.throws(() => img.noData, /GdalNoData tag is not loaded/);
276
+ });
277
+ });
@@ -19,6 +19,12 @@ function validate(tif: Tiff): void {
19
19
  assert.deepEqual(firstTif.size, { width: 64, height: 64 });
20
20
  }
21
21
 
22
+ describe('TiffTag', () => {
23
+ it('should have the correct ModelTransformation tag id', () => {
24
+ assert.equal(TiffTag.ModelTransformation, 34264);
25
+ });
26
+ });
27
+
22
28
  describe('CogRead', () => {
23
29
  it('should read big endian', async () => {
24
30
  const source = new TestFileSource(new URL('../../data/big.endian.tiff', import.meta.url));
@@ -273,4 +279,18 @@ describe('CogRead', () => {
273
279
  [797, 861, 925, 993, 1057, 1121, 1189, 1253, 1317, 1385, 1449, 1513, 1577, 1641, 1705, 1769],
274
280
  );
275
281
  });
282
+
283
+ it('should load a file with a model transformation tag', async () => {
284
+ const cogSourceFile = new URL('../../data/model_transformation.tif', import.meta.url);
285
+
286
+ const buf = await readFile(cogSourceFile);
287
+ const source = new SourceMemory(buf);
288
+
289
+ const tiff = await Tiff.create(source);
290
+ assert.equal(tiff.images.length, 1);
291
+ assert.deepEqual(
292
+ tiff.images[0].tags.get(TiffTag.ModelTransformation)?.value,
293
+ [10, 0, 0, 418080, 0, 10, 0, 4423680, 0, 0, 0, 0, 0, 0, 0, 1],
294
+ );
295
+ });
276
296
  });
@@ -5,6 +5,9 @@ import { Compression } from './tiff.tag.id.js';
5
5
  */
6
6
  export enum TiffMimeType {
7
7
  None = 'application/octet-stream',
8
+ Jbig = 'image/jbig',
9
+ Dcs = 'image/x-kodak-dcs',
10
+ PackBits = 'application/packbits',
8
11
  Jpeg = 'image/jpeg',
9
12
  Jp2000 = 'image/jp2',
10
13
  JpegXl = 'image/jpegxl',
@@ -29,6 +32,26 @@ export const TiffCompressionMimeType: Record<Compression, TiffMimeType> = {
29
32
  [Compression.Zstd]: TiffMimeType.Zstd,
30
33
  [Compression.Webp]: TiffMimeType.Webp,
31
34
  [Compression.JpegXl]: TiffMimeType.JpegXl,
35
+ [Compression.Ccittrle]: TiffMimeType.None,
36
+ [Compression.CcittT4]: TiffMimeType.None,
37
+ [Compression.CcittT6]: TiffMimeType.None,
38
+ [Compression.T85]: TiffMimeType.Jbig,
39
+ [Compression.T43]: TiffMimeType.Jbig,
40
+ [Compression.Next]: TiffMimeType.None,
41
+ [Compression.Ccittrlew]: TiffMimeType.None,
42
+ [Compression.PackBits]: TiffMimeType.PackBits,
43
+ [Compression.ThunderScan]: TiffMimeType.None,
44
+ [Compression.It8ctpad]: TiffMimeType.None,
45
+ [Compression.It8lw]: TiffMimeType.None,
46
+ [Compression.It8mp]: TiffMimeType.None,
47
+ [Compression.It8bl]: TiffMimeType.None,
48
+ [Compression.PixarFilm]: TiffMimeType.None,
49
+ [Compression.PixarLog]: TiffMimeType.None,
50
+ [Compression.Dcs]: TiffMimeType.Dcs,
51
+ [Compression.Jbig]: TiffMimeType.Jbig,
52
+ [Compression.SgiLog]: TiffMimeType.None,
53
+ [Compression.SgiLog24]: TiffMimeType.None,
54
+ [Compression.JpegXlDng17]: TiffMimeType.JpegXl,
32
55
  };
33
56
 
34
57
  /**
@@ -66,17 +66,37 @@ export enum OldSubFileType {
66
66
  /** Tiff compression types */
67
67
  export enum Compression {
68
68
  None = 1,
69
+ Ccittrle = 2,
70
+ CcittT4 = 3,
71
+ CcittT6 = 4,
69
72
  Lzw = 5,
70
73
  Jpeg6 = 6,
71
74
  Jpeg = 7,
72
75
  DeflateOther = 8,
76
+ T85 = 9,
77
+ T43 = 10,
78
+ Next = 32766,
79
+ Ccittrlew = 32771,
80
+ PackBits = 32773,
81
+ ThunderScan = 32809,
82
+ It8ctpad = 32895,
83
+ It8lw = 32896,
84
+ It8mp = 32897,
85
+ It8bl = 32898,
86
+ PixarFilm = 32908,
87
+ PixarLog = 32909,
73
88
  Deflate = 32946,
74
- Jp2000 = 3417,
89
+ Dcs = 32947,
90
+ Jbig = 34661,
91
+ SgiLog = 34676,
92
+ SgiLog24 = 34677,
93
+ Jp2000 = 34712,
75
94
  Lerc = 34887,
76
95
  Lzma = 34925,
77
96
  Zstd = 50000,
78
97
  Webp = 50001,
79
98
  JpegXl = 50002,
99
+ JpegXlDng17 = 52546,
80
100
  }
81
101
 
82
102
  export enum PlanarConfiguration {
@@ -301,7 +321,7 @@ export enum TiffTag {
301
321
  * 0, 0, 0, 1]
302
322
  * ```
303
323
  */
304
- ModelTransformation = 34744,
324
+ ModelTransformation = 34264,
305
325
  /**
306
326
  * List of GeoTiff tags
307
327
  * {@link TiffTagGeo}
@@ -436,9 +456,30 @@ export interface TiffTagType {
436
456
  [TiffTag.SampleFormat]: SampleFormat[];
437
457
  [TiffTag.GdalMetadata]: string;
438
458
  [TiffTag.GdalNoData]: string;
439
- [TiffTag.ModelPixelScale]: number[];
459
+ // Always [ScaleX, ScaleY, ScaleZ]
460
+ // https://web.archive.org/web/20240329145238/https://www.awaresystems.be/imaging/tiff/tifftags/modelpixelscaletag.html
461
+ [TiffTag.ModelPixelScale]: [scaleX: number, scaleY: number, scaleZ: number];
440
462
  [TiffTag.ModelTiePoint]: number[];
441
- [TiffTag.ModelTransformation]: number[];
463
+ // Always 16 numbers
464
+ // https://web.archive.org/web/20240329145255/https://www.awaresystems.be/imaging/tiff/tifftags/modeltransformationtag.html
465
+ [TiffTag.ModelTransformation]: [
466
+ number,
467
+ number,
468
+ number,
469
+ number,
470
+ number,
471
+ number,
472
+ number,
473
+ number,
474
+ number,
475
+ number,
476
+ number,
477
+ number,
478
+ number,
479
+ number,
480
+ number,
481
+ number,
482
+ ];
442
483
  [TiffTag.GeoKeyDirectory]: number[];
443
484
  [TiffTag.GeoDoubleParams]: number[];
444
485
  [TiffTag.GeoAsciiParams]: string;
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */
2
2
  import { TiffTag, TiffTagConvertArray } from '../const/tiff.tag.id.js';
3
3
  import { TiffTagValueType } from '../const/tiff.tag.value.js';
4
- import type { Tiff } from '../tiff.js';
4
+ import type { Tiff, TiffFetchOptions } from '../tiff.js';
5
5
  import { getUint, getUint64 } from '../util/bytes.js';
6
6
  import type { DataViewOffset } from './data.view.offset.js';
7
7
  import { hasBytes } from './data.view.offset.js';
@@ -205,11 +205,11 @@ export function createTag(tiff: Tiff, view: DataViewOffset, offset: number): Tag
205
205
  }
206
206
 
207
207
  /** Fetch the value from a {@link TagLazy} tag */
208
- export async function fetchLazy<T>(tag: TagLazy<T>, tiff: Tiff): Promise<T> {
208
+ export async function fetchLazy<T>(tag: TagLazy<T>, tiff: Tiff, options?: TiffFetchOptions): Promise<T> {
209
209
  if (tag.value != null) return tag.value;
210
210
  const dataTypeSize = getTiffTagSize(tag.dataType);
211
211
  const dataLength = dataTypeSize * tag.count;
212
- const bytes = await tiff.source.fetch(tag.dataOffset, dataLength);
212
+ const bytes = await tiff.source.fetch(tag.dataOffset, dataLength, options);
213
213
  const view = new DataView(bytes);
214
214
  tag.value = readValue(tiff, tag.id, view, 0, tag.dataType, tag.count);
215
215
  return tag.value as T;
@@ -218,11 +218,15 @@ export async function fetchLazy<T>(tag: TagLazy<T>, tiff: Tiff): Promise<T> {
218
218
  /**
219
219
  * Fetch all the values from a {@link TagOffset}
220
220
  */
221
- export async function fetchAllOffsets(tiff: Tiff, tag: TagOffset): Promise<TagOffset['value']> {
221
+ export async function fetchAllOffsets(
222
+ tiff: Tiff,
223
+ tag: TagOffset,
224
+ options?: TiffFetchOptions,
225
+ ): Promise<TagOffset['value']> {
222
226
  const dataTypeSize = getTiffTagSize(tag.dataType);
223
227
 
224
228
  if (tag.view == null) {
225
- const bytes = await tiff.source.fetch(tag.dataOffset, dataTypeSize * tag.count);
229
+ const bytes = await tiff.source.fetch(tag.dataOffset, dataTypeSize * tag.count, options);
226
230
  tag.view = new DataView(bytes) as DataViewOffset;
227
231
  tag.view.sourceOffset = tag.dataOffset;
228
232
  }
@@ -241,13 +245,18 @@ export function setBytes(tag: TagOffset, view: DataViewOffset): void {
241
245
  }
242
246
 
243
247
  /** Partially fetch the values of a {@link TagOffset} and return the value for the offset */
244
- export async function getValueAt(tiff: Tiff, tag: TagOffset, index: number): Promise<number> {
248
+ export async function getValueAt(
249
+ tiff: Tiff,
250
+ tag: TagOffset,
251
+ index: number,
252
+ options?: TiffFetchOptions,
253
+ ): Promise<number> {
245
254
  if (index > tag.count || index < 0) throw new Error('TagOffset: out of bounds :' + index);
246
255
  if (tag.value[index] != null) return tag.value[index];
247
256
  const dataTypeSize = getTiffTagSize(tag.dataType);
248
257
 
249
258
  if (tag.view == null) {
250
- const bytes = await tiff.source.fetch(tag.dataOffset + index * dataTypeSize, dataTypeSize);
259
+ const bytes = await tiff.source.fetch(tag.dataOffset + index * dataTypeSize, dataTypeSize, options);
251
260
  const view = new DataView(bytes);
252
261
  // Skip type conversion to array by using undefined tiff tag id
253
262
  const value = readValue(tiff, undefined, view, 0, tag.dataType, 1);
package/src/tiff.image.ts CHANGED
@@ -3,7 +3,7 @@ import type { TiffTagGeoType, TiffTagType } from './const/tiff.tag.id.js';
3
3
  import { Compression, ModelTypeCode, SubFileType, TiffTag, TiffTagGeo } from './const/tiff.tag.id.js';
4
4
  import { fetchAllOffsets, fetchLazy, getValueAt, getValueAtSync } from './read/tiff.tag.factory.js';
5
5
  import type { Tag, TagInline, TagOffset } from './read/tiff.tag.js';
6
- import type { Tiff } from './tiff.js';
6
+ import type { Tiff, TiffFetchOptions } from './tiff.js';
7
7
  import { getUint } from './util/bytes.js';
8
8
  import type { BoundingBox, Size } from './vector.js';
9
9
 
@@ -72,15 +72,15 @@ export class TiffImage {
72
72
  *
73
73
  * @param loadGeoTags Whether to load the GeoKeyDirectory and unpack it
74
74
  */
75
- async init(loadGeoTags = true): Promise<void> {
75
+ async init(loadGeoTags = true, options?: TiffFetchOptions): Promise<void> {
76
76
  const requiredTags: Promise<unknown>[] = [];
77
77
  ImportantTags.forEach((tag) => {
78
- requiredTags.push(this.fetch(tag));
78
+ requiredTags.push(this.fetch(tag, options));
79
79
  });
80
80
 
81
81
  if (loadGeoTags) {
82
82
  ImportantGeoTags.forEach((tag) => {
83
- requiredTags.push(this.fetch(tag));
83
+ requiredTags.push(this.fetch(tag, options));
84
84
  });
85
85
  }
86
86
 
@@ -132,13 +132,13 @@ export class TiffImage {
132
132
  *
133
133
  * @param tag tag to fetch
134
134
  */
135
- public async fetch<T extends keyof TiffTagType>(tag: T): Promise<TiffTagType[T] | null> {
135
+ public async fetch<T extends keyof TiffTagType>(tag: T, options?: TiffFetchOptions): Promise<TiffTagType[T] | null> {
136
136
  const sourceTag = this.tags.get(tag);
137
137
  if (sourceTag == null) return null;
138
138
  if (sourceTag.type === 'inline') return sourceTag.value as TiffTagType[T];
139
- if (sourceTag.type === 'lazy') return fetchLazy(sourceTag, this.tiff) as Promise<TiffTagType[T]>;
139
+ if (sourceTag.type === 'lazy') return fetchLazy(sourceTag, this.tiff, options) as Promise<TiffTagType[T]>;
140
140
  if (sourceTag.isLoaded) return sourceTag.value as TiffTagType[T];
141
- if (sourceTag.type === 'offset') return fetchAllOffsets(this.tiff, sourceTag) as Promise<TiffTagType[T]>;
141
+ if (sourceTag.type === 'offset') return fetchAllOffsets(this.tiff, sourceTag, options) as Promise<TiffTagType[T]>;
142
142
  throw new Error('Cannot fetch:' + tag);
143
143
  }
144
144
  /**
@@ -164,7 +164,7 @@ export class TiffImage {
164
164
  get noData(): number | null {
165
165
  const tag = this.tags.get(TiffTag.GdalNoData);
166
166
  if (tag == null) return null;
167
- if (tag.value) return Number(tag.value);
167
+ if (tag.value != null) return Number(tag.value);
168
168
  throw new Error('GdalNoData tag is not loaded');
169
169
  }
170
170
 
@@ -439,7 +439,10 @@ export class TiffImage {
439
439
  *
440
440
  * @param index Strip index to read
441
441
  */
442
- async getStrip(index: number): Promise<{ mimeType: TiffMimeType; bytes: ArrayBuffer } | null> {
442
+ async getStrip(
443
+ index: number,
444
+ options?: TiffFetchOptions,
445
+ ): Promise<{ mimeType: TiffMimeType; bytes: ArrayBuffer } | null> {
443
446
  if (this.isTiled()) throw new Error('Cannot read stripes, tiff is tiled: ' + index);
444
447
 
445
448
  const byteCounts = this.tags.get(TiffTag.StripByteCounts) as TagOffset;
@@ -448,10 +451,10 @@ export class TiffImage {
448
451
  if (index >= byteCounts.count) throw new Error('Cannot read strip, index out of bounds');
449
452
 
450
453
  const [byteCount, offset] = await Promise.all([
451
- getOffset(this.tiff, offsets, index),
452
- getOffset(this.tiff, byteCounts, index),
454
+ getOffset(this.tiff, offsets, index, options),
455
+ getOffset(this.tiff, byteCounts, index, options),
453
456
  ]);
454
- return this.getBytes(byteCount, offset);
457
+ return this.getBytes(byteCount, offset, options);
455
458
  }
456
459
 
457
460
  /** The jpeg header is stored in the IFD, read the JPEG header and adjust the byte array to include it */
@@ -476,7 +479,7 @@ export class TiffImage {
476
479
  async getBytes(
477
480
  offset: number,
478
481
  byteCount: number,
479
- options?: { signal?: AbortSignal },
482
+ options?: TiffFetchOptions,
480
483
  ): Promise<{ mimeType: TiffMimeType; bytes: ArrayBuffer; compression: Compression } | null> {
481
484
  if (byteCount === 0) return null;
482
485
 
@@ -504,7 +507,7 @@ export class TiffImage {
504
507
  async getTile(
505
508
  x: number,
506
509
  y: number,
507
- options?: { signal?: AbortSignal },
510
+ options?: TiffFetchOptions,
508
511
  ): Promise<{ mimeType: TiffMimeType; bytes: ArrayBuffer; compression: Compression } | null> {
509
512
  const size = this.size;
510
513
  const tiles = this.tileSize;
@@ -523,7 +526,7 @@ export class TiffImage {
523
526
  const totalTiles = nxTiles * nyTiles;
524
527
  if (idx >= totalTiles) throw new Error(`Tile index is outside of tile range: ${idx} >= ${totalTiles}`);
525
528
 
526
- const { offset, imageSize } = await this.getTileSize(idx);
529
+ const { offset, imageSize } = await this.getTileSize(idx, options);
527
530
 
528
531
  return this.getBytes(offset, imageSize, options);
529
532
  }
@@ -539,7 +542,7 @@ export class TiffImage {
539
542
  *
540
543
  * @returns if the tile exists and has data
541
544
  */
542
- async hasTile(x: number, y: number): Promise<boolean> {
545
+ async hasTile(x: number, y: number, options?: TiffFetchOptions): Promise<boolean> {
543
546
  const tiles = this.tileSize;
544
547
  const size = this.size;
545
548
 
@@ -550,7 +553,7 @@ export class TiffImage {
550
553
  const nxTiles = Math.ceil(size.width / tiles.width);
551
554
  if (x >= nxTiles || y >= nyTiles) return false;
552
555
  const idx = y * nxTiles + x;
553
- const ret = await this.getTileSize(idx);
556
+ const ret = await this.getTileSize(idx, options);
554
557
  return ret.offset > 0;
555
558
  }
556
559
 
@@ -562,7 +565,7 @@ export class TiffImage {
562
565
  * @param index index in the tile array
563
566
  * @returns Offset and byteCount for the tile
564
567
  */
565
- async getTileSize(index: number): Promise<{ offset: number; imageSize: number }> {
568
+ async getTileSize(index: number, options?: TiffFetchOptions): Promise<{ offset: number; imageSize: number }> {
566
569
  // If both the tile offset and tile byte counts are loaded,
567
570
  // we can get the offset and byte count synchronously without needing to fetch any additional data
568
571
  const byteCounts = this.tags.get(TiffTag.TileByteCounts) as TagOffset | TagInline<number[]>;
@@ -574,29 +577,34 @@ export class TiffImage {
574
577
  // the few bytes leading up to the tile
575
578
  const leaderBytes = this.tiff.options?.tileLeaderByteSize;
576
579
  if (leaderBytes) {
577
- const offset = tileOffset ?? (await getOffset(this.tiff, this.tileOffset, index));
580
+ const offset = tileOffset ?? (await getOffset(this.tiff, this.tileOffset, index, options));
578
581
  // Sparse tiff no data found
579
582
  if (offset === 0) return { offset: 0, imageSize: 0 };
580
583
 
581
584
  // This fetch will generally load in the bytes needed for the image too
582
585
  // provided the image size is less than the size of a chunk
583
- const bytes = await this.tiff.source.fetch(offset - leaderBytes, leaderBytes);
586
+ const bytes = await this.tiff.source.fetch(offset - leaderBytes, leaderBytes, options);
584
587
  return { offset, imageSize: getUint(new DataView(bytes), 0, leaderBytes, this.tiff.isLittleEndian) };
585
588
  }
586
589
 
587
590
  if (byteCounts == null) throw new Error('No tile byte counts found');
588
591
  const [offset, imageSize] = await Promise.all([
589
- tileOffset ?? getOffset(this.tiff, this.tileOffset, index),
590
- tileSize ?? getOffset(this.tiff, byteCounts, index),
592
+ tileOffset ?? getOffset(this.tiff, this.tileOffset, index, options),
593
+ tileSize ?? getOffset(this.tiff, byteCounts, index, options),
591
594
  ]);
592
595
  return { offset, imageSize };
593
596
  }
594
597
  }
595
598
 
596
- function getOffset(tiff: Tiff, x: TagOffset | TagInline<number[]>, index: number): number | Promise<number> {
599
+ function getOffset(
600
+ tiff: Tiff,
601
+ x: TagOffset | TagInline<number[]>,
602
+ index: number,
603
+ options?: TiffFetchOptions,
604
+ ): number | Promise<number> {
597
605
  const val = getOffsetSync(tiff, x, index);
598
606
  if (val != null) return Promise.resolve(val);
599
- return getValueAt(tiff, x as TagOffset, index);
607
+ return getValueAt(tiff, x as TagOffset, index, options);
600
608
  }
601
609
 
602
610
  function getOffsetSync(tiff: Tiff, x: TagOffset | TagInline<number[]>, index: number): number | null {