Skip to content

Commit f13b8fb

Browse files
authored
feat(tiler-sharp): allow outputs to customise how output is compressed (#3126)
#### Motivation TerrainRGB needs to be lossless compressed, this adds the abilities for pipelines to customise how they create the output eg resizeKernels and lossless parameters #### Modification allows outputs to define resize kernels #### Checklist _If not applicable, provide explanation of why._ - [ ] Tests updated - [ ] Docs updated - [ ] Issue linked in Title
1 parent 87f152b commit f13b8fb

File tree

6 files changed

+48
-18
lines changed

6 files changed

+48
-18
lines changed

packages/config-loader/src/json/tiff.config.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ export async function initConfigFromUrls(
428428
targets: URL[],
429429
concurrency = 25,
430430
log?: LogType,
431-
): Promise<{ tileSet: ConfigTileSetRaster; imagery: ConfigImageryTiff[] }> {
431+
): Promise<{ tileSet: ConfigTileSetRaster; tileSets: ConfigTileSetRaster[]; imagery: ConfigImageryTiff[] }> {
432432
const q = pLimit(concurrency);
433433

434434
const imageryConfig: Promise<ConfigImageryTiff>[] = [];
@@ -451,23 +451,29 @@ export async function initConfigFromUrls(
451451
type: TileSetType.Raster,
452452
layers: [],
453453
outputs: [
454+
{
455+
title: 'TerrainRGB',
456+
name: 'terrain-rgb',
457+
pipeline: [{ type: 'terrain-rgb' }],
458+
output: {
459+
type: 'webp',
460+
lossless: true,
461+
background: { r: 1, g: 134, b: 160, alpha: 1 },
462+
resizeKernel: { in: 'nearest', out: 'nearest' },
463+
},
464+
},
454465
{
455466
title: 'Color ramp',
456467
name: 'color-ramp',
457468
pipeline: [{ type: 'color-ramp' }],
458469
output: { type: 'webp' },
459470
},
460-
{
461-
title: 'TerrainRGB',
462-
name: 'terrain-rgb',
463-
pipeline: [{ type: 'terrain-rgb' }],
464-
output: { type: 'webp', lossless: true },
465-
},
466471
],
467472
};
468473

469474
provider.put(aerialTileSet);
470475
const configs = await Promise.all(imageryConfig);
476+
const tileSets = [aerialTileSet];
471477
for (const cfg of configs) {
472478
if (isRgbOrRgba(cfg)) {
473479
let existingLayer = aerialTileSet.layers.find((l) => l.title === cfg.title);
@@ -484,10 +490,11 @@ export async function initConfigFromUrls(
484490
}
485491
existingLayer[cfg.projection] = cfg.id;
486492
provider.put(elevationTileSet);
493+
tileSets.push(elevationTileSet);
487494
}
488495
}
489496
// FIXME: this should return all the tile sets that were created
490-
return { tileSet: aerialTileSet, imagery: configs };
497+
return { tileSet: aerialTileSet, tileSets, imagery: configs };
491498
}
492499

493500
/**

packages/config/src/config/tile.set.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,13 @@ export interface ConfigTileSetRasterOutput {
107107
* {@link ConfigTileSetRaster.background} if not defined
108108
*/
109109
background?: { r: number; g: number; b: number; alpha: number };
110+
111+
/**
112+
* When scaling tiles in the rendering process what kernel to use
113+
*
114+
* will fall back to {@link ConfigTileSetRaster.background} if not defined
115+
*/
116+
resizeKernel?: { in: TileResizeKernel; out: TileResizeKernel };
110117
};
111118
}
112119

packages/lambda-tiler/src/routes/preview.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,14 @@ export async function renderPreview(req: LambdaHttpRequest, ctx: PreviewRenderCo
147147
compositions.push(...result);
148148
}
149149

150+
const tileOutput = ctx.output;
150151
const tileContext = {
151152
layers: compositions,
152-
format: ctx.output.output.type,
153-
lossless: ctx.output.output.lossless,
154-
background: ctx.output.output.background ?? ctx.tileSet.background ?? DefaultBackground,
155-
resizeKernel: DefaultResizeKernel,
153+
pipeline: tileOutput.pipeline,
154+
format: tileOutput.output.type,
155+
lossless: tileOutput.output.lossless,
156+
background: tileOutput.output.background ?? ctx.tileSet.background ?? DefaultBackground,
157+
resizeKernel: tileOutput.output.resizeKernel ?? ctx.tileSet.resizeKernel ?? DefaultResizeKernel,
156158
};
157159

158160
// Load all the tiff tiles and resize/them into the correct locations

packages/lambda-tiler/src/routes/tile.xyz.raster.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export const TileXyzRaster = {
139139
format: tileOutput.output.type,
140140
lossless: tileOutput.output.lossless,
141141
background: tileOutput.output.background ?? tileSet.background ?? DefaultBackground,
142-
resizeKernel: tileSet.resizeKernel ?? DefaultResizeKernel,
142+
resizeKernel: tileOutput.output.resizeKernel ?? tileSet.resizeKernel ?? DefaultResizeKernel,
143143
metrics: req.timer,
144144
});
145145

packages/server/src/config.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,13 @@ export async function loadConfig(opts: ServerOptions, logger: LogType): Promise<
2828
if ('paths' in opts) {
2929
const mem = new ConfigProviderMemory();
3030
const ret = await initConfigFromUrls(mem, opts.paths);
31-
logger.info({ tileSet: ret.tileSet.name, layers: ret.tileSet.layers.length }, 'TileSet:Loaded');
31+
for (const ts of ret.tileSets) {
32+
logger.info(
33+
{ tileSet: ts.name, layers: ts.layers.length, outputs: ts.outputs?.map((f) => f.name) },
34+
'TileSet:Loaded',
35+
);
36+
}
37+
3238
for (const im of ret.imagery) {
3339
logger.info(
3440
{ imagery: im.uri, title: im.title, tileMatrix: im.tileMatrix, files: im.files.length },

packages/tiler-sharp/src/index.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,21 +66,29 @@ export class TileMakerSharp implements TileMaker {
6666
toImage(format: ImageFormat, pipeline: Sharp.Sharp, lossless?: boolean): Promise<Buffer> {
6767
switch (format) {
6868
case 'jpeg':
69+
if (lossless) throw new Error('lossless jpeg is not defined');
6970
return pipeline.jpeg().toBuffer();
7071
case 'png':
7172
return pipeline.png().toBuffer();
7273
case 'webp':
73-
return pipeline.webp({ lossless }).toBuffer();
74+
if (lossless) return pipeline.webp({ lossless: true, quality: 100, alphaQuality: 100 }).toBuffer();
75+
return pipeline.webp().toBuffer();
7476
case 'avif':
77+
if (lossless) throw new Error('lossless avif is not defined');
7578
return pipeline.avif().toBuffer();
7679
default:
7780
throw new Error(`Invalid image format "${format}"`);
7881
}
7982
}
8083

81-
private async getImageBuffer(layers: SharpOverlay[], format: ImageFormat, background: Sharp.RGBA): Promise<Buffer> {
84+
private async getImageBuffer(
85+
layers: SharpOverlay[],
86+
format: ImageFormat,
87+
background: Sharp.RGBA,
88+
lossless?: boolean,
89+
): Promise<Buffer> {
8290
if (layers.length === 0) return this.getEmptyImage(format, background);
83-
return this.toImage(format, this.createImage(background).composite(layers));
91+
return this.toImage(format, this.createImage(background).composite(layers), lossless);
8492
}
8593

8694
public async compose(ctx: TileMakerContext): Promise<{ buffer: Buffer; metrics: Metrics; layers: number }> {
@@ -104,7 +112,7 @@ export class TileMakerSharp implements TileMaker {
104112
metrics.end('compose:overlay');
105113

106114
metrics.start('compose:compress');
107-
const buffer = await this.getImageBuffer(overlays, ctx.format, ctx.background);
115+
const buffer = await this.getImageBuffer(overlays, ctx.format, ctx.background, ctx.lossless);
108116
metrics.end('compose:compress');
109117

110118
return { buffer, metrics, layers: overlays.length };

0 commit comments

Comments
 (0)