@cogeotiff/core 6.1.1 → 7.2.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.
- package/CHANGELOG.md +51 -0
- package/build/__benchmark__/cog.read.benchmark.js +7 -4
- package/build/__test__/cog.image.test.js +26 -2
- package/build/cog.tiff.d.ts +2 -2
- package/build/cog.tiff.d.ts.map +1 -1
- package/build/cog.tiff.image.d.ts +16 -7
- package/build/cog.tiff.image.d.ts.map +1 -1
- package/build/cog.tiff.image.js +56 -33
- package/build/cog.tiff.js +19 -54
- package/build/const/tiff.mime.d.ts +5 -1
- package/build/const/tiff.mime.d.ts.map +1 -1
- package/build/const/tiff.mime.js +9 -1
- package/build/read/tag/__test__/tag.test.js +1 -1
- package/build/read/tag/tiff.tag.base.d.ts.map +1 -1
- package/build/read/tag/tiff.tag.base.js +1 -1
- package/build/read/tag/tiff.tag.lazy.d.ts +1 -2
- package/build/read/tag/tiff.tag.lazy.d.ts.map +1 -1
- package/build/read/tag/tiff.tag.lazy.js +3 -3
- package/build/read/tag/tiff.tag.offset.d.ts +2 -3
- package/build/read/tag/tiff.tag.offset.d.ts.map +1 -1
- package/build/read/tag/tiff.tag.offset.js +5 -5
- package/build/read/tiff.gdal.d.ts +6 -19
- package/build/read/tiff.gdal.d.ts.map +1 -1
- package/build/read/tiff.gdal.js +32 -53
- package/package.json +5 -6
- package/src/__benchmark__/cog.read.benchmark.ts +6 -3
- package/src/__test__/cog.image.test.ts +29 -1
- package/src/cog.tiff.image.ts +55 -33
- package/src/cog.tiff.ts +17 -68
- package/src/const/tiff.mime.ts +8 -0
- package/src/read/tag/__test__/tag.test.ts +1 -1
- package/src/read/tag/tiff.tag.base.ts +0 -1
- package/src/read/tag/tiff.tag.lazy.ts +2 -3
- package/src/read/tag/tiff.tag.offset.ts +4 -5
- package/src/read/tiff.gdal.ts +31 -58
- package/build/util/util.enum.d.ts +0 -6
- package/build/util/util.enum.d.ts.map +0 -1
- package/build/util/util.enum.js +0 -17
- package/src/util/util.enum.ts +0 -16
package/src/cog.tiff.image.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { LogType } from '@chunkd/core';
|
|
2
1
|
import { CogTiff } from './cog.tiff.js';
|
|
3
2
|
import { TiffCompression, TiffMimeType } from './const/tiff.mime.js';
|
|
4
3
|
import { TiffTag, TiffTagGeo } from './const/tiff.tag.id.js';
|
|
@@ -56,27 +55,27 @@ export class CogTiffImage {
|
|
|
56
55
|
*
|
|
57
56
|
* @param loadGeoTags Whether to load the GeoKeyDirectory and unpack it
|
|
58
57
|
*/
|
|
59
|
-
async init(loadGeoTags = false
|
|
58
|
+
async init(loadGeoTags = false): Promise<void> {
|
|
60
59
|
const requiredTags = [
|
|
61
|
-
this.fetch(TiffTag.Compression
|
|
62
|
-
this.fetch(TiffTag.ImageHeight
|
|
63
|
-
this.fetch(TiffTag.ImageWidth
|
|
64
|
-
this.fetch(TiffTag.ModelPixelScale
|
|
65
|
-
this.fetch(TiffTag.ModelTiePoint
|
|
66
|
-
this.fetch(TiffTag.ModelTransformation
|
|
67
|
-
this.fetch(TiffTag.TileHeight
|
|
68
|
-
this.fetch(TiffTag.TileWidth
|
|
60
|
+
this.fetch(TiffTag.Compression),
|
|
61
|
+
this.fetch(TiffTag.ImageHeight),
|
|
62
|
+
this.fetch(TiffTag.ImageWidth),
|
|
63
|
+
this.fetch(TiffTag.ModelPixelScale),
|
|
64
|
+
this.fetch(TiffTag.ModelTiePoint),
|
|
65
|
+
this.fetch(TiffTag.ModelTransformation),
|
|
66
|
+
this.fetch(TiffTag.TileHeight),
|
|
67
|
+
this.fetch(TiffTag.TileWidth),
|
|
69
68
|
];
|
|
70
69
|
|
|
71
70
|
if (loadGeoTags) {
|
|
72
|
-
requiredTags.push(this.fetch(TiffTag.GeoKeyDirectory
|
|
73
|
-
requiredTags.push(this.fetch(TiffTag.GeoAsciiParams
|
|
74
|
-
requiredTags.push(this.fetch(TiffTag.GeoDoubleParams
|
|
71
|
+
requiredTags.push(this.fetch(TiffTag.GeoKeyDirectory));
|
|
72
|
+
requiredTags.push(this.fetch(TiffTag.GeoAsciiParams));
|
|
73
|
+
requiredTags.push(this.fetch(TiffTag.GeoDoubleParams));
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
await Promise.all(requiredTags);
|
|
78
77
|
if (loadGeoTags) {
|
|
79
|
-
await this.loadGeoTiffTags(
|
|
78
|
+
await this.loadGeoTiffTags();
|
|
80
79
|
}
|
|
81
80
|
}
|
|
82
81
|
|
|
@@ -92,7 +91,7 @@ export class CogTiffImage {
|
|
|
92
91
|
/**
|
|
93
92
|
* Load and unpack the GeoKeyDirectory
|
|
94
93
|
*/
|
|
95
|
-
async loadGeoTiffTags(
|
|
94
|
+
async loadGeoTiffTags(): Promise<void> {
|
|
96
95
|
// Already loaded
|
|
97
96
|
if (this.tagsGeoLoaded) return;
|
|
98
97
|
const sourceTag = this.tags.get(TiffTag.GeoKeyDirectory);
|
|
@@ -103,9 +102,9 @@ export class CogTiffImage {
|
|
|
103
102
|
if (!sourceTag.isReady && sourceTag instanceof CogTiffTagLazy) {
|
|
104
103
|
// Load all the required keys
|
|
105
104
|
await Promise.all([
|
|
106
|
-
this.fetch(TiffTag.GeoKeyDirectory
|
|
107
|
-
this.fetch(TiffTag.GeoAsciiParams
|
|
108
|
-
this.fetch(TiffTag.GeoDoubleParams
|
|
105
|
+
this.fetch(TiffTag.GeoKeyDirectory),
|
|
106
|
+
this.fetch(TiffTag.GeoAsciiParams),
|
|
107
|
+
this.fetch(TiffTag.GeoDoubleParams),
|
|
109
108
|
]);
|
|
110
109
|
}
|
|
111
110
|
this.tagsGeoLoaded = true;
|
|
@@ -145,10 +144,10 @@ export class CogTiffImage {
|
|
|
145
144
|
* Load a tag, if it is not currently loaded, fetch the required data for the tag.
|
|
146
145
|
* @param tag tag to fetch
|
|
147
146
|
*/
|
|
148
|
-
public async fetch<T>(tag: TiffTag
|
|
147
|
+
public async fetch<T>(tag: TiffTag): Promise<T | null> {
|
|
149
148
|
const sourceTag = this.tags.get(tag);
|
|
150
149
|
if (sourceTag == null) return null;
|
|
151
|
-
if (CogTiffTag.isLazy(sourceTag)) return sourceTag.fetch(
|
|
150
|
+
if (CogTiffTag.isLazy(sourceTag)) return sourceTag.fetch() as any;
|
|
152
151
|
return sourceTag.value as T;
|
|
153
152
|
}
|
|
154
153
|
|
|
@@ -342,14 +341,14 @@ export class CogTiffImage {
|
|
|
342
341
|
* @param index tile index
|
|
343
342
|
* @returns file offset of the specified tile
|
|
344
343
|
*/
|
|
345
|
-
protected async getTileOffset(index: number
|
|
344
|
+
protected async getTileOffset(index: number): Promise<number> {
|
|
346
345
|
const tileOffset = this.tileOffset;
|
|
347
346
|
if (index < 0 || index > tileOffset.dataCount) {
|
|
348
347
|
throw new Error(`Tile offset: ${index} out of range: 0 -> ${tileOffset.dataCount}`);
|
|
349
348
|
}
|
|
350
349
|
|
|
351
350
|
// Fetch only the part of the offsets that are needed
|
|
352
|
-
return tileOffset.getValueAt(index
|
|
351
|
+
return tileOffset.getValueAt(index);
|
|
353
352
|
}
|
|
354
353
|
|
|
355
354
|
// Clamp the bounds of the output image to the size of the image, as sometimes the edge tiles are not full tiles
|
|
@@ -398,13 +397,12 @@ export class CogTiffImage {
|
|
|
398
397
|
private async getBytes(
|
|
399
398
|
offset: number,
|
|
400
399
|
byteCount: number,
|
|
401
|
-
l?: LogType,
|
|
402
400
|
): Promise<{ mimeType: TiffMimeType; bytes: Uint8Array } | null> {
|
|
403
401
|
const mimeType = this.compression;
|
|
404
402
|
if (mimeType == null) throw new Error('Unsupported compression: ' + this.value(TiffTag.Compression));
|
|
405
403
|
if (byteCount === 0) return null;
|
|
406
404
|
|
|
407
|
-
await this.tif.source.loadBytes(offset, byteCount
|
|
405
|
+
await this.tif.source.loadBytes(offset, byteCount);
|
|
408
406
|
const bytes = this.tif.source.bytes(offset, byteCount);
|
|
409
407
|
|
|
410
408
|
if (this.compression === TiffMimeType.JPEG) {
|
|
@@ -421,7 +419,7 @@ export class CogTiffImage {
|
|
|
421
419
|
* @param x Tile x offset
|
|
422
420
|
* @param y Tile y offset
|
|
423
421
|
*/
|
|
424
|
-
async getTile(x: number, y: number
|
|
422
|
+
async getTile(x: number, y: number): Promise<{ mimeType: TiffMimeType; bytes: Uint8Array } | null> {
|
|
425
423
|
const mimeType = this.compression;
|
|
426
424
|
const size = this.size;
|
|
427
425
|
const tiles = this.tileSize;
|
|
@@ -441,22 +439,46 @@ export class CogTiffImage {
|
|
|
441
439
|
const totalTiles = nxTiles * nyTiles;
|
|
442
440
|
if (idx >= totalTiles) throw new Error(`Tile index is outside of tile range: ${idx} >= ${totalTiles}`);
|
|
443
441
|
|
|
444
|
-
const { offset, imageSize } = await this.getTileSize(idx
|
|
445
|
-
return this.getBytes(offset, imageSize
|
|
442
|
+
const { offset, imageSize } = await this.getTileSize(idx);
|
|
443
|
+
return this.getBytes(offset, imageSize);
|
|
446
444
|
}
|
|
447
445
|
|
|
448
|
-
|
|
446
|
+
/**
|
|
447
|
+
* Does this tile exist in the tiff and does it actually have a value
|
|
448
|
+
*
|
|
449
|
+
* Sparse tiffs can have a lot of empty tiles, this checks to see if the tile actually has data.
|
|
450
|
+
*
|
|
451
|
+
* @param x Tile x offset
|
|
452
|
+
* @param y Tile y offset
|
|
453
|
+
* @returns if the tile exists and has data
|
|
454
|
+
*/
|
|
455
|
+
async hasTile(x: number, y: number): Promise<boolean> {
|
|
456
|
+
const tiles = this.tileSize;
|
|
457
|
+
const size = this.size;
|
|
458
|
+
|
|
459
|
+
if (tiles == null) throw new Error('Tiff is not tiled');
|
|
460
|
+
|
|
461
|
+
// TODO support GhostOptionTileOrder
|
|
462
|
+
const nyTiles = Math.ceil(size.height / tiles.height);
|
|
463
|
+
const nxTiles = Math.ceil(size.width / tiles.width);
|
|
464
|
+
if (x >= nxTiles || y >= nyTiles) return false;
|
|
465
|
+
const idx = y * nxTiles + x;
|
|
466
|
+
const ret = await this.getTileSize(idx);
|
|
467
|
+
return ret.offset > 0;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
protected async getTileSize(index: number): Promise<{ offset: number; imageSize: number }> {
|
|
449
471
|
// GDAL optimizes tiles by storing the size of the tile in
|
|
450
472
|
// the few bytes leading up to the tile
|
|
451
|
-
|
|
452
|
-
|
|
473
|
+
const leaderBytes = this.tif.options.tileLeaderByteSize;
|
|
474
|
+
if (leaderBytes) {
|
|
475
|
+
const offset = await this.getTileOffset(index);
|
|
453
476
|
// Sparse COG no data found
|
|
454
477
|
if (offset === 0) return { offset: 0, imageSize: 0 };
|
|
455
478
|
|
|
456
|
-
const leaderBytes = this.tif.options.tileLeaderByteSize;
|
|
457
479
|
// This fetch will generally load in the bytes needed for the image too
|
|
458
480
|
// provided the image size is less than the size of a chunk
|
|
459
|
-
await this.tif.source.loadBytes(offset - leaderBytes, leaderBytes
|
|
481
|
+
await this.tif.source.loadBytes(offset - leaderBytes, leaderBytes);
|
|
460
482
|
return { offset, imageSize: this.tif.source.getUint(offset - leaderBytes, leaderBytes) };
|
|
461
483
|
}
|
|
462
484
|
|
|
@@ -464,7 +486,7 @@ export class CogTiffImage {
|
|
|
464
486
|
if (byteCounts == null) {
|
|
465
487
|
throw new Error('No tile byte counts found');
|
|
466
488
|
}
|
|
467
|
-
const [offset, imageSize] = await Promise.all([this.getTileOffset(index
|
|
489
|
+
const [offset, imageSize] = await Promise.all([this.getTileOffset(index), byteCounts.getValueAt(index)]);
|
|
468
490
|
return { offset, imageSize };
|
|
469
491
|
}
|
|
470
492
|
}
|
package/src/cog.tiff.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChunkSource
|
|
1
|
+
import { ChunkSource } from '@chunkd/core';
|
|
2
2
|
import { CogTiffImage } from './cog.tiff.image.js';
|
|
3
3
|
import { TiffEndian } from './const/tiff.endian.js';
|
|
4
4
|
import { TiffTag } from './const/tiff.tag.id.js';
|
|
@@ -38,24 +38,24 @@ export class CogTiff {
|
|
|
38
38
|
*
|
|
39
39
|
* @param loadGeoKeys Whether to also initialize the GeoKeyDirectory
|
|
40
40
|
*/
|
|
41
|
-
init(loadGeoKeys = false
|
|
41
|
+
init(loadGeoKeys = false): Promise<CogTiff> {
|
|
42
42
|
if (this._initPromise) return this._initPromise;
|
|
43
|
-
this._initPromise = this.doInit(loadGeoKeys
|
|
43
|
+
this._initPromise = this.doInit(loadGeoKeys);
|
|
44
44
|
return this._initPromise;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
private async doInit(loadGeoKeys = false
|
|
47
|
+
private async doInit(loadGeoKeys = false): Promise<CogTiff> {
|
|
48
48
|
if (this.isInitialized) return this;
|
|
49
49
|
// Load the first few KB in, more loads will run as more data is required
|
|
50
|
-
await this.source.loadBytes(0, this.source.chunkSize
|
|
51
|
-
await this.fetchIfd(
|
|
52
|
-
await Promise.all(this.images.map((c) => c.init(loadGeoKeys
|
|
50
|
+
await this.source.loadBytes(0, this.source.chunkSize);
|
|
51
|
+
await this.fetchIfd();
|
|
52
|
+
await Promise.all(this.images.map((c) => c.init(loadGeoKeys)));
|
|
53
53
|
|
|
54
54
|
this.isInitialized = true;
|
|
55
55
|
return this;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
private async fetchIfd(
|
|
58
|
+
private async fetchIfd(): Promise<void> {
|
|
59
59
|
const view = this.cursor.seekTo(0);
|
|
60
60
|
const endian = view.uint16();
|
|
61
61
|
this.source.isLittleEndian = endian === TiffEndian.Little;
|
|
@@ -78,15 +78,9 @@ export class CogTiff {
|
|
|
78
78
|
|
|
79
79
|
const ghostSize = nextOffsetIfd - this.cursor.currentOffset;
|
|
80
80
|
// GDAL now stores metadata between the IFD inside a ghost storage area
|
|
81
|
-
if (ghostSize > 0 && ghostSize < 16 * 1024)
|
|
82
|
-
logger?.debug(
|
|
83
|
-
{ offset: toHexString(this.cursor.currentOffset), length: toHexString(ghostSize) },
|
|
84
|
-
'GhostOptions',
|
|
85
|
-
);
|
|
86
|
-
// this.options.process(this.source, view.currentOffset, ghostSize);
|
|
87
|
-
}
|
|
81
|
+
if (ghostSize > 0 && ghostSize < 16 * 1024) this.options.process(view.bytes(ghostSize));
|
|
88
82
|
|
|
89
|
-
return this.processIfd(nextOffsetIfd
|
|
83
|
+
return this.processIfd(nextOffsetIfd);
|
|
90
84
|
}
|
|
91
85
|
|
|
92
86
|
getImage(z: number): CogTiffImage {
|
|
@@ -135,38 +129,19 @@ export class CogTiff {
|
|
|
135
129
|
return image.getTile(x, y);
|
|
136
130
|
}
|
|
137
131
|
|
|
138
|
-
private async processIfd(offset: number
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (!this.source.hasBytes(offset, 4096)) {
|
|
142
|
-
await this.source.loadBytes(offset, 4096, logger);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const { image, nextOffset } = await this.readIfd(offset, logger);
|
|
132
|
+
private async processIfd(offset: number): Promise<void> {
|
|
133
|
+
const { image, nextOffset } = await this.readIfd(offset);
|
|
146
134
|
this.images.push(image);
|
|
147
|
-
const size = image.size;
|
|
148
|
-
if (image.isTiled()) {
|
|
149
|
-
const tile = image.tileSize;
|
|
150
|
-
logger?.debug(
|
|
151
|
-
{
|
|
152
|
-
...size,
|
|
153
|
-
tileWidth: tile.width,
|
|
154
|
-
tileHeight: tile.height,
|
|
155
|
-
tileCount: Math.ceil(size.width / tile.width),
|
|
156
|
-
},
|
|
157
|
-
'GotImage',
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
135
|
|
|
161
|
-
if (nextOffset) await this.processIfd(nextOffset
|
|
136
|
+
if (nextOffset) await this.processIfd(nextOffset);
|
|
162
137
|
}
|
|
163
138
|
|
|
164
|
-
private async readIfd(offset: number
|
|
165
|
-
if (!this.source.hasBytes(offset,
|
|
139
|
+
private async readIfd(offset: number): Promise<{ nextOffset: number; image: CogTiffImage }> {
|
|
140
|
+
if (!this.source.hasBytes(offset, 4096)) await this.source.loadBytes(offset, 4096);
|
|
141
|
+
|
|
166
142
|
const view = this.cursor.seekTo(offset);
|
|
167
143
|
const tagCount = view.offset();
|
|
168
144
|
const byteStart = offset + this.ifdConfig.offset;
|
|
169
|
-
const logger = log?.child({ imageId: this.images.length });
|
|
170
145
|
const tags: Map<TiffTag, CogTiffTagBase> = new Map();
|
|
171
146
|
|
|
172
147
|
let pos = byteStart;
|
|
@@ -174,33 +149,7 @@ export class CogTiff {
|
|
|
174
149
|
const tag = CogTiffTag.create(this, pos);
|
|
175
150
|
pos += tag.size;
|
|
176
151
|
|
|
177
|
-
if (tag.name == null)
|
|
178
|
-
logger?.error({ code: toHexString(tag.id) }, `IFDUnknown`);
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (!tag.isReady) {
|
|
183
|
-
logger?.trace(
|
|
184
|
-
{
|
|
185
|
-
offset: toHexString(pos - offset),
|
|
186
|
-
code: toHexString(tag.id),
|
|
187
|
-
tagName: tag.name,
|
|
188
|
-
ptr: toHexString(tag.valuePointer),
|
|
189
|
-
},
|
|
190
|
-
'PartialReadIFD',
|
|
191
|
-
);
|
|
192
|
-
} else {
|
|
193
|
-
logger?.trace(
|
|
194
|
-
{
|
|
195
|
-
offset: toHexString(pos - offset),
|
|
196
|
-
code: toHexString(tag.id),
|
|
197
|
-
tagName: tag.name,
|
|
198
|
-
value: Array.isArray(tag.value) ? `[${tag.value.length}]` : tag.value,
|
|
199
|
-
},
|
|
200
|
-
'ReadIFD',
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
|
|
152
|
+
if (tag.name == null) throw new Error('Unknown IFD Tag: ' + toHexString(tag.id));
|
|
204
153
|
tags.set(tag.id, tag);
|
|
205
154
|
}
|
|
206
155
|
|
package/src/const/tiff.mime.ts
CHANGED
|
@@ -5,9 +5,13 @@ export enum TiffMimeType {
|
|
|
5
5
|
NONE = 'application/octet-stream',
|
|
6
6
|
JPEG = 'image/jpeg',
|
|
7
7
|
JP2 = 'image/jp2',
|
|
8
|
+
JPEGXL = 'image/jpegxl',
|
|
8
9
|
WEBP = 'image/webp',
|
|
10
|
+
ZSTD = 'application/zstd',
|
|
9
11
|
LZW = 'application/lzw',
|
|
10
12
|
DEFLATE = 'application/deflate',
|
|
13
|
+
LZERC = 'application/lerc',
|
|
14
|
+
LZMA = 'application/x-lzma',
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
export const TiffCompression: { [key: number]: TiffMimeType } = {
|
|
@@ -16,6 +20,10 @@ export const TiffCompression: { [key: number]: TiffMimeType } = {
|
|
|
16
20
|
6: TiffMimeType.JPEG,
|
|
17
21
|
7: TiffMimeType.JPEG,
|
|
18
22
|
8: TiffMimeType.DEFLATE,
|
|
23
|
+
34887: TiffMimeType.LZERC,
|
|
24
|
+
34925: TiffMimeType.LZMA,
|
|
19
25
|
34712: TiffMimeType.JP2,
|
|
26
|
+
50000: TiffMimeType.ZSTD,
|
|
20
27
|
50001: TiffMimeType.WEBP,
|
|
28
|
+
50002: TiffMimeType.JPEGXL,
|
|
21
29
|
};
|
|
@@ -10,7 +10,7 @@ o.spec('TiffTag', () => {
|
|
|
10
10
|
const cogSourceFile = new TestFileChunkSource(path.join(dirName, '../../../../../data/rgba8_tiled.tiff'));
|
|
11
11
|
|
|
12
12
|
o.beforeEach(() => {
|
|
13
|
-
cogSourceFile.chunks.clear();
|
|
13
|
+
(cogSourceFile.chunks as Map<unknown, unknown>).clear();
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
o('should load lazy tags', async () => {
|
|
@@ -32,7 +32,6 @@ export abstract class CogTiffTagBase<T = unknown> {
|
|
|
32
32
|
|
|
33
33
|
this.id = id;
|
|
34
34
|
this.name = TiffTag[this.id];
|
|
35
|
-
|
|
36
35
|
this.dataType = this.tiff.source.getUint16(offset + 2);
|
|
37
36
|
this.dataCount = this.tiff.source.getUint(offset + 4, this.tiff.ifdConfig.pointer);
|
|
38
37
|
this.dataTypeSize = getTiffTagSize(this.dataType);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { LogType } from '@chunkd/core';
|
|
2
1
|
import { CogTiffTagBase } from './tiff.tag.base.js';
|
|
3
2
|
|
|
4
3
|
export class CogTiffTagLazy<T> extends CogTiffTagBase<T> {
|
|
@@ -8,9 +7,9 @@ export class CogTiffTagLazy<T> extends CogTiffTagBase<T> {
|
|
|
8
7
|
return this.value != null;
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
async fetch(
|
|
10
|
+
async fetch(): Promise<T> {
|
|
12
11
|
if (this.tiff.source.hasBytes(this.valuePointer, this.dataLength) === false) {
|
|
13
|
-
await this.tiff.source.loadBytes(this.valuePointer, this.dataLength
|
|
12
|
+
await this.tiff.source.loadBytes(this.valuePointer, this.dataLength);
|
|
14
13
|
}
|
|
15
14
|
this.value = this.readValue();
|
|
16
15
|
return this.value;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { LogType } from '@chunkd/core';
|
|
2
1
|
import { CogTiff } from '../../cog.tiff.js';
|
|
3
2
|
import { getTiffTagValueReader } from '../tiff.value.reader.js';
|
|
4
3
|
import { CogTiffTagBase } from './tiff.tag.base.js';
|
|
@@ -27,8 +26,8 @@ export class CogTiffTagOffset extends CogTiffTagBase<number[]> {
|
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
/** Load the entire index into memory */
|
|
30
|
-
async load(
|
|
31
|
-
await this.tiff.source.loadBytes(this.valuePointer, this.dataLength
|
|
29
|
+
async load(): Promise<void> {
|
|
30
|
+
await this.tiff.source.loadBytes(this.valuePointer, this.dataLength);
|
|
32
31
|
this.readValue();
|
|
33
32
|
}
|
|
34
33
|
|
|
@@ -47,7 +46,7 @@ export class CogTiffTagOffset extends CogTiffTagBase<number[]> {
|
|
|
47
46
|
* Read a specific value from the offset array
|
|
48
47
|
* @param index index to read at
|
|
49
48
|
*/
|
|
50
|
-
async getValueAt(index: number
|
|
49
|
+
async getValueAt(index: number): Promise<number> {
|
|
51
50
|
if (this.loadedValues) return this.loadedValues[index];
|
|
52
51
|
|
|
53
52
|
const dataSize = this.dataTypeSize;
|
|
@@ -55,7 +54,7 @@ export class CogTiffTagOffset extends CogTiffTagBase<number[]> {
|
|
|
55
54
|
const convert = getTiffTagValueReader(this.dataType);
|
|
56
55
|
|
|
57
56
|
if (!this.tiff.source.hasBytes(valueOffset, dataSize)) {
|
|
58
|
-
await this.tiff.source.loadBytes(valueOffset, dataSize
|
|
57
|
+
await this.tiff.source.loadBytes(valueOffset, dataSize);
|
|
59
58
|
}
|
|
60
59
|
return convert(this.tiff.source, valueOffset) as number;
|
|
61
60
|
}
|
package/src/read/tiff.gdal.ts
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
import { ByteSize
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
// GDAL_STRUCTURAL_METADATA_SIZE: '000140 bytes',
|
|
5
|
-
// LAYOUT: 'IFDS_BEFORE_DATA',
|
|
6
|
-
// BLOCK_ORDER: 'ROW_MAJOR',
|
|
7
|
-
// BLOCK_LEADER: 'SIZE_AS_UINT4',
|
|
8
|
-
// BLOCK_TRAILER: 'LAST_4_BYTES_REPEATED',
|
|
9
|
-
// KNOWN_INCOMPATIBLE_EDITION: 'NO'
|
|
1
|
+
import { ByteSize } from '@chunkd/core';
|
|
2
|
+
import { CogTiff } from '../cog.tiff.js';
|
|
10
3
|
|
|
11
4
|
export enum GhostOption {
|
|
12
5
|
GDAL_STRUCTURAL_METADATA_SIZE = 'GDAL_STRUCTURAL_METADATA_SIZE',
|
|
@@ -25,16 +18,14 @@ export enum GhostOptionTileOrder {
|
|
|
25
18
|
export enum GhostOptionTileLeader {
|
|
26
19
|
uint32 = 'SIZE_AS_UINT4',
|
|
27
20
|
}
|
|
28
|
-
export const GhostOptionTileLeaderSize: { [key: string]: ByteSize } = {
|
|
29
|
-
uint32: ByteSize.UInt32,
|
|
30
|
-
};
|
|
31
21
|
|
|
32
22
|
/**
|
|
33
23
|
* GDAL has made a ghost set of options for Tif files
|
|
34
24
|
* this class represents the optimizations that can be used
|
|
35
25
|
*/
|
|
36
26
|
export class CogTifGhostOptions {
|
|
37
|
-
options: Map<
|
|
27
|
+
options: Map<string, string> = new Map();
|
|
28
|
+
source: CogTiff;
|
|
38
29
|
|
|
39
30
|
/**
|
|
40
31
|
* Has GDAL optimized this tif
|
|
@@ -51,61 +42,43 @@ export class CogTifGhostOptions {
|
|
|
51
42
|
return this.options.get(GhostOption.KNOWN_INCOMPATIBLE_EDITION) === 'YES';
|
|
52
43
|
}
|
|
53
44
|
|
|
54
|
-
private set(key: GhostOption, val: string): void {
|
|
55
|
-
this.options.set(key, val);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
45
|
/**
|
|
59
46
|
* Load the ghost options from a source
|
|
60
|
-
* @param
|
|
61
|
-
* @param offset Byte offset to start reading
|
|
62
|
-
* @param length max number of bytes to read
|
|
47
|
+
* @param bytes the ghost header bytes
|
|
63
48
|
*/
|
|
64
|
-
process(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
49
|
+
process(bytes: Uint8Array): void {
|
|
50
|
+
let key = '';
|
|
51
|
+
let value = '';
|
|
52
|
+
let setValue = false;
|
|
53
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
54
|
+
const charCode = bytes[i];
|
|
55
|
+
if (charCode === 0) break;
|
|
56
|
+
|
|
57
|
+
const char = String.fromCharCode(charCode);
|
|
58
|
+
if (char === '\n') {
|
|
59
|
+
this.options.set(key.trim(), value.trim());
|
|
60
|
+
key = '';
|
|
61
|
+
value = '';
|
|
62
|
+
setValue = false;
|
|
63
|
+
} else if (char === '=') {
|
|
64
|
+
setValue = true;
|
|
65
|
+
} else {
|
|
66
|
+
if (setValue) value += char;
|
|
67
|
+
else key += char;
|
|
68
|
+
}
|
|
70
69
|
}
|
|
71
|
-
|
|
72
|
-
const keyValPairs = chars
|
|
73
|
-
.join('')
|
|
74
|
-
.trim()
|
|
75
|
-
.split('\n')
|
|
76
|
-
.map((c) => c.split('='));
|
|
77
|
-
|
|
78
|
-
for (const [key, value] of keyValPairs) {
|
|
79
|
-
this.options.set(GhostOption[key as GhostOption], value);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
private _getReverse<T>(e: Record<string, any>, key: GhostOption): T | null {
|
|
84
|
-
const opt = this.options.get(key);
|
|
85
|
-
if (opt == null) return null;
|
|
86
|
-
return getReverseEnumValue<T>(e, opt);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* The ordering of tiles inside the tif
|
|
91
|
-
*/
|
|
92
|
-
get tileOrder(): GhostOptionTileOrder | null {
|
|
93
|
-
return this._getReverse(GhostOptionTileOrder, GhostOption.BLOCK_ORDER);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Are tiles stored with the file size immediately before the tile
|
|
98
|
-
*/
|
|
99
|
-
get tileLeader(): GhostOptionTileLeader | null {
|
|
100
|
-
return this._getReverse(GhostOptionTileLeader, GhostOption.BLOCK_LEADER);
|
|
101
70
|
}
|
|
102
71
|
|
|
103
72
|
/**
|
|
104
73
|
* If the tile leader is set, how many bytes are allocated to the tile size
|
|
105
74
|
*/
|
|
106
75
|
get tileLeaderByteSize(): ByteSize | null {
|
|
107
|
-
|
|
108
|
-
|
|
76
|
+
switch (this.options.get(GhostOption.BLOCK_LEADER)) {
|
|
77
|
+
case GhostOptionTileLeader.uint32:
|
|
78
|
+
return ByteSize.UInt32;
|
|
79
|
+
default:
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
109
82
|
}
|
|
110
83
|
|
|
111
84
|
get isMaskInterleaved(): boolean {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"util.enum.d.ts","sourceRoot":"","sources":["../../src/util/util.enum.ts"],"names":[],"mappings":"AACA;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,CAUlF"}
|
package/build/util/util.enum.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
const REVERSE_ENUM_KEY = '__reverse';
|
|
2
|
-
/**
|
|
3
|
-
* Typescript enums do not allow for easy reverse lookup of keys
|
|
4
|
-
* This hacks a reverse lookup key onto the enum
|
|
5
|
-
*/
|
|
6
|
-
export function getReverseEnumValue(e, value) {
|
|
7
|
-
if (e[REVERSE_ENUM_KEY] == null) {
|
|
8
|
-
const reverse = {};
|
|
9
|
-
for (const key of Object.keys(e)) {
|
|
10
|
-
const val = e[key];
|
|
11
|
-
reverse[val] = key;
|
|
12
|
-
}
|
|
13
|
-
Object.defineProperty(e, REVERSE_ENUM_KEY, { enumerable: false, value: reverse, writable: false });
|
|
14
|
-
}
|
|
15
|
-
return e[REVERSE_ENUM_KEY][value];
|
|
16
|
-
}
|
|
17
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbC5lbnVtLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3V0aWwvdXRpbC5lbnVtLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE1BQU0sZ0JBQWdCLEdBQUcsV0FBVyxDQUFDO0FBQ3JDOzs7R0FHRztBQUNILE1BQU0sVUFBVSxtQkFBbUIsQ0FBSSxDQUF5QixFQUFFLEtBQWE7SUFDM0UsSUFBSSxDQUFDLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxJQUFJLEVBQUU7UUFDN0IsTUFBTSxPQUFPLEdBQXdCLEVBQUUsQ0FBQztRQUN4QyxLQUFLLE1BQU0sR0FBRyxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUU7WUFDOUIsTUFBTSxHQUFHLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ25CLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxHQUFHLENBQUM7U0FDdEI7UUFDRCxNQUFNLENBQUMsY0FBYyxDQUFDLENBQUMsRUFBRSxnQkFBZ0IsRUFBRSxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQztLQUN0RztJQUNELE9BQU8sQ0FBQyxDQUFDLGdCQUFnQixDQUFDLENBQUMsS0FBWSxDQUFRLENBQUM7QUFDcEQsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImNvbnN0IFJFVkVSU0VfRU5VTV9LRVkgPSAnX19yZXZlcnNlJztcbi8qKlxuICogVHlwZXNjcmlwdCBlbnVtcyBkbyBub3QgYWxsb3cgZm9yIGVhc3kgcmV2ZXJzZSBsb29rdXAgb2Yga2V5c1xuICogVGhpcyBoYWNrcyBhIHJldmVyc2UgbG9va3VwIGtleSBvbnRvIHRoZSBlbnVtXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRSZXZlcnNlRW51bVZhbHVlPFQ+KGU6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4sIHZhbHVlOiBzdHJpbmcpOiBUIHtcbiAgICBpZiAoZVtSRVZFUlNFX0VOVU1fS0VZXSA9PSBudWxsKSB7XG4gICAgICAgIGNvbnN0IHJldmVyc2U6IFJlY29yZDxzdHJpbmcsIGFueT4gPSB7fTtcbiAgICAgICAgZm9yIChjb25zdCBrZXkgb2YgT2JqZWN0LmtleXMoZSkpIHtcbiAgICAgICAgICAgIGNvbnN0IHZhbCA9IGVba2V5XTtcbiAgICAgICAgICAgIHJldmVyc2VbdmFsXSA9IGtleTtcbiAgICAgICAgfVxuICAgICAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkoZSwgUkVWRVJTRV9FTlVNX0tFWSwgeyBlbnVtZXJhYmxlOiBmYWxzZSwgdmFsdWU6IHJldmVyc2UsIHdyaXRhYmxlOiBmYWxzZSB9KTtcbiAgICB9XG4gICAgcmV0dXJuIGVbUkVWRVJTRV9FTlVNX0tFWV1bdmFsdWUgYXMgYW55XSBhcyBhbnk7XG59XG4iXX0=
|
package/src/util/util.enum.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
const REVERSE_ENUM_KEY = '__reverse';
|
|
2
|
-
/**
|
|
3
|
-
* Typescript enums do not allow for easy reverse lookup of keys
|
|
4
|
-
* This hacks a reverse lookup key onto the enum
|
|
5
|
-
*/
|
|
6
|
-
export function getReverseEnumValue<T>(e: Record<string, string>, value: string): T {
|
|
7
|
-
if (e[REVERSE_ENUM_KEY] == null) {
|
|
8
|
-
const reverse: Record<string, any> = {};
|
|
9
|
-
for (const key of Object.keys(e)) {
|
|
10
|
-
const val = e[key];
|
|
11
|
-
reverse[val] = key;
|
|
12
|
-
}
|
|
13
|
-
Object.defineProperty(e, REVERSE_ENUM_KEY, { enumerable: false, value: reverse, writable: false });
|
|
14
|
-
}
|
|
15
|
-
return e[REVERSE_ENUM_KEY][value as any] as any;
|
|
16
|
-
}
|