@cogeotiff/cli 7.2.0 → 8.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 (74) hide show
  1. package/CHANGELOG.md +7 -490
  2. package/README.md +26 -34
  3. package/bin/cogeotiff.js +1 -2
  4. package/build/action.util.d.ts +2 -8
  5. package/build/action.util.js +7 -18
  6. package/build/action.util.js.map +1 -0
  7. package/build/bin.d.ts +1 -0
  8. package/build/bin.js +13 -0
  9. package/build/bin.js.map +1 -0
  10. package/build/cli.table.d.ts +0 -1
  11. package/build/cli.table.js +9 -4
  12. package/build/cli.table.js.map +1 -0
  13. package/build/commands/dump.d.ts +27 -0
  14. package/build/commands/dump.js +132 -0
  15. package/build/commands/dump.js.map +1 -0
  16. package/build/commands/info.d.ts +19 -0
  17. package/build/commands/info.js +185 -0
  18. package/build/commands/info.js.map +1 -0
  19. package/build/common.d.ts +16 -0
  20. package/build/common.js +19 -0
  21. package/build/common.js.map +1 -0
  22. package/build/fs.d.ts +6 -0
  23. package/build/fs.js +17 -0
  24. package/build/fs.js.map +1 -0
  25. package/build/index.d.ts +57 -2
  26. package/build/index.js +11 -8
  27. package/build/index.js.map +1 -0
  28. package/build/log.d.ts +11 -0
  29. package/build/log.js +37 -0
  30. package/build/log.js.map +1 -0
  31. package/build/util.bytes.d.ts +0 -1
  32. package/build/util.bytes.js +1 -1
  33. package/build/util.bytes.js.map +1 -0
  34. package/build/util.tile.d.ts +2 -3
  35. package/build/util.tile.js +20 -18
  36. package/build/util.tile.js.map +1 -0
  37. package/package.json +37 -37
  38. package/src/action.util.ts +22 -37
  39. package/src/bin.ts +14 -0
  40. package/src/cli.table.ts +27 -27
  41. package/src/commands/dump.ts +156 -0
  42. package/src/commands/info.ts +199 -0
  43. package/src/common.ts +20 -0
  44. package/src/fs.ts +18 -0
  45. package/src/index.ts +10 -7
  46. package/src/log.ts +43 -0
  47. package/src/util.bytes.ts +6 -6
  48. package/src/util.tile.ts +33 -33
  49. package/tsconfig.json +8 -10
  50. package/build/action.dump.tile.d.ts +0 -24
  51. package/build/action.dump.tile.d.ts.map +0 -1
  52. package/build/action.dump.tile.js +0 -194
  53. package/build/action.info.d.ts +0 -10
  54. package/build/action.info.d.ts.map +0 -1
  55. package/build/action.info.js +0 -183
  56. package/build/action.tile.d.ts +0 -9
  57. package/build/action.tile.d.ts.map +0 -1
  58. package/build/action.tile.js +0 -64
  59. package/build/action.util.d.ts.map +0 -1
  60. package/build/cli.cog.info.d.ts +0 -9
  61. package/build/cli.cog.info.d.ts.map +0 -1
  62. package/build/cli.cog.info.js +0 -42
  63. package/build/cli.log.d.ts +0 -3
  64. package/build/cli.log.d.ts.map +0 -1
  65. package/build/cli.log.js +0 -4
  66. package/build/cli.table.d.ts.map +0 -1
  67. package/build/index.d.ts.map +0 -1
  68. package/build/util.bytes.d.ts.map +0 -1
  69. package/build/util.tile.d.ts.map +0 -1
  70. package/src/action.dump.tile.ts +0 -240
  71. package/src/action.info.ts +0 -205
  72. package/src/action.tile.ts +0 -76
  73. package/src/cli.cog.info.ts +0 -45
  74. package/src/cli.log.ts +0 -4
package/build/log.js ADDED
@@ -0,0 +1,37 @@
1
+ import { FsHttp, fsa } from '@chunkd/fs';
2
+ import { SourceCache, SourceChunk } from '@chunkd/middleware';
3
+ import { log } from '@linzjs/tracing';
4
+ import { FetchLog } from './fs.js';
5
+ // Cache the last 10MB of chunks for reuse
6
+ export const sourceCache = new SourceCache({ size: 10 * 1024 * 1024 });
7
+ export const sourceChunk = new SourceChunk({ size: 32 * 1024 });
8
+ export function setupLogger(cfg) {
9
+ if (cfg.verbose) {
10
+ log.level = 'debug';
11
+ }
12
+ else if (cfg.extraVerbose) {
13
+ log.level = 'trace';
14
+ }
15
+ else {
16
+ log.level = 'warn';
17
+ }
18
+ fsa.register('http://', new FsHttp());
19
+ fsa.register('https://', new FsHttp());
20
+ // Order of these are really important
21
+ // Chunk all requests into 32KB chunks
22
+ fsa.middleware.push(sourceChunk);
23
+ // Cache the last 10MB of chunks for reuse
24
+ fsa.middleware.push(sourceCache);
25
+ fsa.middleware.push(FetchLog);
26
+ return log;
27
+ }
28
+ export const logger = log;
29
+ /** S3 client adds approx 300ms to the cli startup time, so only register it if needed */
30
+ export async function ensureS3fs() {
31
+ if (fsa.systems.find((f) => f.prefix.startsWith('s3')))
32
+ return;
33
+ const S3Client = await import('@aws-sdk/client-s3');
34
+ const FsAwsS3 = await import('@chunkd/fs-aws');
35
+ fsa.register('s3://', new FsAwsS3.FsAwsS3(new S3Client.S3Client({})));
36
+ }
37
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,0CAA0C;AAC1C,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC;AACvE,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;AAEhE,MAAM,UAAU,WAAW,CAAC,GAAkD;IAC5E,IAAI,GAAG,CAAC,OAAO,EAAE;QACf,GAAG,CAAC,KAAK,GAAG,OAAO,CAAC;KACrB;SAAM,IAAI,GAAG,CAAC,YAAY,EAAE;QAC3B,GAAG,CAAC,KAAK,GAAG,OAAO,CAAC;KACrB;SAAM;QACL,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC;KACpB;IAED,GAAG,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,MAAM,EAAE,CAAC,CAAC;IACtC,GAAG,CAAC,QAAQ,CAAC,UAAU,EAAE,IAAI,MAAM,EAAE,CAAC,CAAC;IAEvC,sCAAsC;IACtC,sCAAsC;IACtC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACjC,0CAA0C;IAC1C,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAEjC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAE9B,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAC;AAE1B,yFAAyF;AACzF,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO;IAE/D,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;IACpD,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAE/C,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AACxE,CAAC"}
@@ -6,4 +6,3 @@
6
6
  * @param bytes byte count to convert
7
7
  */
8
8
  export declare function toByteSizeString(bytes: number): string;
9
- //# sourceMappingURL=util.bytes.d.ts.map
@@ -16,4 +16,4 @@ export function toByteSizeString(bytes) {
16
16
  return `${Math.round(output)} ${byteSize}`;
17
17
  return `${Math.floor(output * 100) / 100} ${byteSize}`;
18
18
  }
19
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbC5ieXRlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy91dGlsLmJ5dGVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE1BQU0sS0FBSyxHQUFHLENBQUMsT0FBTyxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxDQUFDO0FBRWhEOzs7Ozs7R0FNRztBQUNILE1BQU0sVUFBVSxnQkFBZ0IsQ0FBQyxLQUFhO0lBQzFDLElBQUksS0FBSyxLQUFLLENBQUM7UUFBRSxPQUFPLFFBQVEsQ0FBQztJQUNqQyxNQUFNLENBQUMsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBQ3ZELE1BQU0sTUFBTSxHQUFHLEtBQUssR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQztJQUN6QyxNQUFNLFFBQVEsR0FBRyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFMUIsSUFBSSxDQUFDLEtBQUssQ0FBQztRQUFFLE9BQU8sR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxJQUFJLFFBQVEsRUFBRSxDQUFDO0lBQ3hELE9BQU8sR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sR0FBRyxHQUFHLENBQUMsR0FBRyxHQUFHLElBQUksUUFBUSxFQUFFLENBQUM7QUFDM0QsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImNvbnN0IHNpemVzID0gWydCeXRlcycsICdLQicsICdNQicsICdHQicsICdUQiddO1xuXG4vKipcbiAqIENvbnZlcnQgYSBieXRlIGNvdW50IGludG8gaHVtYW4gcmVhZGFibGUgYnl0ZSBudW1iZXJzXG4gKlxuICogZWcgMTAyNCA9PiAxS0JcbiAqXG4gKiBAcGFyYW0gYnl0ZXMgIGJ5dGUgY291bnQgdG8gY29udmVydFxuICovXG5leHBvcnQgZnVuY3Rpb24gdG9CeXRlU2l6ZVN0cmluZyhieXRlczogbnVtYmVyKTogc3RyaW5nIHtcbiAgICBpZiAoYnl0ZXMgPT09IDEpIHJldHVybiAnMSBCeXRlJztcbiAgICBjb25zdCBpID0gTWF0aC5mbG9vcihNYXRoLmxvZyhieXRlcykgLyBNYXRoLmxvZygxMDI0KSk7XG4gICAgY29uc3Qgb3V0cHV0ID0gYnl0ZXMgLyBNYXRoLnBvdygxMDI0LCBpKTtcbiAgICBjb25zdCBieXRlU2l6ZSA9IHNpemVzW2ldO1xuXG4gICAgaWYgKGkgPT09IDEpIHJldHVybiBgJHtNYXRoLnJvdW5kKG91dHB1dCl9ICR7Ynl0ZVNpemV9YDtcbiAgICByZXR1cm4gYCR7TWF0aC5mbG9vcihvdXRwdXQgKiAxMDApIC8gMTAwfSAke2J5dGVTaXplfWA7XG59XG4iXX0=
19
+ //# sourceMappingURL=util.bytes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.bytes.js","sourceRoot":"","sources":["../src/util.bytes.ts"],"names":[],"mappings":"AAAA,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAEhD;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IACjC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAE1B,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;IACxD,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,IAAI,QAAQ,EAAE,CAAC;AACzD,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { CogTiff } from '@cogeotiff/core';
2
- import type pino from 'pino';
2
+ import { log } from '@linzjs/tracing';
3
3
  /**
4
4
  * Get a human readable tile name
5
5
  *
@@ -11,5 +11,4 @@ import type pino from 'pino';
11
11
  * @returns tile name eg `001_002_12.png`
12
12
  */
13
13
  export declare function getTileName(mimeType: string, index: number, x: number, y: number): string;
14
- export declare function writeTile(tif: CogTiff, x: number, y: number, index: number, outputPath: string, logger: pino.Logger): Promise<void>;
15
- //# sourceMappingURL=util.tile.d.ts.map
14
+ export declare function writeTile(tiff: CogTiff, x: number, y: number, index: number, outputPath: URL, logger: typeof log): Promise<string | null>;
@@ -1,12 +1,16 @@
1
1
  import { TiffMimeType } from '@cogeotiff/core';
2
- import { promises as fs } from 'fs';
3
- import * as path from 'path';
2
+ import { promises as fs } from 'node:fs';
4
3
  const FileExtension = {
5
- [TiffMimeType.JPEG]: 'jpeg',
6
- [TiffMimeType.JP2]: 'jp2',
7
- [TiffMimeType.WEBP]: 'webp',
8
- [TiffMimeType.LZW]: 'lzw',
9
- [TiffMimeType.DEFLATE]: 'deflate',
4
+ [TiffMimeType.Jpeg]: 'jpeg',
5
+ [TiffMimeType.Jp2]: 'jp2',
6
+ [TiffMimeType.Webp]: 'webp',
7
+ [TiffMimeType.Lzw]: 'lzw',
8
+ [TiffMimeType.Deflate]: 'deflate',
9
+ [TiffMimeType.None]: 'bin',
10
+ [TiffMimeType.JpegXl]: 'jpeg',
11
+ [TiffMimeType.Zstd]: 'zstd',
12
+ [TiffMimeType.Lerc]: 'lerc',
13
+ [TiffMimeType.Lzma]: 'lzma',
10
14
  };
11
15
  /**
12
16
  * Get a human readable tile name
@@ -21,20 +25,18 @@ const FileExtension = {
21
25
  export function getTileName(mimeType, index, x, y) {
22
26
  const xS = `${x}`.padStart(3, '0');
23
27
  const yS = `${y}`.padStart(3, '0');
24
- const fileExt = FileExtension[mimeType];
25
- if (fileExt == null) {
26
- throw new Error(`Unable to process tile type:${mimeType}`);
27
- }
28
+ const fileExt = FileExtension[mimeType] ?? 'unknown';
28
29
  return `${xS}_${yS}_${index}.${fileExt}`;
29
30
  }
30
- export async function writeTile(tif, x, y, index, outputPath, logger) {
31
- const tile = await tif.getTile(x, y, index);
31
+ export async function writeTile(tiff, x, y, index, outputPath, logger) {
32
+ const tile = await tiff.images[index].getTile(x, y);
32
33
  if (tile == null) {
33
- logger.debug({ index, x, y }, 'TileEmpty');
34
- return;
34
+ logger.debug('Tile:Empty', { source: tiff.source.url.href, index, x, y });
35
+ return null;
35
36
  }
36
37
  const fileName = getTileName(tile.mimeType, index, x, y);
37
- await fs.writeFile(path.join(outputPath, fileName), tile.bytes);
38
- logger.debug({ index, x, y, fileName, bytes: tile.bytes.length }, 'TileWrite');
38
+ await fs.writeFile(new URL(fileName, outputPath), Buffer.from(tile.bytes));
39
+ logger.debug('Tile:Write', { source: tiff.source.url.href, index, x, y, fileName, bytes: tile.bytes.byteLength });
40
+ return fileName;
39
41
  }
40
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbC50aWxlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3V0aWwudGlsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQVcsWUFBWSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDeEQsT0FBTyxFQUFFLFFBQVEsSUFBSSxFQUFFLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFDcEMsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUM7QUFHN0IsTUFBTSxhQUFhLEdBQThCO0lBQzdDLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxFQUFFLE1BQU07SUFDM0IsQ0FBQyxZQUFZLENBQUMsR0FBRyxDQUFDLEVBQUUsS0FBSztJQUN6QixDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsRUFBRSxNQUFNO0lBQzNCLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQyxFQUFFLEtBQUs7SUFDekIsQ0FBQyxZQUFZLENBQUMsT0FBTyxDQUFDLEVBQUUsU0FBUztDQUNwQyxDQUFDO0FBRUY7Ozs7Ozs7OztHQVNHO0FBQ0gsTUFBTSxVQUFVLFdBQVcsQ0FBQyxRQUFnQixFQUFFLEtBQWEsRUFBRSxDQUFTLEVBQUUsQ0FBUztJQUM3RSxNQUFNLEVBQUUsR0FBRyxHQUFHLENBQUMsRUFBRSxDQUFDLFFBQVEsQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDbkMsTUFBTSxFQUFFLEdBQUcsR0FBRyxDQUFDLEVBQUUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFDO0lBRW5DLE1BQU0sT0FBTyxHQUFXLGFBQWEsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUNoRCxJQUFJLE9BQU8sSUFBSSxJQUFJLEVBQUU7UUFDakIsTUFBTSxJQUFJLEtBQUssQ0FBQywrQkFBK0IsUUFBUSxFQUFFLENBQUMsQ0FBQztLQUM5RDtJQUVELE9BQU8sR0FBRyxFQUFFLElBQUksRUFBRSxJQUFJLEtBQUssSUFBSSxPQUFPLEVBQUUsQ0FBQztBQUM3QyxDQUFDO0FBRUQsTUFBTSxDQUFDLEtBQUssVUFBVSxTQUFTLENBQzNCLEdBQVksRUFDWixDQUFTLEVBQ1QsQ0FBUyxFQUNULEtBQWEsRUFDYixVQUFrQixFQUNsQixNQUFtQjtJQUVuQixNQUFNLElBQUksR0FBRyxNQUFNLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQztJQUM1QyxJQUFJLElBQUksSUFBSSxJQUFJLEVBQUU7UUFDZCxNQUFNLENBQUMsS0FBSyxDQUFDLEVBQUUsS0FBSyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxXQUFXLENBQUMsQ0FBQztRQUMzQyxPQUFPO0tBQ1Y7SUFDRCxNQUFNLFFBQVEsR0FBRyxXQUFXLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxLQUFLLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ3pELE1BQU0sRUFBRSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxRQUFRLENBQUMsRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDaEUsTUFBTSxDQUFDLEtBQUssQ0FBQyxFQUFFLEtBQUssRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLEVBQUUsRUFBRSxXQUFXLENBQUMsQ0FBQztBQUNuRixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29nVGlmZiwgVGlmZk1pbWVUeXBlIH0gZnJvbSAnQGNvZ2VvdGlmZi9jb3JlJztcbmltcG9ydCB7IHByb21pc2VzIGFzIGZzIH0gZnJvbSAnZnMnO1xuaW1wb3J0ICogYXMgcGF0aCBmcm9tICdwYXRoJztcbmltcG9ydCB0eXBlIHBpbm8gZnJvbSAncGlubyc7XG5cbmNvbnN0IEZpbGVFeHRlbnNpb246IHsgW2tleTogc3RyaW5nXTogc3RyaW5nIH0gPSB7XG4gICAgW1RpZmZNaW1lVHlwZS5KUEVHXTogJ2pwZWcnLFxuICAgIFtUaWZmTWltZVR5cGUuSlAyXTogJ2pwMicsXG4gICAgW1RpZmZNaW1lVHlwZS5XRUJQXTogJ3dlYnAnLFxuICAgIFtUaWZmTWltZVR5cGUuTFpXXTogJ2x6dycsXG4gICAgW1RpZmZNaW1lVHlwZS5ERUZMQVRFXTogJ2RlZmxhdGUnLFxufTtcblxuLyoqXG4gKiBHZXQgYSBodW1hbiByZWFkYWJsZSB0aWxlIG5hbWVcbiAqXG4gKiBAcGFyYW0gbWltZVR5cGUgaW1hZ2UgdHlwZSBvZiB0aWxlIEBzZWUgRmlsZUV4dGVuc2lvblxuICogQHBhcmFtIGluZGV4IEltYWdlIGluZGV4XG4gKiBAcGFyYW0geCBUaWxlIFhcbiAqIEBwYXJhbSB5IFRpbGUgWVxuICpcbiAqIEByZXR1cm5zIHRpbGUgbmFtZSBlZyBgMDAxXzAwMl8xMi5wbmdgXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRUaWxlTmFtZShtaW1lVHlwZTogc3RyaW5nLCBpbmRleDogbnVtYmVyLCB4OiBudW1iZXIsIHk6IG51bWJlcik6IHN0cmluZyB7XG4gICAgY29uc3QgeFMgPSBgJHt4fWAucGFkU3RhcnQoMywgJzAnKTtcbiAgICBjb25zdCB5UyA9IGAke3l9YC5wYWRTdGFydCgzLCAnMCcpO1xuXG4gICAgY29uc3QgZmlsZUV4dDogc3RyaW5nID0gRmlsZUV4dGVuc2lvblttaW1lVHlwZV07XG4gICAgaWYgKGZpbGVFeHQgPT0gbnVsbCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYFVuYWJsZSB0byBwcm9jZXNzIHRpbGUgdHlwZToke21pbWVUeXBlfWApO1xuICAgIH1cblxuICAgIHJldHVybiBgJHt4U31fJHt5U31fJHtpbmRleH0uJHtmaWxlRXh0fWA7XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiB3cml0ZVRpbGUoXG4gICAgdGlmOiBDb2dUaWZmLFxuICAgIHg6IG51bWJlcixcbiAgICB5OiBudW1iZXIsXG4gICAgaW5kZXg6IG51bWJlcixcbiAgICBvdXRwdXRQYXRoOiBzdHJpbmcsXG4gICAgbG9nZ2VyOiBwaW5vLkxvZ2dlcixcbik6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IHRpbGUgPSBhd2FpdCB0aWYuZ2V0VGlsZSh4LCB5LCBpbmRleCk7XG4gICAgaWYgKHRpbGUgPT0gbnVsbCkge1xuICAgICAgICBsb2dnZXIuZGVidWcoeyBpbmRleCwgeCwgeSB9LCAnVGlsZUVtcHR5Jyk7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgZmlsZU5hbWUgPSBnZXRUaWxlTmFtZSh0aWxlLm1pbWVUeXBlLCBpbmRleCwgeCwgeSk7XG4gICAgYXdhaXQgZnMud3JpdGVGaWxlKHBhdGguam9pbihvdXRwdXRQYXRoLCBmaWxlTmFtZSksIHRpbGUuYnl0ZXMpO1xuICAgIGxvZ2dlci5kZWJ1Zyh7IGluZGV4LCB4LCB5LCBmaWxlTmFtZSwgYnl0ZXM6IHRpbGUuYnl0ZXMubGVuZ3RoIH0sICdUaWxlV3JpdGUnKTtcbn1cbiJdfQ==
42
+ //# sourceMappingURL=util.tile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.tile.js","sourceRoot":"","sources":["../src/util.tile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAExD,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,aAAa,GAA2B;IAC5C,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,MAAM;IAC3B,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,KAAK;IACzB,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,MAAM;IAC3B,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,KAAK;IACzB,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,SAAS;IACjC,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,KAAK;IAC1B,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM;IAC7B,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,MAAM;IAC3B,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,MAAM;IAC3B,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,MAAM;CAC5B,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,KAAa,EAAE,CAAS,EAAE,CAAS;IAC/E,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACnC,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC;IACrD,OAAO,GAAG,EAAE,IAAI,EAAE,IAAI,KAAK,IAAI,OAAO,EAAE,CAAC;AAC3C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,IAAa,EACb,CAAS,EACT,CAAS,EACT,KAAa,EACb,UAAe,EACf,MAAkB;IAElB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACpD,IAAI,IAAI,IAAI,IAAI,EAAE;QAChB,MAAM,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC1E,OAAO,IAAI,CAAC;KACb;IACD,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACzD,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3E,MAAM,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;IAClH,OAAO,QAAQ,CAAC;AAClB,CAAC"}
package/package.json CHANGED
@@ -1,39 +1,39 @@
1
1
  {
2
- "name": "@cogeotiff/cli",
3
- "version": "7.2.0",
4
- "repository": {
5
- "type": "git",
6
- "url": "https://github.com/blacha/cogeotiff.git",
7
- "directory": "packages/cli"
8
- },
9
- "author": "Blayne Chard",
10
- "license": "MIT",
11
- "bin": {
12
- "cogeotiff": "bin/cogeotiff.js"
13
- },
14
- "type": "module",
15
- "engines": {
16
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
17
- },
18
- "scripts": {},
19
- "dependencies": {
20
- "@chunkd/core": "^8.1.0",
21
- "@chunkd/fs": "^8.1.0",
22
- "@cogeotiff/core": "^7.2.0",
23
- "@rushstack/ts-command-line": "^4.3.14",
24
- "ansi-colors": "^4.1.1",
25
- "aws-sdk": "^2.781.0",
26
- "p-limit": "^4.0.0",
27
- "pino": "^8.0.0",
28
- "pretty-json-log": "^1.0.0",
29
- "source-map-support": "^0.5.12"
30
- },
31
- "devDependencies": {
32
- "@types/node": "^18.0.0",
33
- "@types/pino": "^7.0.5"
34
- },
35
- "publishConfig": {
36
- "access": "public"
37
- },
38
- "gitHead": "64a437a3590a1fc8395d1f4294a5fc74cbbe6127"
2
+ "name": "@cogeotiff/cli",
3
+ "version": "8.0.1",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/blacha/cogeotiff.git",
7
+ "directory": "packages/cli"
8
+ },
9
+ "author": "Blayne Chard",
10
+ "license": "MIT",
11
+ "bin": {
12
+ "cogeotiff": "bin/cogeotiff.js"
13
+ },
14
+ "type": "module",
15
+ "engines": {
16
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
17
+ },
18
+ "scripts": {},
19
+ "dependencies": {
20
+ "@chunkd/fs": "^11.0.2",
21
+ "@chunkd/fs-aws": "^11.0.2",
22
+ "@chunkd/middleware": "^11.0.0",
23
+ "@chunkd/source": "^11.0.0",
24
+ "@cogeotiff/core": "^8.0.1",
25
+ "@linzjs/tracing": "^1.1.1",
26
+ "ansi-colors": "^4.1.1",
27
+ "cmd-ts": "^0.13.0",
28
+ "p-limit": "^4.0.0",
29
+ "pretty-json-log": "^1.4.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.0.0",
33
+ "@types/pino": "^7.0.5"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "gitHead": "6e7b4f6250189ce8a3fa88641eaf35538b104ddd"
39
39
  }
@@ -1,47 +1,32 @@
1
- import { ChunkSource } from '@chunkd/core';
2
- import { fsa } from '@chunkd/fs';
3
- import { CogTiff } from '@cogeotiff/core';
4
- import { CommandLineStringParameter } from '@rushstack/ts-command-line';
5
1
  import c from 'ansi-colors';
6
2
 
7
3
  export interface CLiResultMapLine {
8
- key: string;
9
- value: string | number | boolean | number[] | null;
4
+ key: string;
5
+ value: string | number | boolean | number[] | null;
6
+ enabled?: boolean;
10
7
  }
11
8
  export interface CliResultMap {
12
- title?: string;
13
- keys: (CLiResultMapLine | null)[];
9
+ title?: string;
10
+ keys: (CLiResultMapLine | null)[];
11
+ enabled?: boolean;
14
12
  }
15
13
 
16
14
  export const ActionUtil = {
17
- async getCogSource(file?: CommandLineStringParameter | null): Promise<{ source: ChunkSource; tif: CogTiff }> {
18
- if (file == null || file.value == null) {
19
- throw new Error(`File "${file} is not valid`);
15
+ formatResult(title: string, result: CliResultMap[]): string[] {
16
+ const msg: string[] = [title];
17
+ for (const group of result) {
18
+ if (group.enabled === false) continue;
19
+ msg.push('');
20
+ if (group.title) msg.push(c.bold(group.title));
21
+ for (const kv of group.keys) {
22
+ if (kv == null) continue;
23
+ if (kv.enabled === false) continue;
24
+ if (kv.value == null || (typeof kv.value === 'string' && kv.value.trim() === '')) {
25
+ continue;
20
26
  }
21
- const source = fsa.source(file.value);
22
- if (source == null) throw new Error(`File "${file} is not valid`);
23
-
24
- const tif = new CogTiff(source);
25
- await tif.init(false);
26
- return { source, tif };
27
- },
28
- formatResult(title: string, result: CliResultMap[]): string[] {
29
- const msg: string[] = [title];
30
- for (const group of result) {
31
- msg.push('');
32
- if (group.title) {
33
- msg.push(c.bold(group.title));
34
- }
35
- for (const kv of group.keys) {
36
- if (kv == null) {
37
- continue;
38
- }
39
- if (kv.value == null || (typeof kv.value === 'string' && kv.value.trim() === '')) {
40
- continue;
41
- }
42
- msg.push(` ${kv.key.padEnd(14, ' ')} ${String(kv.value)}`);
43
- }
44
- }
45
- return msg;
46
- },
27
+ msg.push(` ${kv.key.padEnd(14, ' ')} ${String(kv.value)}`);
28
+ }
29
+ }
30
+ return msg;
31
+ },
47
32
  };
package/src/bin.ts ADDED
@@ -0,0 +1,14 @@
1
+ // Ensure connection reuse is enabled
2
+ process.env['AWS_NODEJS_CONNECTION_REUSE_ENABLED'] = '1';
3
+
4
+ import { run } from 'cmd-ts';
5
+ import { cmd } from './index.js';
6
+ import { logger } from './log.js';
7
+ import { otError } from '@linzjs/tracing';
8
+
9
+ run(cmd, process.argv.slice(2)).catch((err) => {
10
+ logger.fatal('Command:Failed', { ...otError(err) });
11
+ logger.pino.flush();
12
+ // Give the log some time to flush before exiting
13
+ setTimeout(() => process.exit(1), 25);
14
+ });
package/src/cli.table.ts CHANGED
@@ -1,34 +1,34 @@
1
1
  interface CliTableInfo<T> {
2
- /** Header for the table */
3
- name: string;
4
- /** Pad the field out to this width */
5
- width: number;
6
- /** Get the field value */
7
- get: (obj: T, index?: number) => string | null;
8
- /**
9
- * Is this field enabled,
10
- * Fields are only enabled if every data record returns true for enabled
11
- */
12
- enabled?: (obj: T) => boolean;
2
+ /** Header for the table */
3
+ name: string;
4
+ /** Pad the field out to this width */
5
+ width: number;
6
+ /** Get the field value */
7
+ get: (obj: T, index?: number) => string | null;
8
+ /**
9
+ * Is this field enabled,
10
+ * Fields are only enabled if every data record returns true for enabled
11
+ */
12
+ enabled?: (obj: T) => boolean;
13
13
  }
14
14
 
15
15
  export class CliTable<T> {
16
- fields: CliTableInfo<T>[] = [];
17
- add(fields: CliTableInfo<T>): void {
18
- this.fields.push(fields);
19
- }
16
+ fields: CliTableInfo<T>[] = [];
17
+ add(fields: CliTableInfo<T>): void {
18
+ this.fields.push(fields);
19
+ }
20
20
 
21
- print(data: T[], rowPadding = ''): string[] {
22
- const fields = this.fields.filter((f) => data.every((d) => f.enabled?.(d) ?? true));
23
- const rows = data.map((d, index) => {
24
- const row = fields.map((f) => {
25
- const str = f.get(d, index);
26
- return (str ?? '').padEnd(f.width);
27
- });
28
- return rowPadding + row.join('\t');
29
- });
21
+ print(data: T[], rowPadding = ''): string[] {
22
+ const fields = this.fields.filter((f) => data.every((d) => f.enabled?.(d) ?? true));
23
+ const rows = data.map((d, index) => {
24
+ const row = fields.map((f) => {
25
+ const str = f.get(d, index);
26
+ return (str ?? '').padEnd(f.width);
27
+ });
28
+ return rowPadding + row.join('\t');
29
+ });
30
30
 
31
- rows.unshift(rowPadding + fields.map((f) => f.name.padEnd(f.width)).join('\t'));
32
- return rows;
33
- }
31
+ rows.unshift(rowPadding + fields.map((f) => f.name.padEnd(f.width)).join('\t'));
32
+ return rows;
33
+ }
34
34
  }
@@ -0,0 +1,156 @@
1
+ import { fsa } from '@chunkd/fs';
2
+ import { CogTiff, TiffMimeType } from '@cogeotiff/core';
3
+ import { log } from '@linzjs/tracing';
4
+ import { command, number, option, optional, restPositionals } from 'cmd-ts';
5
+ import { promises as fs } from 'node:fs';
6
+ import { basename } from 'node:path';
7
+ import { pathToFileURL } from 'node:url';
8
+ import pLimit from 'p-limit';
9
+ import { DefaultArgs, Url } from '../common.js';
10
+ import { ensureS3fs, setupLogger } from '../log.js';
11
+ import { getTileName, writeTile } from '../util.tile.js';
12
+
13
+ const TileQueue = pLimit(5);
14
+
15
+ export interface GeoJsonPolygon {
16
+ type: 'Feature';
17
+ geometry: {
18
+ type: 'Polygon';
19
+ coordinates: [[[number, number], [number, number], [number, number], [number, number], [number, number]]];
20
+ };
21
+ properties: Record<string, unknown>;
22
+ }
23
+
24
+ function makePolygon(xMin: number, yMin: number, xMax: number, yMax: number): GeoJsonPolygon {
25
+ return {
26
+ type: 'Feature',
27
+ properties: {},
28
+ geometry: {
29
+ type: 'Polygon',
30
+ coordinates: [
31
+ [
32
+ [xMin, yMin],
33
+ [xMin, yMax],
34
+ [xMax, yMax],
35
+ [xMax, yMin],
36
+ [xMin, yMin],
37
+ ],
38
+ ],
39
+ },
40
+ };
41
+ }
42
+
43
+ export const commandDump = command({
44
+ name: 'dump',
45
+ args: {
46
+ ...DefaultArgs,
47
+ path: option({ short: 'f', long: 'file', type: optional(Url) }),
48
+ image: option({ short: 'i', long: 'image', description: 'Image Id to dump (starting from 0)', type: number }),
49
+ output: option({ short: 'o', long: 'output', type: optional(Url) }),
50
+ paths: restPositionals({ type: Url, description: 'Files to process' }),
51
+ },
52
+
53
+ async handler(args) {
54
+ const cwd = pathToFileURL(process.cwd() + '/');
55
+ const logger = setupLogger(args);
56
+ for (const path of [args.path, ...args.paths]) {
57
+ if (path == null) continue;
58
+ if (path.protocol === 's3:') await ensureS3fs();
59
+ const source = fsa.source(path);
60
+ const tiff = await new CogTiff(source).init();
61
+ const img = tiff.images[args.image];
62
+ if (!img.isTiled()) throw Error(`Tiff: ${path.href} file is not tiled.`);
63
+ const output = new URL(`${basename(path.href)}-i${args.image}/`, args.output ?? cwd);
64
+ await fs.mkdir(output, { recursive: true });
65
+
66
+ await dumpTiles(tiff, output, args.image, logger);
67
+ await dumpBounds(tiff, output, args.image);
68
+ }
69
+ },
70
+ });
71
+
72
+ async function dumpBounds(tiff: CogTiff, target: URL, index: number): Promise<void> {
73
+ const img = tiff.images[index];
74
+ if (!img.isTiled() || !img.isGeoLocated) return;
75
+
76
+ const features: GeoJsonPolygon[] = [];
77
+ const featureCollection: Record<string, unknown> = {
78
+ type: 'FeatureCollection',
79
+ features,
80
+ };
81
+
82
+ const tileCount = img.tileCount;
83
+ const tileInfo = img.tileSize;
84
+ const tileSize = img.size;
85
+
86
+ const firstImage = tiff.images[0];
87
+ if (firstImage.epsg !== 4326) {
88
+ featureCollection['crs'] = {
89
+ type: 'name',
90
+ properties: { name: `epsg:${firstImage.epsg}` },
91
+ };
92
+ }
93
+ firstImage.compression;
94
+ const firstTileSize = firstImage.size;
95
+ const origin = firstImage.origin;
96
+ const resolution = firstImage.resolution;
97
+
98
+ const xScale = (resolution[0] * firstTileSize.width) / tileSize.width;
99
+ const yScale = (resolution[1] * firstTileSize.height) / tileSize.height;
100
+
101
+ for (let y = 0; y < tileCount.y; y++) {
102
+ const yMax = origin[1] + y * tileInfo.height * yScale;
103
+ const yMin = yMax + tileInfo.height * yScale;
104
+ for (let x = 0; x < tileCount.x; x++) {
105
+ const xMin = origin[0] + x * tileInfo.width * xScale;
106
+ const xMax = xMin + tileInfo.width * xScale;
107
+ const poly = makePolygon(xMin, yMin, xMax, yMax);
108
+ poly.properties = { tile: getTileName(firstImage.compression ?? TiffMimeType.None, index, x, y) };
109
+ features.push(poly);
110
+ }
111
+ }
112
+
113
+ await fs.writeFile(new URL(`i${index}.bounds.geojson`, target), JSON.stringify(featureCollection, null, 2));
114
+ }
115
+
116
+ async function dumpTiles(tiff: CogTiff, target: URL, index: number, logger: typeof log): Promise<number> {
117
+ const promises: Promise<string | null>[] = [];
118
+ const img = tiff.images[index];
119
+ if (!img.isTiled()) return 0;
120
+
121
+ logger.info('Tiff:Info', { source: tiff.source.url, ...img.tileSize, ...img.tileCount });
122
+ const { tileCount, tileSize } = img;
123
+
124
+ const html = ['<html>'];
125
+ const styles = `<style>.c { min-width: ${tileSize.width}px; min-height: ${tileSize.height}px; outline: 1px #ff00fff0 }</style>`;
126
+ html.push(styles);
127
+ for (let y = 0; y < tileCount.y; y++) {
128
+ for (let x = 0; x < tileCount.x; x++) {
129
+ const promise = TileQueue(() => writeTile(tiff, x, y, index, target, logger));
130
+ promises.push(promise);
131
+ }
132
+ }
133
+
134
+ const result = await Promise.all(promises);
135
+ let i = 0;
136
+ for (let y = 0; y < tileCount.y; y++) {
137
+ html.push('\t<div style="display:flex;">');
138
+
139
+ for (let x = 0; x < tileCount.x; x++) {
140
+ const fileName = result[i];
141
+ if (fileName == null) {
142
+ html.push(`\t\t<div class="c"></div>`);
143
+ continue;
144
+ }
145
+ html.push(`\t\t<img class="c" src="./${fileName}" >`);
146
+
147
+ i++;
148
+ }
149
+ html.push('\t</div>');
150
+ }
151
+
152
+ html.push('</html>');
153
+ await fs.writeFile(new URL('index.html', target), html.join('\n'));
154
+
155
+ return promises.length;
156
+ }