Skip to content

Commit

Permalink
feat(lambda-tiler): allow relative sprites and glyphs (#2071)
Browse files Browse the repository at this point in the history
* feat(lambda-tiler): allow relative sprites and glyphs

* refactor: improve unit tests for xyz requests
  • Loading branch information
blacha authored Feb 1, 2022
1 parent c500fc4 commit a283157
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 44 deletions.
40 changes: 40 additions & 0 deletions packages/lambda-tiler/src/__test__/tile.style.json.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Env } from '@basemaps/shared';
import o from 'ospec';
import { convertRelativeUrl } from '../routes/tile.style.json.js';

o.spec('TileStyleJson', () => {
const host = 'https://tiles.test';
let originalHost: string | undefined;
o.beforeEach(() => {
originalHost = process.env[Env.PublicUrlBase];
process.env[Env.PublicUrlBase] = host;
});

o.afterEach(() => {
process.env[Env.PublicUrlBase] = originalHost;
});

o('should not convert empty urls', () => {
o(convertRelativeUrl()).equals('');
o(convertRelativeUrl('')).equals('');
o(convertRelativeUrl(undefined)).equals('');
});

o('should only convert relative urls', () => {
o(convertRelativeUrl('/foo')).equals('https://tiles.test/foo');
o(convertRelativeUrl('/bar/baz/')).equals('https://tiles.test/bar/baz/');
});

o('should only convert with api keys', () => {
o(convertRelativeUrl('/foo', 'abc')).equals('https://tiles.test/foo?api=abc');
o(convertRelativeUrl('/bar/baz/', 'abc')).equals('https://tiles.test/bar/baz/?api=abc');
});

o('should convert with other query params', () => {
o(convertRelativeUrl('/foo?bar=baz', 'abc')).equals('https://tiles.test/foo?bar=baz&api=abc');
});

o('should not convert full urls', () => {
o(convertRelativeUrl('https://foo.com/foo?bar=baz', 'abc')).equals('https://foo.com/foo?bar=baz');
});
});
62 changes: 32 additions & 30 deletions packages/lambda-tiler/src/__test__/xyz.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@ import { TileMatrixSets } from '@basemaps/geo';
import { Config, Env, LogConfig, VNodeParser } from '@basemaps/shared';
import { round } from '@basemaps/test/build/rounding.js';
import o from 'ospec';
import sinon from 'sinon';
import { handleRequest } from '../index.js';
import { TileEtag } from '../routes/tile.etag.js';
import { TileSets } from '../tile.set.cache.js';
import { FakeTileSet, mockRequest, Provider } from './xyz.util.js';
import sinon from 'sinon';
import { TileComposer } from '../tile.set.raster.js';
import { FakeTileSet, mockRequest, Provider } from './xyz.util.js';

const sandbox = sinon.createSandbox();

const TileSetNames = ['aerial', 'aerial@head', 'aerial@beta', '01E7PJFR9AMQFJ05X9G7FQ3XMW'];
/* eslint-disable @typescript-eslint/explicit-function-return-type */
o.spec('LambdaXyz', () => {
const host = 'https://tiles.test';
const origPublicUrlBase = process.env[Env.PublicUrlBase];

/** Generate mock ALBEvent */

let rasterMock = o.spy();
Expand All @@ -26,6 +30,8 @@ o.spec('LambdaXyz', () => {
const apiKeyHeader = { 'x-linz-api-key': 'd01f7w7rnhdzg0p7fyrc9v9ard1' };

o.beforeEach(() => {
process.env[Env.PublicUrlBase] = host;

LogConfig.disable();
// tileMock = o.spy(() => tileMockData) as any;
rasterMock = o.spy(() => {
Expand Down Expand Up @@ -54,6 +60,7 @@ o.spec('LambdaXyz', () => {
TileSets.cache.clear();
TileComposer.compose = origCompose;
TileEtag.generate = origTileEtag;
process.env[Env.PublicUrlBase] = origPublicUrlBase;
sandbox.restore();
});

Expand Down Expand Up @@ -134,12 +141,8 @@ o.spec('LambdaXyz', () => {
});

o.spec('WMTSCapabilities', () => {
const origPublicUrlBase = process.env[Env.PublicUrlBase];
o.after(() => {
process.env[Env.PublicUrlBase] = origPublicUrlBase;
});

o('should 304 if a xml is not modified', async () => {
delete process.env[Env.PublicUrlBase];
o.timeout(1000);
const key = 'NuirTK8fozzCJV1iG1FznmdHhKvk6WaWuDhhEA1d40c=';
const request = mockRequest('/v1/tiles/WMTSCapabilities.xml', 'get', {
Expand All @@ -148,6 +151,7 @@ o.spec('LambdaXyz', () => {
});

const res = await handleRequest(request);

if (res.status === 200) {
o(res.header('eTaG')).equals(key); // this line is useful for discovering the new etag
return;
Expand All @@ -160,9 +164,6 @@ o.spec('LambdaXyz', () => {
});

o('should serve WMTSCapabilities for tile_set', async () => {
console.log('\n\nTestStart');
process.env[Env.PublicUrlBase] = 'https://tiles.test';

const request = mockRequest('/v1/tiles/aerial@beta/WMTSCapabilities.xml', 'get', apiKeyHeader);

const res = await handleRequest(request);
Expand All @@ -185,13 +186,10 @@ o.spec('LambdaXyz', () => {
});

o.spec('tileJson', () => {
const origPublicUrlBase = process.env[Env.PublicUrlBase];
o.after(() => {
process.env[Env.PublicUrlBase] = origPublicUrlBase;
});

o('should 304 if a json is not modified', async () => {
const key = 'XecTdbcdjCyzB1MHOOQbrOkD2TTJ0ORh4JuXqhxXEE0=';
// delete process.env[Env.PublicUrlBase];

const key = 'BBfQpNXA3Q90jlFrLSOZhxbvfOh7OpN1OEE+BylpbHw=';
const request = mockRequest('/v1/tiles/tile.json', 'get', { 'if-none-match': key, ...apiKeyHeader });

const res = await handleRequest(request);
Expand All @@ -206,9 +204,19 @@ o.spec('LambdaXyz', () => {
o(request.logContext['cache']).deepEquals({ key, match: key, hit: true });
});

o('should serve tile json for tile_set', async () => {
process.env[Env.PublicUrlBase] = 'https://tiles.test';
o('should 200 if a invalid etag is given', async () => {
const key = 'ABCXecTdbcdjCyzB1MHOOQbrOkD2TTJ0ORh4JuXqhxXEE0=';
const request = mockRequest('/v1/tiles/tile.json', 'get', { 'if-none-match': key, ...apiKeyHeader });

const res = await handleRequest(request);
o(res.status).equals(200);
o(res.header('etag')).equals('BBfQpNXA3Q90jlFrLSOZhxbvfOh7OpN1OEE+BylpbHw=');
const out = JSON.parse(Buffer.from(res.body ?? '', 'base64').toString());
o(out.tiles[0].startsWith('https://tiles.test/v1/tiles/tile.json/undefined/{z}/{x}/{y}.pbf?api=')).equals(true);
o(request.logContext['cache']).deepEquals(undefined);
});

o('should serve tile json for tile_set', async () => {
const request = mockRequest('/v1/tiles/topographic/Google/tile.json', 'get', apiKeyHeader);

const res = await handleRequest(request);
Expand All @@ -228,14 +236,7 @@ o.spec('LambdaXyz', () => {
});

o.spec('styleJson', () => {
const origPublicUrlBase = process.env[Env.PublicUrlBase];
o.after(() => {
process.env[Env.PublicUrlBase] = origPublicUrlBase;
});

o('should not found style json', async () => {
process.env[Env.PublicUrlBase] = 'https://tiles.test';

const request = mockRequest('/v1/tiles/topographic/Google/style/topographic.json', 'get', apiKeyHeader);

sandbox.stub(Config.Style, 'get').resolves(null);
Expand All @@ -245,9 +246,6 @@ o.spec('LambdaXyz', () => {
});

o('should serve style json', async () => {
const host = 'https://tiles.test';
process.env[Env.PublicUrlBase] = host;

const request = mockRequest('/v1/tiles/topographic/Google/style/topographic.json', 'get', apiKeyHeader);

const fakeStyle: StyleJson = {
Expand Down Expand Up @@ -289,8 +287,8 @@ o.spec('LambdaXyz', () => {
minzoom: 0,
},
],
glyphs: 'glyphs',
sprite: 'sprite',
glyphs: '/glyphs',
sprite: '/sprite',
metadata: { id: 'test' },
};

Expand Down Expand Up @@ -320,6 +318,10 @@ o.spec('LambdaXyz', () => {
type: 'raster',
tiles: [`${host}/raster/{z}/{x}/{y}.webp?api=${apiKey}`],
};

fakeStyle.sprite = `${host}/sprite`;
fakeStyle.glyphs = `${host}/glyphs`;

o(JSON.parse(body)).deepEquals(fakeStyle);
});
});
Expand Down
33 changes: 19 additions & 14 deletions packages/lambda-tiler/src/routes/tile.style.json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,25 @@ import { Router } from '../router.js';
import { NotFound, NotModified } from './response.js';
import { TileEtag } from './tile.etag.js';

/**
* Convert relative URLS into a full hostname url
* @param url possible url to update
* @param apiKey ApiKey to append with ?api= if required
* @returns Updated Url or empty string if url is empty
*/
export function convertRelativeUrl(url?: string, apiKey?: string): string {
if (url == null) return '';
const host = Env.get(Env.PublicUrlBase) ?? '';
if (!url.startsWith('/')) return url; // Not relative ignore
const fullUrl = new URL(fsa.join(host, url));
if (apiKey) fullUrl.searchParams.set('api', apiKey);
return fullUrl.toString().replace(/%7B/g, '{').replace(/%7D/g, '}');
}

export async function styleJson(req: LambdaHttpRequest, fileName: string): Promise<LambdaHttpResponse> {
const apiKey = Router.apiKey(req);
if (apiKey == null) return new LambdaHttpResponse(400, 'Invalid API Key.');
const styleName = fileName.split('.json')[0];
const host = Env.get(Env.PublicUrlBase) ?? '';

// Get style Config from db
const dbId = Config.Style.id(styleName);
Expand All @@ -24,19 +38,10 @@ export async function styleJson(req: LambdaHttpRequest, fileName: string): Promi
const sources: Sources = {};
for (const [key, value] of Object.entries(style.sources)) {
if (value.type === 'vector') {
if (value.url.startsWith('/')) {
const url = new URL(fsa.join(host, value.url));
url.searchParams.set('api', apiKey);
value.url = url.toString().replace(/%7B/g, '{').replace(/%7D/g, '}');
}
value.url = convertRelativeUrl(value.url, apiKey);
} else if (value.type === 'raster' && Array.isArray(value.tiles)) {
for (let i = 0; i < value.tiles.length; i++) {
const tile = value.tiles[i];
if (tile.startsWith('/')) {
const url = new URL(fsa.join(host, tile));
url.searchParams.set('api', apiKey);
value.tiles[i] = url.toString().replace(/%7B/g, '{').replace(/%7D/g, '}');
}
value.tiles[i] = convertRelativeUrl(value.tiles[i], apiKey);
}
}
sources[key] = value;
Expand All @@ -51,8 +56,8 @@ export async function styleJson(req: LambdaHttpRequest, fileName: string): Promi
sources,
layers: style.layers,
metadata: style.metadata || {},
glyphs: style.glyphs || '',
sprite: style.sprite || '',
glyphs: convertRelativeUrl(style.glyphs),
sprite: convertRelativeUrl(style.sprite),
};

const json = JSON.stringify(styleJson);
Expand Down

0 comments on commit a283157

Please sign in to comment.