@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.
- package/CHANGELOG.md +31 -0
- package/README.md +45 -7
- package/build/__benchmark__/cog.read.benchmark.js +5 -5
- package/build/__benchmark__/cog.read.benchmark.js.map +1 -1
- package/build/__benchmark__/source.file.js +1 -1
- package/build/__benchmark__/source.file.js.map +1 -1
- package/build/__benchmark__/source.memory.d.ts +3 -0
- package/build/__benchmark__/source.memory.js +13 -0
- package/build/__benchmark__/source.memory.js.map +1 -1
- package/build/__test__/cog.image.test.js +14 -9
- package/build/__test__/cog.image.test.js.map +1 -1
- package/build/__test__/cog.read.test.js +42 -17
- package/build/__test__/cog.read.test.js.map +1 -1
- package/build/__test__/example.js +2 -2
- package/build/__test__/example.js.map +1 -1
- package/build/const/index.d.ts +1 -1
- package/build/const/index.js +1 -1
- package/build/const/index.js.map +1 -1
- package/build/const/tiff.mime.d.ts +11 -5
- package/build/const/tiff.mime.js +27 -14
- package/build/const/tiff.mime.js.map +1 -1
- package/build/const/tiff.tag.id.d.ts +603 -41
- package/build/const/tiff.tag.id.js +479 -50
- package/build/const/tiff.tag.id.js.map +1 -1
- package/build/const/tiff.tag.value.js +1 -1
- package/build/const/tiff.tag.value.js.map +1 -1
- package/build/index.d.ts +6 -5
- package/build/index.js +6 -4
- package/build/index.js.map +1 -1
- package/build/read/tiff.gdal.d.ts +1 -1
- package/build/read/tiff.gdal.js +1 -1
- package/build/read/tiff.gdal.js.map +1 -1
- package/build/read/tiff.ifd.config.js +1 -1
- package/build/read/tiff.ifd.config.js.map +1 -1
- package/build/read/tiff.tag.d.ts +3 -0
- package/build/read/tiff.tag.factory.d.ts +5 -5
- package/build/read/tiff.tag.factory.js +22 -13
- package/build/read/tiff.tag.factory.js.map +1 -1
- package/build/read/tiff.value.reader.d.ts +1 -1
- package/build/read/tiff.value.reader.js.map +1 -1
- package/build/{cog.tiff.d.ts → tiff.d.ts} +12 -10
- package/build/{cog.tiff.image.d.ts → tiff.image.d.ts} +69 -27
- package/build/{cog.tiff.image.js → tiff.image.js} +125 -82
- package/build/tiff.image.js.map +1 -0
- package/build/{cog.tiff.js → tiff.js} +19 -11
- package/build/tiff.js.map +1 -0
- package/build/util/bytes.d.ts +4 -2
- package/build/util/bytes.js +5 -2
- package/build/util/bytes.js.map +1 -1
- package/package.json +2 -2
- package/src/__benchmark__/cog.read.benchmark.ts +6 -5
- package/src/__benchmark__/source.file.ts +2 -1
- package/src/__benchmark__/source.memory.ts +10 -0
- package/src/__test__/cog.image.test.ts +17 -9
- package/src/__test__/cog.read.test.ts +53 -18
- package/src/__test__/example.ts +3 -2
- package/src/const/index.ts +1 -1
- package/src/const/tiff.mime.ts +28 -14
- package/src/const/tiff.tag.id.ts +758 -131
- package/src/const/tiff.tag.value.ts +16 -16
- package/src/index.ts +20 -5
- package/src/read/tiff.gdal.ts +1 -1
- package/src/read/tiff.ifd.config.ts +1 -1
- package/src/read/tiff.tag.factory.ts +33 -17
- package/src/read/tiff.tag.ts +3 -0
- package/src/read/tiff.value.reader.ts +1 -1
- package/src/{cog.tiff.image.ts → tiff.image.ts} +137 -92
- package/src/{cog.tiff.ts → tiff.ts} +22 -17
- package/src/util/bytes.ts +5 -1
- package/build/cog.tiff.image.js.map +0 -1
- package/build/cog.tiff.js.map +0 -1
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
export enum TiffTagValueType {
|
|
2
|
-
Uint8 =
|
|
3
|
-
Ascii =
|
|
4
|
-
Uint16 =
|
|
5
|
-
Uint32 =
|
|
6
|
-
Rational =
|
|
7
|
-
Int8 =
|
|
8
|
-
Undefined =
|
|
9
|
-
Int16 =
|
|
10
|
-
Int32 =
|
|
11
|
-
SignedRational =
|
|
12
|
-
Float32 =
|
|
13
|
-
Float64 =
|
|
14
|
-
//
|
|
15
|
-
Uint64 =
|
|
16
|
-
Int64 =
|
|
17
|
-
Ifd8 =
|
|
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,
|
|
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
|
|
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';
|
package/src/read/tiff.gdal.ts
CHANGED
|
@@ -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
|
|
25
|
+
export class TiffGhostOptions {
|
|
26
26
|
options: Map<string, string> = new Map();
|
|
27
27
|
|
|
28
28
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
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>(
|
|
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)
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/read/tiff.tag.ts
CHANGED
|
@@ -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,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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)
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
286
|
-
return
|
|
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.
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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():
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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():
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
427
|
-
|
|
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(
|
|
439
|
-
|
|
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
|
|
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
|
}
|