Skip to content

Commit

Permalink
feat(lambda-tiler): remove @basemaps/lambda and replace with `@linz…
Browse files Browse the repository at this point in the history
…js/lambda` (#1821)

* feat(lambda-tiler): remote `@basemaps/lambda` and replace with `@linzjs/lambda`

* refactor: remove unused reference

* refactor: use undefined to prevent a ! assertion

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
blacha and kodiakhq[bot] authored Aug 23, 2021
1 parent 9367e3f commit cb22b3d
Show file tree
Hide file tree
Showing 41 changed files with 183 additions and 1,194 deletions.
2 changes: 1 addition & 1 deletion packages/geo/src/stac/stac.attribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export type AttributionItem = StacItem<{
/**
* A Single File STAC compliant attribution json structure.
*/
export interface AttributionStac extends StacCatalog, GeoJSON.GeoJsonObject {
export interface AttributionStac extends StacCatalog, GeoJSON.GeoJsonObject, Record<string, unknown> {
type: 'FeatureCollection';
features: AttributionItem[];
collections: AttributionCollection[];
Expand Down
1 change: 1 addition & 0 deletions packages/lambda-tiler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@cogeotiff/source-aws": "^4.3.0",
"@cotar/core": "3.1.0",
"@linzjs/geojson": "^6.0.0",
"@linzjs/lambda": "^0.3.2",
"farmhash": "^3.2.1",
"path-to-regexp": "^6.1.0",
"pixelmatch": "^5.1.0",
Expand Down
6 changes: 3 additions & 3 deletions packages/lambda-tiler/src/__test__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { LambdaContext } from '@basemaps/lambda';
import { LogConfig } from '@basemaps/shared';
import { LambdaAlbRequest, LambdaHttpRequest } from '@linzjs/lambda';
import o from 'ospec';
import { handleRequest } from '../index';

o.spec('LambdaXyz index', () => {
function req(path: string, method = 'get'): LambdaContext {
return new LambdaContext(
function req(path: string, method = 'get'): LambdaHttpRequest {
return new LambdaAlbRequest(
{
requestContext: null as any,
httpMethod: method.toUpperCase(),
Expand Down
6 changes: 3 additions & 3 deletions packages/lambda-tiler/src/__test__/xyz.util.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ConfigProvider } from '@basemaps/config';
import { TileMatrixSet } from '@basemaps/geo';
import { LambdaContext } from '@basemaps/lambda';
import { LambdaHttpRequest, LambdaAlbRequest } from '@linzjs/lambda';
import { LogConfig } from '@basemaps/shared';
import { TileSetRaster } from '../tile.set.raster';

export function mockRequest(path: string, method = 'get', headers: Record<string, string> = {}): LambdaContext {
return new LambdaContext(
export function mockRequest(path: string, method = 'get', headers: Record<string, string> = {}): LambdaHttpRequest {
return new LambdaAlbRequest(
{
requestContext: null as any,
httpMethod: method.toUpperCase(),
Expand Down
2 changes: 1 addition & 1 deletion packages/lambda-tiler/src/api.key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const OneHourMs = 60 * 60 * 1000;
const OneDayMs = 24 * OneHourMs;
const MaxApiAgeMs = 91 * OneDayMs;

export function isValidApiKey(apiKey?: string): boolean {
export function isValidApiKey(apiKey?: string | null): boolean {
if (apiKey == null) return false;
if (!apiKey.startsWith('c') && !apiKey.startsWith('d')) return false;
const ulidId = apiKey.slice(1).toUpperCase();
Expand Down
4 changes: 2 additions & 2 deletions packages/lambda-tiler/src/cli/dump.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GoogleTms } from '@basemaps/geo';
import { LambdaContext } from '@basemaps/lambda';
import { LambdaAlbRequest } from '@linzjs/lambda';
import { LogConfig } from '@basemaps/shared';
import { ImageFormat } from '@basemaps/tiler';
import { promises as fs } from 'fs';
Expand Down Expand Up @@ -41,7 +41,7 @@ async function main(): Promise<void> {

logger.info({ ...xyz, projection: tileMatrix.projection.code, tileMatrix: tileMatrix.identifier }, 'RenderTile');

const ctx = new LambdaContext(
const ctx = new LambdaAlbRequest(
{
httpMethod: 'get',
path: `/v1/tiles/${tileSet.fullName}/${tileMatrix.identifier}/${xyz.z}/${xyz.x}/${xyz.y}.${ext}`,
Expand Down
8 changes: 4 additions & 4 deletions packages/lambda-tiler/src/cli/serve.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Epsg } from '@basemaps/geo';
import { HttpHeader, LambdaContext } from '@basemaps/lambda';
import { HttpHeader, LambdaAlbRequest, LambdaHttpRequest } from '@linzjs/lambda';
import { Env, LogConfig, LogType } from '@basemaps/shared';
import express from 'express';
import { PrettyTransform } from 'pretty-json-log';
Expand All @@ -18,7 +18,7 @@ const port = Env.getNumber('PORT', 5050);
if (process.stdout.isTTY) LogConfig.setOutputStream(PrettyTransform.stream());

async function handleRequest(
ctx: LambdaContext,
ctx: LambdaHttpRequest,
res: express.Response<any>,
startTime: number,
logger: LogType,
Expand Down Expand Up @@ -54,7 +54,7 @@ function useAws(): void {
const startTime = Date.now();
const requestId = ulid.ulid();
const logger = LogConfig.get().child({ id: requestId });
const ctx = new LambdaContext(
const ctx = new LambdaAlbRequest(
{
httpMethod: 'get',
path: req.path,
Expand Down Expand Up @@ -95,7 +95,7 @@ async function useLocal(): Promise<void> {
const requestId = ulid.ulid();
const logger = LogConfig.get().child({ id: requestId });
const { x, y, z, ext, imageryName, projection } = req.params;
const ctx = new LambdaContext(
const ctx = new LambdaAlbRequest(
{
httpMethod: 'get',
path: `/v1/tiles/${imageryName}/${projection}/${z}/${x}/${y}.${ext}`,
Expand Down
5 changes: 3 additions & 2 deletions packages/lambda-tiler/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { LambdaContext, LambdaFunction, LambdaHttpResponse, Router } from '@basemaps/lambda';
import { LambdaFunction, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
import { LogConfig } from '@basemaps/shared';
import { Ping, Version } from './routes/api';
import { Health } from './routes/health';
import { Tiles } from './routes/tile';
import { Router } from './router';

const app = new Router();

Expand All @@ -11,7 +12,7 @@ app.get('health', Health);
app.get('version', Version);
app.get('tiles', Tiles);

export async function handleRequest(req: LambdaContext): Promise<LambdaHttpResponse> {
export async function handleRequest(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
req.set('name', 'LambdaTiler');
return await app.handle(req);
}
Expand Down
56 changes: 56 additions & 0 deletions packages/lambda-tiler/src/router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Const } from '@basemaps/shared';
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';

export type ReqCallback = (req: LambdaHttpRequest) => Promise<LambdaHttpResponse>;

export interface ActionData {
version: string;
name: string;
rest: string[];
}

export class Router {
static action(req: LambdaHttpRequest): ActionData {
const path = req.path;
const [version, name, ...rest] = (path[0] === '/' ? path.slice(1) : path).split('/');
if (name == null) return { version: 'v1', name: version, rest: [] };
return { version, name, rest };
}

static apiKey(req: LambdaHttpRequest): string | undefined {
const apiKey = req.query.get(Const.ApiKey.QueryString) ?? req.header('X-LINZ-Api-Key');
if (apiKey != null && !Array.isArray(apiKey)) {
req.set(Const.ApiKey.QueryString, this.apiKey);
return apiKey;
}
return;
}

private handlers: Record<string, ReqCallback> = {};

async handle(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
// Allow cross origin requests
if (req.method === 'options') {
return new LambdaHttpResponse(200, 'Options', {
[HttpHeader.Cors]: '*',
'Access-Control-Allow-Credentials': 'false',
'Access-Control-Allow-Methods': 'OPTIONS,GET,PUT,POST,DELETE',
});
}

if (req.method !== 'GET') return new LambdaHttpResponse(405, 'Method not allowed');

const action = Router.action(req);
const handler = action.version === 'v1' ? this.handlers[action.name] : null;
if (handler == null) return new LambdaHttpResponse(404, 'Not Found');

const response = await handler(req);
response.header(HttpHeader.Cors, '*');
return response;
}

get(path: string, handler: ReqCallback): void {
if (this.handlers[path] != null) throw new Error(path + ' already registered');
this.handlers[path] = handler;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ConfigImagery, ConfigLayer, ConfigProvider } from '@basemaps/config';
import { EpsgCode, GoogleTms, NamedBounds, Nztm2000QuadTms, Nztm2000Tms, Stac, TileMatrixSets } from '@basemaps/geo';
import { HttpHeader } from '@basemaps/lambda';
import { HttpHeader } from '@linzjs/lambda';
import { Config } from '@basemaps/shared';
import { mockFileOperator } from '@basemaps/shared/build/file/__test__/file.operator.test.helper';
import { round } from '@basemaps/test/build/rounding';
Expand Down
4 changes: 2 additions & 2 deletions packages/lambda-tiler/src/routes/__test__/health.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import o from 'ospec';
import sinon from 'sinon';
import * as Tile from '../tile';
import { getExpectedTileName, Health, TestTiles } from '../health';
import { LambdaContext, LambdaHttpResponse } from '@basemaps/lambda';
import { LambdaAlbRequest, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
import { LogConfig } from '@basemaps/shared';

const ctx: LambdaContext = new LambdaContext(
const ctx: LambdaHttpRequest = new LambdaAlbRequest(
{
requestContext: null as any,
httpMethod: 'get',
Expand Down
2 changes: 1 addition & 1 deletion packages/lambda-tiler/src/routes/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LambdaHttpResponse, HttpHeader } from '@basemaps/lambda';
import { LambdaHttpResponse, HttpHeader } from '@linzjs/lambda';

const OkResponse = new LambdaHttpResponse(200, 'ok');
OkResponse.header(HttpHeader.CacheControl, 'no-store');
Expand Down
8 changes: 5 additions & 3 deletions packages/lambda-tiler/src/routes/attribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
StacExtent,
TileMatrixSet,
} from '@basemaps/geo';
import { HttpHeader, LambdaContext, LambdaHttpResponse } from '@basemaps/lambda';
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
import {
Config,
extractYearRangeFromName,
Expand All @@ -25,6 +25,7 @@ import { BBox, MultiPolygon, multiPolygonToWgs84, Pair, union, Wgs84 } from '@li
import { createHash } from 'crypto';
import { TileSets } from '../tile.set.cache';
import { TileSetRaster } from '../tile.set.raster';
import { Router } from '../router';

/** Amount to pad imagery bounds to avoid fragmenting polygons */
const SmoothPadding = 1 + 1e-10; // about 1/100th of a millimeter at equator
Expand Down Expand Up @@ -206,8 +207,9 @@ async function tileSetAttribution(tileSet: TileSetRaster): Promise<AttributionSt
/**
* Create a LambdaHttpResponse for a attribution request
*/
export async function attribution(req: LambdaContext): Promise<LambdaHttpResponse> {
const data = tileAttributionFromPath(req.action.rest);
export async function attribution(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
const action = Router.action(req);
const data = tileAttributionFromPath(action.rest);
if (data == null) return NotFound;
setNameAndProjection(req, data);

Expand Down
6 changes: 3 additions & 3 deletions packages/lambda-tiler/src/routes/health.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as path from 'path';
import Sharp from 'sharp';
import PixelMatch = require('pixelmatch');
import { Epsg, Tile } from '@basemaps/geo';
import { LambdaHttpResponse, LambdaContext, HttpHeader } from '@basemaps/lambda';
import { LambdaHttpResponse, LambdaHttpRequest, HttpHeader, LambdaAlbRequest } from '@linzjs/lambda';
import { ImageFormat } from '@basemaps/tiler';
import { tile } from './tile';

Expand Down Expand Up @@ -51,7 +51,7 @@ async function getTestBuffer(testTile: TestTile): Promise<Buffer> {
*
* @throws LambdaHttpResponse for failure health test
*/
export async function Health(req: LambdaContext): Promise<LambdaHttpResponse> {
export async function Health(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
for (const test of TestTiles) {
const projection = test.projection;
const testTile = test.testTile;
Expand All @@ -63,7 +63,7 @@ export async function Health(req: LambdaContext): Promise<LambdaHttpResponse> {
/${testTile.y}
.${format}`;

const ctx: LambdaContext = new LambdaContext(
const ctx: LambdaHttpRequest = new LambdaAlbRequest(
{
requestContext: null as any,
httpMethod: 'get',
Expand Down
4 changes: 2 additions & 2 deletions packages/lambda-tiler/src/routes/tile.etag.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Composition } from '@basemaps/tiler';
import { createHash } from 'crypto';
import { TileDataXyz } from '@basemaps/shared';
import { HttpHeader, LambdaContext } from '@basemaps/lambda';
import { HttpHeader, LambdaHttpRequest } from '@linzjs/lambda';

export const TileEtag = {
// To force a full cache invalidation change this number
Expand All @@ -22,7 +22,7 @@ export const TileEtag = {
return TileEtag.key({ xyz, layers, RenderId: TileEtag.RenderId });
},

isNotModified(req: LambdaContext, cacheKey: string): boolean {
isNotModified(req: LambdaHttpRequest, cacheKey: string): boolean {
// If the user has supplied a IfNoneMatch Header and it contains the full sha256 sum for our
// etag this tile has not been modified.
const ifNoneMatch = req.header(HttpHeader.IfNoneMatch);
Expand Down
40 changes: 24 additions & 16 deletions packages/lambda-tiler/src/routes/tile.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Sources, StyleJson } from '@basemaps/config';
import { TileMatrixSet } from '@basemaps/geo';
import { HttpHeader, LambdaContext, LambdaHttpResponse, ValidateTilePath } from '@basemaps/lambda';
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
import { Config, Env, setNameAndProjection, TileSetName, tileWmtsFromPath, tileXyzFromPath } from '@basemaps/shared';
import { TileMakerSharp } from '@basemaps/tiler-sharp';
import { createHash } from 'crypto';
Expand All @@ -10,14 +10,17 @@ import { TileSetRaster } from '../tile.set.raster';
import { WmtsCapabilities } from '../wmts.capability';
import { attribution } from './attribution';
import { TileEtag } from './tile.etag';
import { Router } from '../router';
import { ValidateTilePath } from '../validate';

export const TileComposer = new TileMakerSharp(256);

export const NotFound = new LambdaHttpResponse(404, 'Not Found');
export const NotModified = new LambdaHttpResponse(304, 'Not modified');

export async function tile(req: LambdaContext): Promise<LambdaHttpResponse> {
const xyzData = tileXyzFromPath(req.action.rest);
export async function tile(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
const action = Router.action(req);
const xyzData = tileXyzFromPath(action.rest);
if (xyzData == null) return NotFound;
ValidateTilePath.validate(req, xyzData);

Expand All @@ -40,8 +43,9 @@ async function wmtsLoadTileSets(name: string, tileMatrix: TileMatrixSet | null):
return (await TileSets.getAll(name, tileMatrix)).filter((f) => f.type === 'raster') as TileSetRaster[];
}

export async function wmts(req: LambdaContext): Promise<LambdaHttpResponse> {
const wmtsData = tileWmtsFromPath(req.action.rest);
export async function wmts(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
const action = Router.action(req);
const wmtsData = tileWmtsFromPath(action.rest);
if (wmtsData == null) return NotFound;
setNameAndProjection(req, wmtsData);
const host = Env.get(Env.PublicUrlBase) ?? '';
Expand All @@ -55,7 +59,8 @@ export async function wmts(req: LambdaContext): Promise<LambdaHttpResponse> {
const provider = await Config.Provider.get(providerId);
if (provider == null) return NotFound;

const xml = WmtsCapabilities.toXml(host, provider, tileSets, req.apiKey);
const apiKey = Router.apiKey(req);
const xml = WmtsCapabilities.toXml(host, provider, tileSets, apiKey);
if (xml == null) return NotFound;

const data = Buffer.from(xml);
Expand All @@ -79,10 +84,11 @@ export interface TileJson {
tilejson: string;
}

export async function tileJson(req: LambdaContext): Promise<LambdaHttpResponse> {
const { version, rest, name } = req.action;
export async function tileJson(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
const { version, rest, name } = Router.action(req);
const apiKey = Router.apiKey(req);
const host = Env.get(Env.PublicUrlBase) ?? '';
const tileUrl = `${host}/${version}/${name}/${rest[0]}/${rest[1]}/{z}/{x}/{y}.pbf?api=${req.apiKey}`;
const tileUrl = `${host}/${version}/${name}/${rest[0]}/${rest[1]}/{z}/{x}/{y}.pbf?api=${apiKey}`;

const tileJson: TileJson = {
tiles: [tileUrl],
Expand All @@ -108,8 +114,9 @@ export async function tileJson(req: LambdaContext): Promise<LambdaHttpResponse>
return response;
}

export async function styleJson(req: LambdaContext, fileName: string): Promise<LambdaHttpResponse> {
const { version, rest, name } = req.action;
export async function styleJson(req: LambdaHttpRequest, fileName: string): Promise<LambdaHttpResponse> {
const { version, rest, name } = Router.action(req);
const apiKey = Router.apiKey(req);
const styleName = fileName.split('.json')[0];
const host = Env.get(Env.PublicUrlBase) ?? '';

Expand All @@ -121,8 +128,8 @@ export async function styleJson(req: LambdaContext, fileName: string): Promise<L
// Prepare sources and add linz source
const style = styleConfig.style;
const sources: Sources = {};
const tileJsonUrl = `${host}/${version}/${name}/${rest[0]}/${rest[1]}/tile.json?api=${req.apiKey}`;
const rasterUrl = `${host}/${version}/${name}/aerial/${rest[1]}/{z}/{x}/{y}.webp?api=${req.apiKey}`;
const tileJsonUrl = `${host}/${version}/${name}/${rest[0]}/${rest[1]}/tile.json?api=${apiKey}`;
const rasterUrl = `${host}/${version}/${name}/aerial/${rest[1]}/{z}/{x}/{y}.webp?api=${apiKey}`;
for (const [key, value] of Object.entries(style.sources)) {
if (value.type === 'vector' && value.url === '') {
value.url = tileJsonUrl;
Expand Down Expand Up @@ -161,10 +168,11 @@ export async function styleJson(req: LambdaContext, fileName: string): Promise<L
return response;
}

export async function Tiles(req: LambdaContext): Promise<LambdaHttpResponse> {
const { rest } = req.action;
export async function Tiles(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
const { rest } = Router.action(req);
if (rest.length < 1) return NotFound;
if (!isValidApiKey(req.apiKey)) return new LambdaHttpResponse(400, 'Invalid API Key');
const apiKey = Router.apiKey(req);
if (!isValidApiKey(apiKey)) return new LambdaHttpResponse(400, 'Invalid API Key');

const fileName = rest[rest.length - 1].toLowerCase();
if (fileName === 'attribution.json') return attribution(req);
Expand Down
Loading

0 comments on commit cb22b3d

Please sign in to comment.