CLI for Orbis.
Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.

131 righe
3.3 KiB

  1. import yargs from 'yargs';
  2. import { hideBin } from 'yargs/helpers';
  3. import { project, Bounds } from '@theoryofnekomata/orbis-core';
  4. import { writeFile } from 'fs/promises';
  5. import { basename, dirname, resolve } from 'path';
  6. type ProjectArgs = {
  7. input: string,
  8. projection: string,
  9. output?: string,
  10. bounds: Bounds,
  11. width?: number,
  12. height?: number,
  13. padding: [number, number],
  14. };
  15. const coerceNumber = (n?: string) => {
  16. if (typeof n === 'undefined') {
  17. return undefined;
  18. }
  19. const tryWidth = Number(n);
  20. if (Number.isFinite(tryWidth)) {
  21. return tryWidth;
  22. }
  23. return undefined;
  24. };
  25. const coercePadding = (n?: string) => {
  26. if (typeof n !== 'string') {
  27. return [0, 0];
  28. }
  29. const [paddingXString = '0', paddingYString = paddingXString] = n.split(';');
  30. return [
  31. Number(paddingXString),
  32. Number(paddingYString),
  33. ];
  34. };
  35. const coerceBounds = (n?: string): Bounds => {
  36. if (typeof n !== 'string') {
  37. return [
  38. [-180, 90],
  39. [180, -90],
  40. ] as Bounds;
  41. }
  42. return n
  43. .split(';')
  44. .map((p) => (
  45. p
  46. .split(',')
  47. .map((c: string) => Number(c))
  48. )) as Bounds;
  49. };
  50. const main = async (argv: string | readonly string[]) => {
  51. await yargs
  52. .option('interactive', {
  53. alias: 'i',
  54. })
  55. .command({
  56. command: 'project <input> <projection>',
  57. aliases: 'p',
  58. describe: 'Project an equirectangular PNG image to another projection.',
  59. builder: (y) => y
  60. .option('output', {
  61. alias: 'o',
  62. })
  63. .coerce('output', (output) => {
  64. if (!output) {
  65. return null;
  66. }
  67. if (typeof output !== 'string') {
  68. return null;
  69. }
  70. return output;
  71. })
  72. .option('width', {
  73. alias: 'w',
  74. })
  75. .coerce('width', coerceNumber)
  76. .option('height', {
  77. alias: 'h',
  78. })
  79. .coerce('height', coerceNumber)
  80. .option('padding', {
  81. alias: 'p',
  82. default: [0, 0],
  83. })
  84. .coerce('padding', coercePadding)
  85. .option('bounds', {
  86. alias: 'b',
  87. default: [[-180, 90], [180, -90]],
  88. })
  89. .coerce('bounds', coerceBounds),
  90. handler: async (projectArgvRaw) => {
  91. const projectArgv = projectArgvRaw as unknown as ProjectArgs;
  92. const outputPng = await project(projectArgv.input, [projectArgv.projection], {
  93. bounds: projectArgv.bounds,
  94. wrapAround: false,
  95. outputSize: {
  96. width: projectArgv.width,
  97. height: projectArgv.height,
  98. },
  99. outputPadding: {
  100. x: projectArgv.padding[0],
  101. y: projectArgv.padding[1],
  102. },
  103. });
  104. if (!outputPng) {
  105. process.stdout.write('No output created.\n');
  106. return;
  107. }
  108. const outputFilename = projectArgv.output ?? `${basename(projectArgv.input, '.png')}.out.png`;
  109. const outputPath = resolve(
  110. dirname(projectArgv.input),
  111. outputFilename.endsWith('.png') ? outputFilename : `${outputFilename}.png`,
  112. );
  113. await writeFile(outputPath, outputPng);
  114. process.stdout.write(`Created output file: ${outputPath}\n`);
  115. },
  116. })
  117. .demandCommand(1, 'Please specify a command')
  118. .help()
  119. .parse(
  120. hideBin((Array.isArray(argv) ? argv : [argv]) as string[]),
  121. );
  122. };
  123. void main(process.argv);