diff --git a/packages/cli/src/cli/cogify/action.make.cog.ts b/packages/cli/src/cli/cogify/action.make.cog.ts index 238b0691e..a9923e769 100644 --- a/packages/cli/src/cli/cogify/action.make.cog.ts +++ b/packages/cli/src/cli/cogify/action.make.cog.ts @@ -33,7 +33,7 @@ export class CommandMakeCog extends CommandLineAction { private target: CommandLineStringParameter; private cutline: CommandLineStringParameter; private blend: CommandLineIntegerParameter; - private maxPixelWidth: CommandLineIntegerParameter; + private alignedLevel: CommandLineIntegerParameter; private output: CommandLineStringParameter; private aws: CommandLineFlagParameter; @@ -85,10 +85,10 @@ export class CommandMakeCog extends CommandLineAction { description: 'Cutline blend', required: false, }); - this.maxPixelWidth = this.defineIntegerParameter({ - argumentName: 'MAX_PIXEL_WIDTH', - parameterLongName: '--max-pixel', - description: 'Maximum Pixel Width for the cogs', + this.alignedLevel = this.defineIntegerParameter({ + argumentName: 'ALIGNED_LEVEL', + parameterLongName: '--aligned-level', + description: 'Aligned level between resolution and cog', required: false, }); this.output = this.defineStringParameter({ @@ -185,7 +185,12 @@ export class CommandMakeCog extends CommandLineAction { const ctx: JobCreationContext = { imageryName, - override: { id, projection: Epsg.Nztm2000, resampling, maxImageSize: this.maxPixelWidth.value }, + override: { + id, + projection: Epsg.Nztm2000, + resampling, + alignedLevel: this.alignedLevel.value, + }, outputLocation: this.aws.value ? await this.findLocation(`s3://${bucket}/`) : { type: 'local' as const, path: '.' }, diff --git a/packages/cli/src/cog/__tests__/cutline.test.ts b/packages/cli/src/cog/__tests__/cutline.test.ts index db735b3b6..aa127cc9e 100644 --- a/packages/cli/src/cog/__tests__/cutline.test.ts +++ b/packages/cli/src/cog/__tests__/cutline.test.ts @@ -5,6 +5,7 @@ import { MultiPolygon } from '@linzjs/geojson'; import o from 'ospec'; import path from 'path'; import url from 'url'; +import { AlignedLevel } from '../constants.js'; import { Cutline, polyContainsBounds } from '../cutline.js'; import { SourceMetadata } from '../types.js'; import { SourceTiffTestHelper } from './source.tiff.testhelper.js'; @@ -163,11 +164,14 @@ o.spec('cutline', () => { o('full-extent 3857', () => { const cutline = new Cutline(GoogleTms); - const covering = cutline.optimizeCovering({ - projection: EpsgCode.Google, - bounds: [{ ...GoogleTms.extent, name: 'gebco' }], - resZoom: 5, - } as SourceMetadata); + const covering = cutline.optimizeCovering( + { + projection: EpsgCode.Google, + bounds: [{ ...GoogleTms.extent, name: 'gebco' }], + resZoom: 5, + } as SourceMetadata, + 4, + ); o(round(covering, 4)).deepEquals([ { @@ -207,7 +211,7 @@ o.spec('cutline', () => { const covering = cutline.optimizeCovering({ projection: EpsgCode.Nztm2000, bounds, - resZoom: 14, + resZoom: 7 + AlignedLevel, } as SourceMetadata); o(covering[4]).deepEquals({ @@ -253,11 +257,14 @@ o.spec('cutline', () => { const bounds = [tiff1, tiff2]; const cutline = new Cutline(GoogleTms, await Cutline.loadCutline(testDir + '/kapiti.geojson'), 500); - const covering = cutline.optimizeCovering({ - projection: EpsgCode.Nztm2000, - bounds, - resZoom: 22, - } as SourceMetadata); + const covering = cutline.optimizeCovering( + { + projection: EpsgCode.Nztm2000, + bounds, + resZoom: 22, + } as SourceMetadata, + 8, + ); o(round(cutline.clipPoly, 4)).deepEquals([ [ @@ -298,11 +305,14 @@ o.spec('cutline', () => { }, ]; - const covering = cutline.optimizeCovering({ - projection: EpsgCode.Nztm2000, - bounds, - resZoom: 22, - } as SourceMetadata); + const covering = cutline.optimizeCovering( + { + projection: EpsgCode.Nztm2000, + bounds, + resZoom: 22, + } as SourceMetadata, + 8, + ); o(round(cutline.clipPoly, 4)).deepEquals([ [ @@ -328,22 +338,28 @@ o.spec('cutline', () => { o('low res', () => { const cutline = new Cutline(GoogleTms); - const covering = cutline.optimizeCovering({ - projection: EpsgCode.Nztm2000, - bounds, - resZoom: 13, - } as SourceMetadata); + const covering = cutline.optimizeCovering( + { + projection: EpsgCode.Nztm2000, + bounds, + resZoom: 13, + } as SourceMetadata, + 9, + ); o(covering.length).equals(covering.length); o(covering.map((c) => c.name)).deepEquals(['8-252-159', '8-252-160']); }); o('hi res', () => { - const covering2 = new Cutline(GoogleTms).optimizeCovering({ - projection: EpsgCode.Nztm2000, - bounds, - resZoom: 18, - } as SourceMetadata); + const covering2 = new Cutline(GoogleTms).optimizeCovering( + { + projection: EpsgCode.Nztm2000, + bounds, + resZoom: 18, + } as SourceMetadata, + 8, + ); o(covering2.length).equals(covering2.length); diff --git a/packages/cli/src/cog/builder.ts b/packages/cli/src/cog/builder.ts index 9039a93a2..1aad74dba 100644 --- a/packages/cli/src/cog/builder.ts +++ b/packages/cli/src/cog/builder.ts @@ -188,11 +188,11 @@ export class CogBuilder { * @param tiffs list of source imagery to be converted * @returns List of Tile bounds covering tiffs */ - async build(tiffs: ChunkSource[], cutline: Cutline, maxImageSize?: number): Promise { + async build(tiffs: ChunkSource[], cutline: Cutline, alignedLevel?: number): Promise { const metadata = await this.bounds(tiffs); // Ensure that the projection definition is loaded await ProjectionLoader.load(metadata.projection); - const files = cutline.optimizeCovering(metadata, maxImageSize); + const files = cutline.optimizeCovering(metadata, alignedLevel); let union: Bounds | null = null; for (const bounds of files) { if (union == null) union = Bounds.fromJson(bounds); diff --git a/packages/cli/src/cog/cog.stac.job.ts b/packages/cli/src/cog/cog.stac.job.ts index 5ad60c0dc..ed85520dd 100644 --- a/packages/cli/src/cog/cog.stac.job.ts +++ b/packages/cli/src/cog/cog.stac.job.ts @@ -73,9 +73,9 @@ export interface JobCreationContext { projection?: Epsg; /** - * Override Maximum Image Pixel Size + * Override Default aligned zoom level */ - maxImageSize?: number; + alignedLevel?: number; /** * Resampling method diff --git a/packages/cli/src/cog/constants.ts b/packages/cli/src/cog/constants.ts index 318eb414f..13d7449fb 100644 --- a/packages/cli/src/cog/constants.ts +++ b/packages/cli/src/cog/constants.ts @@ -1,5 +1,5 @@ -/** Default Maximum desired image size */ -export const MaxImagePixelWidth = 256000; +/** Default Aligned levels between resolution zoom level and cog zoom level */ +export const AlignedLevel = 6; /** When a tile has at least this much covering merge it up to parent */ export const CoveringFraction = 0.25; diff --git a/packages/cli/src/cog/cutline.ts b/packages/cli/src/cog/cutline.ts index 65ee8a089..57316edb2 100644 --- a/packages/cli/src/cog/cutline.ts +++ b/packages/cli/src/cog/cutline.ts @@ -10,7 +10,7 @@ import { union, } from '@linzjs/geojson'; import { FeatureCollection } from 'geojson'; -import { CoveringFraction, MaxImagePixelWidth } from './constants.js'; +import { AlignedLevel, CoveringFraction } from './constants.js'; import { CogJob, FeatureCollectionWithCrs, SourceMetadata } from './types.js'; /** Padding to always apply to image boundies */ @@ -155,7 +155,7 @@ export class Cutline { * Generate an optimized WebMercator tile cover for the supplied source images * @param sourceMetadata contains images bounds and projection info */ - optimizeCovering(sourceMetadata: SourceMetadata, maxImageSize: number = MaxImagePixelWidth): NamedBounds[] { + optimizeCovering(sourceMetadata: SourceMetadata, alignedLevel: number = AlignedLevel): NamedBounds[] { if (this.oneCogCovering) { const extent = this.tileMatrix.extent.toJson(); return [{ ...extent, name: '0-0-0' }]; @@ -164,15 +164,8 @@ export class Cutline { const { resZoom } = sourceMetadata; - // Look for the biggest tile size we are allowed to create. - let minZ = resZoom - 1; - while ( - minZ > 0 && - Projection.getImagePixelWidth(this.tileMatrix, { x: 0, y: 0, z: minZ }, resZoom) < maxImageSize - ) { - --minZ; - } - minZ = Math.max(1, minZ + 1); + // Fix the cog Minimum Zoom by the aligned level + const minZ = Math.max(0, resZoom - alignedLevel); let tiles: Tile[] = []; diff --git a/packages/cli/src/cog/job.factory.ts b/packages/cli/src/cog/job.factory.ts index 594be4be0..49b68ccb2 100644 --- a/packages/cli/src/cog/job.factory.ts +++ b/packages/cli/src/cog/job.factory.ts @@ -62,7 +62,7 @@ export const CogJobFactory = { ); const builder = new CogBuilder(ctx.tileMatrix, maxConcurrency, logger, ctx.override?.projection); - const metadata = await builder.build(tiffSource, cutline, ctx.override?.maxImageSize); + const metadata = await builder.build(tiffSource, cutline, ctx.override?.alignedLevel); if (cutline.clipPoly.length === 0) { // no cutline needed for this imagery set