@cogeotiff/core 8.1.1 → 9.0.1

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.
Files changed (71) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +45 -7
  3. package/build/__benchmark__/cog.read.benchmark.js +5 -5
  4. package/build/__benchmark__/cog.read.benchmark.js.map +1 -1
  5. package/build/__benchmark__/source.file.js +1 -1
  6. package/build/__benchmark__/source.file.js.map +1 -1
  7. package/build/__benchmark__/source.memory.d.ts +3 -0
  8. package/build/__benchmark__/source.memory.js +13 -0
  9. package/build/__benchmark__/source.memory.js.map +1 -1
  10. package/build/__test__/cog.image.test.js +14 -9
  11. package/build/__test__/cog.image.test.js.map +1 -1
  12. package/build/__test__/cog.read.test.js +42 -17
  13. package/build/__test__/cog.read.test.js.map +1 -1
  14. package/build/__test__/example.js +2 -2
  15. package/build/__test__/example.js.map +1 -1
  16. package/build/const/index.d.ts +1 -1
  17. package/build/const/index.js +1 -1
  18. package/build/const/index.js.map +1 -1
  19. package/build/const/tiff.mime.d.ts +11 -5
  20. package/build/const/tiff.mime.js +27 -14
  21. package/build/const/tiff.mime.js.map +1 -1
  22. package/build/const/tiff.tag.id.d.ts +603 -41
  23. package/build/const/tiff.tag.id.js +479 -50
  24. package/build/const/tiff.tag.id.js.map +1 -1
  25. package/build/const/tiff.tag.value.js +1 -1
  26. package/build/const/tiff.tag.value.js.map +1 -1
  27. package/build/index.d.ts +6 -5
  28. package/build/index.js +6 -4
  29. package/build/index.js.map +1 -1
  30. package/build/read/tiff.gdal.d.ts +1 -1
  31. package/build/read/tiff.gdal.js +1 -1
  32. package/build/read/tiff.gdal.js.map +1 -1
  33. package/build/read/tiff.ifd.config.js +1 -1
  34. package/build/read/tiff.ifd.config.js.map +1 -1
  35. package/build/read/tiff.tag.d.ts +3 -0
  36. package/build/read/tiff.tag.factory.d.ts +5 -5
  37. package/build/read/tiff.tag.factory.js +22 -13
  38. package/build/read/tiff.tag.factory.js.map +1 -1
  39. package/build/read/tiff.value.reader.d.ts +1 -1
  40. package/build/read/tiff.value.reader.js.map +1 -1
  41. package/build/{cog.tiff.d.ts → tiff.d.ts} +12 -10
  42. package/build/{cog.tiff.image.d.ts → tiff.image.d.ts} +69 -27
  43. package/build/{cog.tiff.image.js → tiff.image.js} +125 -82
  44. package/build/tiff.image.js.map +1 -0
  45. package/build/{cog.tiff.js → tiff.js} +19 -11
  46. package/build/tiff.js.map +1 -0
  47. package/build/util/bytes.d.ts +4 -2
  48. package/build/util/bytes.js +5 -2
  49. package/build/util/bytes.js.map +1 -1
  50. package/package.json +2 -2
  51. package/src/__benchmark__/cog.read.benchmark.ts +6 -5
  52. package/src/__benchmark__/source.file.ts +2 -1
  53. package/src/__benchmark__/source.memory.ts +10 -0
  54. package/src/__test__/cog.image.test.ts +17 -9
  55. package/src/__test__/cog.read.test.ts +53 -18
  56. package/src/__test__/example.ts +3 -2
  57. package/src/const/index.ts +1 -1
  58. package/src/const/tiff.mime.ts +28 -14
  59. package/src/const/tiff.tag.id.ts +758 -131
  60. package/src/const/tiff.tag.value.ts +16 -16
  61. package/src/index.ts +20 -5
  62. package/src/read/tiff.gdal.ts +1 -1
  63. package/src/read/tiff.ifd.config.ts +1 -1
  64. package/src/read/tiff.tag.factory.ts +33 -17
  65. package/src/read/tiff.tag.ts +3 -0
  66. package/src/read/tiff.value.reader.ts +1 -1
  67. package/src/{cog.tiff.image.ts → tiff.image.ts} +137 -92
  68. package/src/{cog.tiff.ts → tiff.ts} +22 -17
  69. package/src/util/bytes.ts +5 -1
  70. package/build/cog.tiff.image.js.map +0 -1
  71. package/build/cog.tiff.js.map +0 -1
@@ -1,18 +1,18 @@
1
1
  export enum TiffTagValueType {
2
- Uint8 = 0x0001,
3
- Ascii = 0x0002,
4
- Uint16 = 0x0003,
5
- Uint32 = 0x0004,
6
- Rational = 0x0005,
7
- Int8 = 0x0006,
8
- Undefined = 0x0007,
9
- Int16 = 0x0008,
10
- Int32 = 0x0009,
11
- SignedRational = 0x000a,
12
- Float32 = 0x000b,
13
- Float64 = 0x000c,
14
- // introduced by BigTIFF
15
- Uint64 = 0x0010,
16
- Int64 = 0x0011,
17
- Ifd8 = 0x0012,
2
+ Uint8 = 1,
3
+ Ascii = 2,
4
+ Uint16 = 3,
5
+ Uint32 = 4,
6
+ Rational = 5,
7
+ Int8 = 6,
8
+ Undefined = 7,
9
+ Int16 = 8,
10
+ Int32 = 9,
11
+ SignedRational = 10,
12
+ Float32 = 11,
13
+ Float64 = 12,
14
+ // BigTiff
15
+ Uint64 = 16,
16
+ Int64 = 17,
17
+ Ifd8 = 18,
18
18
  }
package/src/index.ts CHANGED
@@ -1,12 +1,27 @@
1
- export { CogTiff } from './cog.tiff.js';
2
- export { CogTiffImage } from './cog.tiff.image.js';
3
1
  export { TiffEndian } from './const/tiff.endian.js';
4
- export { TiffCompression, TiffMimeType } from './const/tiff.mime.js';
5
- export { TiffTagGeo, TiffTag } from './const/tiff.tag.id.js';
2
+ export { TiffCompressionMimeType as TiffCompression, TiffMimeType } from './const/tiff.mime.js';
3
+ export { TiffTag, TiffTagGeo, TiffTagGeoType, TiffTagType } from './const/tiff.tag.id.js';
6
4
  export { TiffTagValueType } from './const/tiff.tag.value.js';
7
5
  export { TiffVersion } from './const/tiff.version.js';
8
- export { TagInline, TagLazy, TagOffset, Tag } from './read/tiff.tag.js';
6
+ export { Tag, TagInline, TagLazy, TagOffset } from './read/tiff.tag.js';
9
7
  export { getTiffTagSize } from './read/tiff.value.reader.js';
10
8
  export { Source } from './source.js';
9
+ export { TiffImage } from './tiff.image.js';
10
+ export { Tiff } from './tiff.js';
11
11
  export { toHex } from './util/util.hex.js';
12
12
  export type { BoundingBox, Point, Size, Vector } from './vector.js';
13
+
14
+ // Tag value constants
15
+ export {
16
+ AngularUnit,
17
+ Compression,
18
+ LinearUnit,
19
+ ModelTypeCode,
20
+ OldSubFileType,
21
+ Orientation,
22
+ Photometric,
23
+ PlanarConfiguration,
24
+ RasterTypeKey,
25
+ SampleFormat,
26
+ SubFileType,
27
+ } from './const/tiff.tag.id.js';
@@ -22,7 +22,7 @@ export enum GhostOptionTileLeader {
22
22
  * GDAL has made a ghost set of options for Tiff files
23
23
  * this class represents the optimizations that GDAL has applied
24
24
  */
25
- export class CogTifGhostOptions {
25
+ export class TiffGhostOptions {
26
26
  options: Map<string, string> = new Map();
27
27
 
28
28
  /**
@@ -1,5 +1,5 @@
1
- import { ByteSize } from '../util/bytes.js';
2
1
  import { TiffVersion } from '../const/tiff.version.js';
2
+ import { ByteSize } from '../util/bytes.js';
3
3
 
4
4
  export const TagTiffConfig: TiffIfdConfig = {
5
5
  version: TiffVersion.Tiff,
@@ -1,6 +1,6 @@
1
- import { CogTiff } from '../cog.tiff.js';
2
- import { TiffTag } from '../const/tiff.tag.id.js';
1
+ import { TiffTag, TiffTagConvertArray } from '../const/tiff.tag.id.js';
3
2
  import { TiffTagValueType } from '../const/tiff.tag.value.js';
3
+ import { Tiff } from '../tiff.js';
4
4
  import { getUint, getUint64 } from '../util/bytes.js';
5
5
  import { DataViewOffset, hasBytes } from './data.view.offset.js';
6
6
  import { Tag, TagLazy, TagOffset } from './tiff.tag.js';
@@ -54,11 +54,24 @@ function readTagValue(
54
54
  }
55
55
  }
56
56
 
57
- function readValue<T>(tiff: CogTiff, bytes: DataView, offset: number, type: TiffTagValueType, count: number): T {
57
+ function readValue<T>(
58
+ tiff: Tiff,
59
+ tagId: TiffTag | undefined,
60
+ bytes: DataView,
61
+ offset: number,
62
+ type: TiffTagValueType,
63
+ count: number,
64
+ ): T {
58
65
  const typeSize = getTiffTagSize(type);
59
66
  const dataLength = count * typeSize;
60
67
 
61
- if (count === 1) return readTagValue(type, bytes, offset, tiff.isLittleEndian) as unknown as T;
68
+ if (count === 1) {
69
+ const val = readTagValue(type, bytes, offset, tiff.isLittleEndian) as unknown as T;
70
+ // Force some single values to be arrays eg BitsPerSample
71
+ // makes it easier to not check for number | number[]
72
+ if (tagId && TiffTagConvertArray[tagId]) return [val] as T;
73
+ return val;
74
+ }
62
75
 
63
76
  switch (type) {
64
77
  case TiffTagValueType.Ascii:
@@ -85,7 +98,7 @@ function readValue<T>(tiff: CogTiff, bytes: DataView, offset: number, type: Tiff
85
98
  * @param view Bytes to read from
86
99
  * @param offset Offset in the dataview to read a tag
87
100
  */
88
- export function createTag(tiff: CogTiff, view: DataViewOffset, offset: number): Tag<unknown> {
101
+ export function createTag(tiff: Tiff, view: DataViewOffset, offset: number): Tag<unknown> {
89
102
  const tagId = view.getUint16(offset + 0, tiff.isLittleEndian);
90
103
 
91
104
  const dataType = view.getUint16(offset + 2, tiff.isLittleEndian) as TiffTagValueType;
@@ -95,8 +108,8 @@ export function createTag(tiff: CogTiff, view: DataViewOffset, offset: number):
95
108
 
96
109
  // Tag value is inline read the value
97
110
  if (dataLength <= tiff.ifdConfig.pointer) {
98
- const value = readValue(tiff, view, offset + 4 + tiff.ifdConfig.pointer, dataType, dataCount);
99
- return { type: 'inline', id: tagId, count: dataCount, value, dataType, tagOffset: offset };
111
+ const value = readValue(tiff, tagId, view, offset + 4 + tiff.ifdConfig.pointer, dataType, dataCount);
112
+ return { type: 'inline', id: tagId, name: TiffTag[tagId], count: dataCount, value, dataType, tagOffset: offset };
100
113
  }
101
114
 
102
115
  const dataOffset = getUint(view, offset + 4 + tiff.ifdConfig.pointer, tiff.ifdConfig.pointer, tiff.isLittleEndian);
@@ -108,6 +121,7 @@ export function createTag(tiff: CogTiff, view: DataViewOffset, offset: number):
108
121
  const tag: TagOffset = {
109
122
  type: 'offset',
110
123
  id: tagId,
124
+ name: TiffTag[tagId],
111
125
  count: dataCount,
112
126
  dataType,
113
127
  dataOffset,
@@ -122,28 +136,28 @@ export function createTag(tiff: CogTiff, view: DataViewOffset, offset: number):
122
136
 
123
137
  // If we already have the bytes in the view read them in
124
138
  if (hasBytes(view, dataOffset, dataLength)) {
125
- const value = readValue(tiff, view, dataOffset - view.sourceOffset, dataType, dataCount);
126
- return { type: 'inline', id: tagId, count: dataCount, value, dataType, tagOffset: offset };
139
+ const value = readValue(tiff, tagId, view, dataOffset - view.sourceOffset, dataType, dataCount);
140
+ return { type: 'inline', id: tagId, name: TiffTag[tagId], count: dataCount, value, dataType, tagOffset: offset };
127
141
  }
128
142
 
129
- return { type: 'lazy', id: tagId, count: dataCount, dataOffset, dataType, tagOffset: offset };
143
+ return { type: 'lazy', id: tagId, name: TiffTag[tagId], count: dataCount, dataOffset, dataType, tagOffset: offset };
130
144
  }
131
145
 
132
146
  /** Fetch the value from a {@link TagLazy} tag */
133
- export async function fetchLazy<T>(tag: TagLazy<T>, tiff: CogTiff): Promise<T> {
147
+ export async function fetchLazy<T>(tag: TagLazy<T>, tiff: Tiff): Promise<T> {
134
148
  if (tag.value != null) return tag.value;
135
149
  const dataTypeSize = getTiffTagSize(tag.dataType);
136
150
  const dataLength = dataTypeSize * tag.count;
137
151
  const bytes = await tiff.source.fetch(tag.dataOffset, dataLength);
138
152
  const view = new DataView(bytes);
139
- tag.value = readValue(tiff, view, 0, tag.dataType, tag.count);
153
+ tag.value = readValue(tiff, tag.id, view, 0, tag.dataType, tag.count);
140
154
  return tag.value as T;
141
155
  }
142
156
 
143
157
  /**
144
158
  * Fetch all the values from a {@link TagOffset}
145
159
  */
146
- export async function fetchAllOffsets(tiff: CogTiff, tag: TagOffset): Promise<number[]> {
160
+ export async function fetchAllOffsets(tiff: Tiff, tag: TagOffset): Promise<number[]> {
147
161
  const dataTypeSize = getTiffTagSize(tag.dataType);
148
162
 
149
163
  if (tag.view == null) {
@@ -152,7 +166,7 @@ export async function fetchAllOffsets(tiff: CogTiff, tag: TagOffset): Promise<nu
152
166
  tag.view.sourceOffset = tag.dataOffset;
153
167
  }
154
168
 
155
- tag.value = readValue(tiff, tag.view, 0, tag.dataType, tag.count) as number[];
169
+ tag.value = readValue(tiff, tag.id, tag.view, 0, tag.dataType, tag.count) as number[];
156
170
  tag.isLoaded = true;
157
171
  return tag.value;
158
172
  }
@@ -165,7 +179,7 @@ export function setBytes(tag: TagOffset, view: DataViewOffset): void {
165
179
  }
166
180
 
167
181
  /** Partially fetch the values of a {@link TagOffset} and return the value for the offset */
168
- export async function getValueAt(tiff: CogTiff, tag: TagOffset, index: number): Promise<number> {
182
+ export async function getValueAt(tiff: Tiff, tag: TagOffset, index: number): Promise<number> {
169
183
  if (index > tag.count || index < 0) throw new Error('TagOffset: out of bounds :' + index);
170
184
  if (tag.value[index] != null) return tag.value[index];
171
185
  const dataTypeSize = getTiffTagSize(tag.dataType);
@@ -173,12 +187,14 @@ export async function getValueAt(tiff: CogTiff, tag: TagOffset, index: number):
173
187
  if (tag.view == null) {
174
188
  const bytes = await tiff.source.fetch(tag.dataOffset + index * dataTypeSize, dataTypeSize);
175
189
  const view = new DataView(bytes);
176
- const value = readValue(tiff, view, 0, tag.dataType, 1) as number;
190
+ // Skip type conversion to array by using undefined tiff tag id
191
+ const value = readValue(tiff, undefined, view, 0, tag.dataType, 1) as number;
177
192
  tag.value[index] = value;
178
193
  return value;
179
194
  }
180
195
 
181
- const value = readValue(tiff, tag.view, index * dataTypeSize, tag.dataType, 1) as number;
196
+ // Skip type conversion to array by using undefined tiff tag id
197
+ const value = readValue(tiff, undefined, tag.view, index * dataTypeSize, tag.dataType, 1) as number;
182
198
  tag.value[index] = value;
183
199
  return value;
184
200
  }
@@ -8,6 +8,8 @@ export type Tag<T = unknown> = TagLazy<T> | TagInline<T> | TagOffset;
8
8
  export interface TagBase {
9
9
  /** Id of the Tag */
10
10
  id: TiffTag;
11
+ /** Name of the tiff tag */
12
+ name: string;
11
13
  /** Offset in bytes to where this tag was read from */
12
14
  tagOffset: number;
13
15
  /** Number of values */
@@ -28,6 +30,7 @@ export interface TagLazy<T> extends TagBase {
28
30
  /** Tiff tag that's value is inside the IFD and is already read */
29
31
  export interface TagInline<T> extends TagBase {
30
32
  type: 'inline';
33
+ /** Value of the tag */
31
34
  value: T;
32
35
  }
33
36
 
@@ -1,5 +1,5 @@
1
- import { ByteSize } from '../util/bytes.js';
2
1
  import { TiffTagValueType } from '../const/tiff.tag.value.js';
2
+ import { ByteSize } from '../util/bytes.js';
3
3
 
4
4
  export function getTiffTagSize(fieldType: TiffTagValueType): ByteSize {
5
5
  switch (fieldType) {
@@ -1,10 +1,10 @@
1
- import { getUint } from './util/bytes.js';
2
- import { CogTiff } from './cog.tiff.js';
3
- import { TiffCompression, TiffMimeType } from './const/tiff.mime.js';
4
- import { TiffTag, TiffTagGeo } from './const/tiff.tag.id.js';
1
+ import { getCompressionMimeType, TiffCompressionMimeType, TiffMimeType } from './const/tiff.mime.js';
2
+ import { Compression, SubFileType, TiffTag, TiffTagGeo, TiffTagGeoType, TiffTagType } from './const/tiff.tag.id.js';
3
+ import { fetchAllOffsets, fetchLazy, getValueAt } from './read/tiff.tag.factory.js';
5
4
  import { Tag, TagInline, TagOffset } from './read/tiff.tag.js';
5
+ import { Tiff } from './tiff.js';
6
+ import { getUint } from './util/bytes.js';
6
7
  import { BoundingBox, Size } from './vector.js';
7
- import { fetchAllOffsets, fetchLazy, getValueAt } from './read/tiff.tag.factory.js';
8
8
 
9
9
  /** Invalid EPSG code */
10
10
  export const InvalidProjectionCode = 32767;
@@ -12,7 +12,7 @@ export const InvalidProjectionCode = 32767;
12
12
  /**
13
13
  * Number of tiles used inside this image
14
14
  */
15
- export interface CogTiffImageTiledCount {
15
+ export interface TiffImageTileCount {
16
16
  /** Number of tiles on the x axis */
17
17
  x: number;
18
18
  /** Number of tiles on the y axis */
@@ -38,16 +38,14 @@ export const ImportantTags = new Set([
38
38
  /**
39
39
  * Size of a individual tile
40
40
  */
41
- export interface CogTiffImageTileSize {
41
+ export interface TiffImageTileSize {
42
42
  /** Tile width (pixels) */
43
43
  width: number;
44
44
  /** Tile height (pixels) */
45
45
  height: number;
46
46
  }
47
47
 
48
- export class CogTiffImage {
49
- /** All IFD tags that have been read for the image */
50
- tags: Map<TiffTag, Tag>;
48
+ export class TiffImage {
51
49
  /**
52
50
  * Id of the tif image, generally the image index inside the tif
53
51
  * where 0 is the root image, and every sub image is +1
@@ -56,13 +54,15 @@ export class CogTiffImage {
56
54
  */
57
55
  id: number;
58
56
  /** Reference to the TIFF that owns this image */
59
- tiff: CogTiff;
57
+ tiff: Tiff;
60
58
  /** Has loadGeoTiffTags been called */
61
59
  isGeoTagsLoaded = false;
62
60
  /** Sub tags stored in TiffTag.GeoKeyDirectory */
63
- tagsGeo: Map<TiffTagGeo, string | number> = new Map();
61
+ tagsGeo: Map<TiffTagGeo, string | number | number[]> = new Map();
62
+ /** All IFD tags that have been read for the image */
63
+ tags: Map<TiffTag, Tag>;
64
64
 
65
- constructor(tiff: CogTiff, id: number, tags: Map<TiffTag, Tag>) {
65
+ constructor(tiff: Tiff, id: number, tags: Map<TiffTag, Tag>) {
66
66
  this.tiff = tiff;
67
67
  this.id = id;
68
68
  this.tags = tags;
@@ -74,7 +74,7 @@ export class CogTiffImage {
74
74
  * @param loadGeoTags Whether to load the GeoKeyDirectory and unpack it
75
75
  */
76
76
  async init(loadGeoTags = true): Promise<void> {
77
- const requiredTags = [
77
+ const requiredTags: Promise<unknown>[] = [
78
78
  this.fetch(TiffTag.Compression),
79
79
  this.fetch(TiffTag.ImageHeight),
80
80
  this.fetch(TiffTag.ImageWidth),
@@ -96,16 +96,83 @@ export class CogTiffImage {
96
96
  }
97
97
 
98
98
  /**
99
- * Get the value of a TiffTag if it has been loaded, null otherwise
99
+ * Get the value of a TiffTag if it has been loaded, null otherwise.
100
+ *
101
+ * If the value is not loaded use {@link TiffImage.fetch} to load the value
102
+ * Or use {@link TiffImage.has} to check if the tag exists
103
+ *
100
104
  *
101
- * if the value is not loaded @see {CogTiffImage.fetch}
102
105
  * @returns value if loaded, null otherwise
103
106
  */
104
- value<T>(tag: TiffTag): T | null {
107
+ value<T extends keyof TiffTagType>(tag: T): TiffTagType[T] | null {
105
108
  const sourceTag = this.tags.get(tag);
106
109
  if (sourceTag == null) return null;
107
110
  if (sourceTag.type === 'offset' && sourceTag.isLoaded === false) return null;
108
- return sourceTag.value as T;
111
+ // TODO would be good to type check this
112
+ return sourceTag.value as TiffTagType[T];
113
+ }
114
+
115
+ /**
116
+ * Does the tag exist
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * img.has(TiffTag.ImageWidth) // true
121
+ * ```
122
+ *
123
+ * @param tag Tag to check
124
+ * @returns true if the tag exists, false otherwise
125
+ */
126
+ has<T extends keyof TiffTagType>(tag: T): boolean {
127
+ return this.tags.has(tag);
128
+ }
129
+
130
+ /**
131
+ * Load a tag.
132
+ *
133
+ * If it is not currently loaded, fetch the required data for the tag.
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * await img.fetch(TiffTag.ImageWidth) // 512 (px)
138
+ * ```
139
+ *
140
+ * @param tag tag to fetch
141
+ */
142
+ public async fetch<T extends keyof TiffTagType>(tag: T): Promise<TiffTagType[T] | null> {
143
+ const sourceTag = this.tags.get(tag);
144
+ if (sourceTag == null) return null;
145
+ if (sourceTag.type === 'inline') return sourceTag.value as TiffTagType[T];
146
+ if (sourceTag.type === 'lazy') return fetchLazy(sourceTag, this.tiff) as Promise<TiffTagType[T]>;
147
+ if (sourceTag.isLoaded) return sourceTag.value as TiffTagType[T];
148
+ if (sourceTag.type === 'offset') return fetchAllOffsets(this.tiff, sourceTag) as Promise<TiffTagType[T]>;
149
+ throw new Error('Cannot fetch:' + tag);
150
+ }
151
+ /**
152
+ * Get the associated TiffTagGeo
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * image.valueGeo(TiffTagGeo.GTRasterTypeGeoKey)
157
+ * ```
158
+ * @throws if {@link loadGeoTiffTags} has not been called
159
+ */
160
+ valueGeo<T extends keyof TiffTagGeoType>(tag: T): TiffTagGeoType[T] | null {
161
+ if (this.isGeoTagsLoaded === false) throw new Error('loadGeoTiffTags() has not been called');
162
+ return this.tagsGeo.get(tag) as TiffTagGeoType[T];
163
+ }
164
+
165
+ /**
166
+ * Load and parse the GDAL_NODATA Tifftag
167
+ *
168
+ * @throws if the tag is not loaded
169
+ * @returns null if the tag does not exist
170
+ */
171
+ get noData(): number | null {
172
+ const tag = this.tags.get(TiffTag.GdalNoData);
173
+ if (tag == null) return null;
174
+ if (tag.value) return Number(tag.value);
175
+ throw new Error('GdalNoData tag is not loaded');
109
176
  }
110
177
 
111
178
  /**
@@ -152,60 +219,31 @@ export class CogTiffImage {
152
219
  this.tagsGeo.set(key, tag.value.slice(offset, offset + count - 1).trim());
153
220
  } else if (Array.isArray(tag.value)) {
154
221
  if (count === 1) this.tagsGeo.set(key, tag.value[offset]);
155
- else this.tagsGeo.set(key, tag.value.slice(offset, offset + count) as any);
222
+ else this.tagsGeo.set(key, tag.value.slice(offset, offset + count));
156
223
  } else {
157
224
  throw new Error('Failed to extract GeoTiffTags');
158
225
  }
159
226
  }
160
227
  }
161
228
 
162
- /**
163
- * Get the associated TiffTagGeo
164
- *
165
- * @example
166
- * ```typescript
167
- * image.valueGeo(TiffTagGeo.GTRasterTypeGeoKey)
168
- * ```
169
- * @throws if {@link loadGeoTiffTags} has not been called
170
- */
171
- valueGeo(tag: TiffTagGeo): string | number | undefined {
172
- if (this.isGeoTagsLoaded === false) throw new Error('loadGeoTiffTags() has not been called');
173
- return this.tagsGeo.get(tag);
174
- }
175
-
176
- /**
177
- * Load a tag, if it is not currently loaded, fetch the required data for the tag.
178
- *
179
- * @param tag tag to fetch
180
- */
181
- public async fetch<T>(tag: TiffTag): Promise<T | null> {
182
- const sourceTag = this.tags.get(tag);
183
- if (sourceTag == null) return null;
184
- if (sourceTag.type === 'inline') return sourceTag.value as unknown as T;
185
- if (sourceTag.type === 'lazy') return fetchLazy(sourceTag, this.tiff) as T;
186
- if (sourceTag.isLoaded) return sourceTag.value as unknown as T;
187
- if (sourceTag.type === 'offset') return fetchAllOffsets(this.tiff, sourceTag) as T;
188
- throw new Error('Cannot fetch:' + tag);
189
- }
190
-
191
229
  /**
192
230
  * Get the origin point for the image
193
231
  *
194
232
  * @returns origin point of the image
195
233
  */
196
234
  get origin(): [number, number, number] {
197
- const tiePoints: number[] | null = this.value<number[]>(TiffTag.ModelTiePoint);
235
+ const tiePoints = this.value(TiffTag.ModelTiePoint);
198
236
  if (tiePoints != null && tiePoints.length === 6) {
199
237
  return [tiePoints[3], tiePoints[4], tiePoints[5]];
200
238
  }
201
239
 
202
- const modelTransformation = this.value<number[]>(TiffTag.ModelTransformation);
240
+ const modelTransformation = this.value(TiffTag.ModelTransformation);
203
241
  if (modelTransformation != null) {
204
242
  return [modelTransformation[3], modelTransformation[7], modelTransformation[11]];
205
243
  }
206
244
 
207
245
  // If this is a sub image, use the origin from the top level image
208
- if (this.value(TiffTag.NewSubFileType) === 1 && this.id !== 0) {
246
+ if (this.value(TiffTag.SubFileType) === SubFileType.ReducedImage && this.id !== 0) {
209
247
  return this.tiff.images[0].origin;
210
248
  }
211
249
 
@@ -218,7 +256,7 @@ export class CogTiffImage {
218
256
  this.value(TiffTag.ModelPixelScale) != null || this.value(TiffTag.ModelTransformation) != null;
219
257
  if (isImageLocated) return true;
220
258
  // If this is a sub image, use the isGeoLocated from the top level image
221
- if (this.value(TiffTag.NewSubFileType) === 1 && this.id !== 0) return this.tiff.images[0].isGeoLocated;
259
+ if (this.isSubImage && this.id !== 0) return this.tiff.images[0].isGeoLocated;
222
260
  return false;
223
261
  }
224
262
 
@@ -238,7 +276,7 @@ export class CogTiffImage {
238
276
  }
239
277
 
240
278
  // If this is a sub image, use the resolution from the top level image
241
- if (this.value(TiffTag.NewSubFileType) === 1 && this.id !== 0) {
279
+ if (this.isSubImage && this.id !== 0) {
242
280
  const firstImg = this.tiff.images[0];
243
281
  const [resX, resY, resZ] = firstImg.resolution;
244
282
  const firstImgSize = firstImg.size;
@@ -250,6 +288,15 @@ export class CogTiffImage {
250
288
  throw new Error('Image does not have a geo transformation.');
251
289
  }
252
290
 
291
+ /**
292
+ * Is this image a reduced size image
293
+ * @see {@link TiffTag.SubFileType}
294
+ * @returns true if SubFileType is Reduces image, false otherwise
295
+ */
296
+ get isSubImage(): boolean {
297
+ return this.value(TiffTag.SubFileType) === SubFileType.ReducedImage;
298
+ }
299
+
253
300
  /**
254
301
  * Bounding box of the image
255
302
  *
@@ -276,23 +323,25 @@ export class CogTiffImage {
276
323
  /**
277
324
  * Get the compression used by the tile
278
325
  *
279
- * @see {@link TiffCompression}
326
+ * @see {@link TiffCompressionMimeType}
280
327
  *
281
328
  * @returns Compression type eg webp
282
329
  */
283
330
  get compression(): TiffMimeType | null {
284
331
  const compression = this.value(TiffTag.Compression);
285
- if (compression == null || typeof compression !== 'number') return null;
286
- return TiffCompression[compression];
332
+ if (compression == null) return null;
333
+ return TiffCompressionMimeType[compression];
287
334
  }
288
335
 
289
336
  /**
290
337
  * Attempt to read the EPSG Code from TiffGeoTags
291
338
  *
339
+ * looks at both TiffTagGeo.ProjectionGeoKey and TiffTagGeo.ProjectedCRSGeoKey
340
+ *
292
341
  * @returns EPSG Code if it exists
293
342
  */
294
343
  get epsg(): number | null {
295
- const projection = this.valueGeo(TiffTagGeo.ProjectedCSTypeGeoKey) as number;
344
+ const projection = this.valueGeo(TiffTagGeo.ProjectionGeoKey) ?? this.valueGeo(TiffTagGeo.ProjectedCRSGeoKey);
296
345
  if (projection === InvalidProjectionCode) return null;
297
346
  return projection;
298
347
  }
@@ -303,10 +352,11 @@ export class CogTiffImage {
303
352
  * @returns Size in pixels
304
353
  */
305
354
  get size(): Size {
306
- return {
307
- width: this.value<number>(TiffTag.ImageWidth) as number,
308
- height: this.value<number>(TiffTag.ImageHeight) as number,
309
- };
355
+ const width = this.value(TiffTag.ImageWidth);
356
+ const height = this.value(TiffTag.ImageWidth);
357
+ if (width == null || height == null) throw new Error('Tiff has no height or width');
358
+
359
+ return { width, height };
310
360
  }
311
361
 
312
362
  /**
@@ -319,17 +369,17 @@ export class CogTiffImage {
319
369
  /**
320
370
  * Get size of individual tiles
321
371
  */
322
- get tileSize(): CogTiffImageTileSize {
323
- return {
324
- width: this.value<number>(TiffTag.TileWidth) as number,
325
- height: this.value<number>(TiffTag.TileHeight) as number,
326
- };
372
+ get tileSize(): TiffImageTileSize {
373
+ const width = this.value(TiffTag.TileWidth);
374
+ const height = this.value(TiffTag.TileHeight);
375
+ if (width == null || height == null) throw new Error('Tiff is not tiled');
376
+ return { width, height };
327
377
  }
328
378
 
329
379
  /**
330
380
  * Number of tiles used to create this image
331
381
  */
332
- get tileCount(): CogTiffImageTiledCount {
382
+ get tileCount(): TiffImageTileCount {
333
383
  const size = this.size;
334
384
  const tileSize = this.tileSize;
335
385
  const x = Math.ceil(size.width / tileSize.width);
@@ -344,7 +394,7 @@ export class CogTiffImage {
344
394
  *
345
395
  * @returns file offset to where the tiffs are stored
346
396
  */
347
- get tileOffset(): TagOffset {
397
+ get tileOffset(): TagOffset | TagInline<number[]> {
348
398
  const tileOffset = this.tags.get(TiffTag.TileOffsets) as TagOffset;
349
399
  if (tileOffset == null) throw new Error('No tile offsets found');
350
400
  return tileOffset;
@@ -358,9 +408,7 @@ export class CogTiffImage {
358
408
  * @returns number of strips present
359
409
  */
360
410
  get stripCount(): number {
361
- const tileOffset = this.tags.get(TiffTag.StripByteCounts) as TagOffset;
362
- if (tileOffset == null) return 0;
363
- return tileOffset.count;
411
+ return this.tags.get(TiffTag.StripByteCounts)?.count ?? 0;
364
412
  }
365
413
 
366
414
  // Clamp the bounds of the output image to the size of the image, as sometimes the edge tiles are not full tiles
@@ -396,10 +444,10 @@ export class CogTiffImage {
396
444
  }
397
445
 
398
446
  /** The jpeg header is stored in the IFD, read the JPEG header and adjust the byte array to include it */
399
- private getJpegHeader(bytes: ArrayBuffer): ArrayBuffer {
447
+ getJpegHeader(bytes: ArrayBuffer): ArrayBuffer {
400
448
  // Both the JPEGTable and the Bytes with have the start of image and end of image markers
401
449
  // StartOfImage 0xffd8 EndOfImage 0xffd9
402
- const tables = this.value<number[]>(TiffTag.JPEGTables);
450
+ const tables = this.value(TiffTag.JpegTables);
403
451
  if (tables == null) throw new Error('Unable to find Jpeg header');
404
452
 
405
453
  // Remove EndOfImage marker
@@ -411,20 +459,23 @@ export class CogTiffImage {
411
459
  }
412
460
 
413
461
  /** Read image bytes at the given offset */
414
- private async getBytes(
462
+ async getBytes(
415
463
  offset: number,
416
464
  byteCount: number,
417
- ): Promise<{ mimeType: TiffMimeType; bytes: ArrayBuffer } | null> {
418
- const mimeType = this.compression;
419
- if (mimeType == null) throw new Error('Unsupported compression: ' + this.value(TiffTag.Compression));
465
+ ): Promise<{ mimeType: TiffMimeType; bytes: ArrayBuffer; compression: Compression } | null> {
420
466
  if (byteCount === 0) return null;
421
467
 
422
468
  const bytes = await this.tiff.source.fetch(offset, byteCount);
423
469
  if (bytes.byteLength < byteCount) {
424
470
  throw new Error(`Failed to fetch bytes from offset:${offset} wanted:${byteCount} got:${bytes.byteLength}`);
425
471
  }
426
- if (this.compression === TiffMimeType.Jpeg) return { mimeType, bytes: this.getJpegHeader(bytes) };
427
- return { mimeType, bytes };
472
+
473
+ let compression = this.value(TiffTag.Compression);
474
+ if (compression == null) compression = Compression.None; // No compression found default ??
475
+ const mimeType = getCompressionMimeType(compression) ?? TiffMimeType.None;
476
+
477
+ if (compression === Compression.Jpeg) return { mimeType, bytes: this.getJpegHeader(bytes), compression };
478
+ return { mimeType, bytes, compression };
428
479
  }
429
480
 
430
481
  /**
@@ -435,13 +486,14 @@ export class CogTiffImage {
435
486
  * @param x Tile x offset
436
487
  * @param y Tile y offset
437
488
  */
438
- async getTile(x: number, y: number): Promise<{ mimeType: TiffMimeType; bytes: ArrayBuffer } | null> {
439
- const mimeType = this.compression;
489
+ async getTile(
490
+ x: number,
491
+ y: number,
492
+ ): Promise<{ mimeType: TiffMimeType; bytes: ArrayBuffer; compression: Compression } | null> {
440
493
  const size = this.size;
441
494
  const tiles = this.tileSize;
442
495
 
443
496
  if (tiles == null) throw new Error('Tiff is not tiled');
444
- if (mimeType == null) throw new Error('Unsupported compression: ' + this.value(TiffTag.Compression));
445
497
 
446
498
  // TODO support GhostOptionTileOrder
447
499
  const nyTiles = Math.ceil(size.height / tiles.height);
@@ -497,7 +549,7 @@ export class CogTiffImage {
497
549
  const leaderBytes = this.tiff.options?.tileLeaderByteSize;
498
550
  if (leaderBytes) {
499
551
  const offset = await getOffset(this.tiff, this.tileOffset, index);
500
- // Sparse COG no data found
552
+ // Sparse tiff no data found
501
553
  if (offset === 0) return { offset: 0, imageSize: 0 };
502
554
 
503
555
  // This fetch will generally load in the bytes needed for the image too
@@ -516,15 +568,8 @@ export class CogTiffImage {
516
568
  }
517
569
  }
518
570
 
519
- function getOffset(
520
- tiff: CogTiff,
521
- x: TagOffset | TagInline<number | number[]>,
522
- index: number,
523
- ): number | Promise<number> {
571
+ function getOffset(tiff: Tiff, x: TagOffset | TagInline<number[]>, index: number): number | Promise<number> {
524
572
  if (index > x.count || index < 0) throw new Error('TagIndex: out of bounds ' + x.id + ' @ ' + index);
525
- if (x.type === 'inline') {
526
- if (x.count > 1) return (x.value as number[])[index] as number;
527
- return x.value as number;
528
- }
573
+ if (x.type === 'inline') return x.value[index] as number;
529
574
  return getValueAt(tiff, x, index);
530
575
  }