Skip to content

Commit 32c501c

Browse files
authored
feat: move to query parameters for pipeline selection (#3136)
#### Motivation inline parameters in the URL were hard to parse and did not allow for future expansion of pipeline configuration, by moving to a query parameter the pipline can be configured easier and has opportunities for expansion later. #### Modification switches from a inline url to a query parameter for pipline selection #### Checklist _If not applicable, provide explanation of why._ - [ ] Tests updated - [ ] Docs updated - [ ] Issue linked in Title
1 parent f13b8fb commit 32c501c

File tree

16 files changed

+190
-137
lines changed

16 files changed

+190
-137
lines changed

packages/_infra/src/edge/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export class EdgeStack extends cdk.Stack {
103103
forwardedValues: {
104104
/** Forward all query strings but do not use them for caching */
105105
queryString: true,
106-
queryStringCacheKeys: ['config', 'exclude'].map(encodeURIComponent),
106+
queryStringCacheKeys: ['config', 'exclude', 'pipeline'].map(encodeURIComponent),
107107
},
108108
lambdaFunctionAssociations: [],
109109
},
@@ -118,6 +118,7 @@ export class EdgeStack extends cdk.Stack {
118118
'exclude',
119119
'tileMatrix',
120120
'style',
121+
'pipeline',
121122
// Deprecated single character query params for style and projection
122123
's',
123124
'p',

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

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import {
22
ConfigImagery,
33
ConfigProviderMemory,
44
ConfigTileSetRaster,
5+
DefaultColorRampOutput,
6+
DefaultTerrainRgbOutput,
57
ImageryDataType,
68
sha256base58,
79
TileSetType,
@@ -446,29 +448,11 @@ export async function initConfigFromUrls(
446448
const elevationTileSet: ConfigTileSetRaster = {
447449
id: 'ts_elevation',
448450
name: 'elevation',
449-
title: 'Basemaps',
451+
title: 'Elevation Basemap',
450452
category: 'Basemaps',
451453
type: TileSetType.Raster,
452454
layers: [],
453-
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-
},
465-
{
466-
title: 'Color ramp',
467-
name: 'color-ramp',
468-
pipeline: [{ type: 'color-ramp' }],
469-
output: { type: 'webp' },
470-
},
471-
],
455+
outputs: [DefaultTerrainRgbOutput, DefaultColorRampOutput],
472456
};
473457

474458
provider.put(aerialTileSet);
@@ -489,11 +473,13 @@ export async function initConfigFromUrls(
489473
elevationTileSet.layers.push(existingLayer);
490474
}
491475
existingLayer[cfg.projection] = cfg.id;
492-
provider.put(elevationTileSet);
493-
tileSets.push(elevationTileSet);
476+
if (!provider.objects.has(elevationTileSet.id)) {
477+
provider.put(elevationTileSet);
478+
tileSets.push(elevationTileSet);
479+
}
494480
}
495481
}
496-
// FIXME: this should return all the tile sets that were created
482+
// FIXME: tileSet should be removed now that we are returning all tilesets
497483
return { tileSet: aerialTileSet, tileSets, imagery: configs };
498484
}
499485

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ConfigTileSetRasterOutput } from './tile.set.js';
2+
3+
export const DefaultTerrainRgbOutput: ConfigTileSetRasterOutput = {
4+
title: 'TerrainRGB',
5+
name: 'terrain-rgb',
6+
pipeline: [{ type: 'terrain-rgb' }],
7+
output: {
8+
// terrain rgb cannot be resampled after it has been made
9+
lossless: true,
10+
// Zero encoded as a TerrainRGB
11+
background: { r: 1, g: 134, b: 160, alpha: 1 },
12+
resizeKernel: { in: 'nearest', out: 'nearest' },
13+
},
14+
} as const;
15+
16+
export const DefaultColorRampOutput: ConfigTileSetRasterOutput = {
17+
title: 'Color ramp',
18+
name: 'color-ramp',
19+
pipeline: [{ type: 'color-ramp' }],
20+
output: {
21+
background: { r: 1, g: 134, b: 160, alpha: 1 },
22+
},
23+
} as const;

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,20 @@ export interface ConfigTileSetRasterOutput {
9292
*/
9393
pipeline?: ConfigRasterPipeline[];
9494

95-
/** Raster output format */
96-
output: {
97-
/** Output file format to use */
98-
type: ImageFormat;
95+
/**
96+
* Raster output format
97+
* if none is provided it is assumed to be a RGBA output allowing all image formats
98+
*/
99+
output?: {
100+
/**
101+
* Allowed output file format to use
102+
*
103+
* Will default to all image formats
104+
* if "lossless" is set then lossless image formats
105+
*
106+
* @default ImageFormat[] - All Image formats
107+
*/
108+
type?: ImageFormat[];
99109
/**
100110
* should the output be lossless
101111
*
@@ -111,7 +121,7 @@ export interface ConfigTileSetRasterOutput {
111121
/**
112122
* When scaling tiles in the rendering process what kernel to use
113123
*
114-
* will fall back to {@link ConfigTileSetRaster.background} if not defined
124+
* will fall back to {@link ConfigTileSetRaster.background} if not defined
115125
*/
116126
resizeKernel?: { in: TileResizeKernel; out: TileResizeKernel };
117127
};

packages/config/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export {
2222
TileResizeKernel,
2323
TileSetType,
2424
} from './config/tile.set.js';
25+
export { DefaultColorRampOutput, DefaultTerrainRgbOutput } from './config/tile.set.output.js';
2526
export { ConfigVectorStyle, Layer, Sources, StyleJson } from './config/vector.style.js';
2627
export { ConfigBundled, ConfigProviderMemory } from './memory/memory.config.js';
2728
export { standardizeLayerName } from './name.convertor.js';

packages/config/src/memory/memory.config.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ConfigPrefix } from '../config/prefix.js';
1010
import { ConfigProvider } from '../config/provider.js';
1111
import { ConfigLayer, ConfigTileSet, ConfigTileSetRaster, TileSetType } from '../config/tile.set.js';
1212
import { ConfigVectorStyle } from '../config/vector.style.js';
13+
import { DefaultColorRampOutput, DefaultTerrainRgbOutput } from '../index.js';
1314
import { standardizeLayerName } from '../name.convertor.js';
1415

1516
interface DuplicatedImagery {
@@ -144,10 +145,15 @@ export class ConfigProviderMemory extends BasemapsConfigProvider {
144145
const layerByName = new Map<string, ConfigLayer>();
145146
// Set all layers as minZoom:32
146147
for (const l of layers) {
148+
// Ignore any tileset that has defined pipelines
149+
const tileSet = this.objects.get(this.TileSet.id(l.name)) as ConfigTileSetRaster;
150+
if (tileSet.outputs) continue;
151+
147152
const newLayer = { ...l, minZoom: 32 };
148153
delete newLayer.maxZoom; // max zoom not needed when minzoom is 32
149154
layerByName.set(newLayer.name, { ...layerByName.get(l.name), ...newLayer });
150155
}
156+
151157
const allTileset: ConfigTileSet = {
152158
type: TileSetType.Raster,
153159
id: 'ts_all',
@@ -180,14 +186,7 @@ export class ConfigProviderMemory extends BasemapsConfigProvider {
180186

181187
// FIXME: should we store output types here
182188
if (i.bands?.length === 1) {
183-
existing.outputs = [
184-
{
185-
title: 'Color ramp',
186-
name: 'color-ramp',
187-
pipeline: [{ type: 'color-ramp' }],
188-
output: { type: 'webp' },
189-
},
190-
];
189+
existing.outputs = [DefaultTerrainRgbOutput, DefaultColorRampOutput];
191190
}
192191
}
193192
// The latest imagery overwrite the earlier ones.
@@ -219,14 +218,7 @@ export class ConfigProviderMemory extends BasemapsConfigProvider {
219218

220219
// FIXME: should we store output types here
221220
if (i.bands?.length === 1) {
222-
ts.outputs = [
223-
{
224-
title: 'Color ramp',
225-
name: 'color-ramp',
226-
pipeline: [{ type: 'color-ramp' }],
227-
output: { type: 'webp' },
228-
},
229-
];
221+
ts.outputs = [DefaultTerrainRgbOutput, DefaultColorRampOutput];
230222
}
231223
return ts;
232224
}

packages/lambda-tiler/src/cli/render.preview.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ async function main(): Promise<void> {
3939
tileSet,
4040
location,
4141
z,
42-
output: { title: outputFormat, output: { type: outputFormat }, name: 'rgba' },
42+
output: { title: outputFormat, output: { type: [outputFormat] }, name: 'rgba' },
4343
});
4444
const previewFile = fsa.toUrl(`./z${z}_${location.lon}_${location.lat}.${outputFormat}`);
4545
await fsa.write(previewFile, Buffer.from(res.body, 'base64'));

packages/lambda-tiler/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ handler.router.get('/v1/tiles/:tileSet/:tileMatrix/style/:styleName.json', style
7373
handler.router.get('/v1/tiles/:tileSet/:tileMatrix/tile.json', tileJsonGet);
7474

7575
// Tiles
76-
handler.router.get('/v1/tiles/:tileSet/:tileMatrix/:z(^\\d+)/:x(^\\d+)/:y(^\\d+):tileType', tileXyzGet);
76+
handler.router.get('/v1/tiles/:tileSet/:tileMatrix/:z/:x/:y.:tileType', tileXyzGet);
7777

7878
// Preview
7979
handler.router.get('/v1/preview/:tileSet/:tileMatrix/:z/:lon/:lat', tilePreviewGet);

packages/lambda-tiler/src/routes/__tests__/xyz.test.ts

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { LogConfig } from '@basemaps/shared';
66
import { round } from '@basemaps/test/build/rounding.js';
77

88
import { FakeData } from '../../__tests__/config.data.js';
9-
import { Api, mockRequest } from '../../__tests__/xyz.util.js';
9+
import { Api, mockRequest, mockUrlRequest } from '../../__tests__/xyz.util.js';
1010
import { handler } from '../../index.js';
1111
import { ConfigLoader } from '../../util/config.loader.js';
1212
import { Etag } from '../../util/etag.js';
@@ -53,8 +53,8 @@ describe('/v1/tiles', () => {
5353

5454
const request = mockRequest('/v1/tiles/aerial/3857/0/0/0.webp', 'get', Api.header);
5555
const res = await handler.router.handle(request);
56-
console.log(res.statusDescription);
57-
assert.equal(res.status, 200);
56+
57+
assert.equal(res.status, 200, res.statusDescription);
5858
assert.equal(res.header('content-type'), 'image/webp');
5959
assert.equal(res.header('eTaG'), 'fakeEtag');
6060
// o(res.body).equals(rasterMockBuffer.toString('base64'));
@@ -68,13 +68,13 @@ describe('/v1/tiles', () => {
6868
it(`should 200 with empty ${fmt} if a tile is out of bounds`, async (t) => {
6969
t.mock.method(ConfigLoader, 'getDefaultConfig', () => Promise.resolve(config));
7070

71-
const res = await handler.router.handle(
72-
mockRequest(`/v1/tiles/aerial/global-mercator/0/0/0.${fmt}`, 'get', Api.header),
73-
);
74-
assert.equal(res.status, 200);
71+
const request = mockRequest(`/v1/tiles/aerial/global-mercator/0/0/0.${fmt}`, 'get', Api.header);
72+
const res = await handler.router.handle(request);
73+
assert.equal(res.status, 200, res.statusDescription);
7574
assert.equal(res.header('content-type'), `image/${fmt}`);
7675
assert.notEqual(res.header('etag'), undefined);
7776
assert.equal(res.header('cache-control'), 'public, max-age=604800, stale-while-revalidate=86400');
77+
assert.deepEqual(request.logContext['pipeline'], 'rgba');
7878
});
7979
});
8080

@@ -114,7 +114,7 @@ describe('/v1/tiles', () => {
114114
const req = mockRequest('/v1/tiles/🦄 🌈/global-mercator/0/0/0.png', 'get', Api.header);
115115
assert.equal(req.path, '/v1/tiles/%F0%9F%A6%84%20%F0%9F%8C%88/global-mercator/0/0/0.png');
116116
const res = await handler.router.handle(req);
117-
assert.equal(res.status, 200);
117+
assert.equal(res.status, 200, res.statusDescription);
118118
assert.equal(res.header('content-type'), 'image/png');
119119
assert.notEqual(res.header('etag'), undefined);
120120
assert.equal(res.header('cache-control'), 'public, max-age=604800, stale-while-revalidate=86400');
@@ -129,21 +129,52 @@ describe('/v1/tiles', () => {
129129
});
130130
});
131131

132+
it('should 404 if pipelines are defined but one is not requested', async (t) => {
133+
t.mock.method(ConfigLoader, 'getDefaultConfig', () => Promise.resolve(config));
134+
135+
const elevation = FakeData.tileSetRaster('elevation');
136+
137+
elevation.outputs = [{ title: 'Terrain RGB', name: 'terrain-rgb', output: { lossless: true } }];
138+
config.put(elevation);
139+
140+
const request = mockRequest('/v1/tiles/elevation/3857/11/2022/1283.webp', 'get', Api.header);
141+
142+
const res = await handler.router.handle(request);
143+
144+
assert.equal(res.status, 404, res.statusDescription);
145+
});
146+
132147
it('should generate a terrain-rgb 11/2022/1283 in webp', async (t) => {
133148
t.mock.method(ConfigLoader, 'getDefaultConfig', () => Promise.resolve(config));
134149

135150
const elevation = FakeData.tileSetRaster('elevation');
136151

137-
elevation.outputs = [{ title: 'Terrain RGB', name: 'terrain-rgb', output: { type: 'webp', lossless: true } }];
152+
elevation.outputs = [{ title: 'Terrain RGB', name: 'terrain-rgb', output: { lossless: true } }];
138153
config.put(elevation);
139154

140-
const request = mockRequest('/v1/tiles/elevation/3857/11/2022/1283-terrain-rgb.webp', 'get', Api.header);
155+
const request = mockUrlRequest('/v1/tiles/elevation/3857/11/2022/1283.webp', '?pipeline=terrain-rgb', Api.header);
141156

142157
const res = await handler.router.handle(request);
143158

144-
assert.equal(res.status, 200);
159+
assert.equal(res.status, 200, res.statusDescription);
145160
// Validate the session information has been set correctly
146161
assert.deepEqual(request.logContext['xyz'], { x: 2022, y: 1283, z: 11 });
162+
assert.deepEqual(request.logContext['pipeline'], 'terrain-rgb');
147163
assert.deepEqual(round(request.logContext['location']), { lat: -41.44272638, lon: 175.51757812 });
148164
});
165+
166+
it('should validate lossless if pipelines are defined but one is not requested', async (t) => {
167+
t.mock.method(ConfigLoader, 'getDefaultConfig', () => Promise.resolve(config));
168+
169+
const elevation = FakeData.tileSetRaster('elevation');
170+
171+
elevation.outputs = [{ title: 'Terrain RGB', name: 'terrain-rgb', output: { lossless: true } }];
172+
config.put(elevation);
173+
174+
// JPEG is not lossless
175+
const res = await handler.router.handle(
176+
mockUrlRequest('/v1/tiles/elevation/3857/11/2022/1283.jpeg', '?pipeline=terrain-rgb', Api.header),
177+
);
178+
assert.equal(res.status, 400, res.statusDescription);
179+
});
149180
});

0 commit comments

Comments
 (0)