TileJSON support for Cloudflare and AWS [#169]

* Remove TILE_PATH configuration as this makes supporting non-tile paths difficult
* create shared/ dir in serverless for common code
* linting fixes
This commit is contained in:
Brandon Liu
2023-06-21 18:19:10 +08:00
parent a885f4098a
commit dc8eb73458
8 changed files with 239 additions and 270 deletions

View File

@@ -1,4 +1,3 @@
import { Readable } from "stream";
import {
Context,
APIGatewayProxyResult,
@@ -12,8 +11,8 @@ import {
Compression,
TileType,
} from "../../../js/index";
import { pmtiles_path, tile_path, tileJSON } from "../../shared/index";
import https from "https";
import zlib from "zlib";
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
@@ -43,47 +42,6 @@ async function nativeDecompress(
// Lambda needs to run with 512MB, empty function takes about 70
const CACHE = new ResolvedValueCache(undefined, undefined, nativeDecompress);
// duplicated code below
export const pmtiles_path = (name: string, setting?: string): string => {
if (setting) {
return setting.replaceAll("{name}", name);
}
return name + ".pmtiles";
};
const TILE =
/^\/(?<NAME>[0-9a-zA-Z\/!\-_\.\*\'\(\)]+)\/(?<Z>\d+)\/(?<X>\d+)\/(?<Y>\d+).(?<EXT>[a-z]+)$/;
export const tile_path = (
path: string,
setting?: string
): {
ok: boolean;
name: string;
tile: [number, number, number];
ext: string;
} => {
let pattern = TILE;
if (setting) {
// escape regex
setting = setting.replace(/[.*+?^$()|[\]\\]/g, "\\$&");
setting = setting.replace("{name}", "(?<NAME>[0-9a-zA-Z/!-_.*'()]+)");
setting = setting.replace("{z}", "(?<Z>\\d+)");
setting = setting.replace("{x}", "(?<X>\\d+)");
setting = setting.replace("{y}", "(?<Y>\\d+)");
setting = setting.replace("{ext}", "(?<EXT>[a-z]+)");
pattern = new RegExp(setting);
}
let match = path.match(pattern);
if (match) {
const g = match.groups!;
return { ok: true, name: g.NAME, tile: [+g.Z, +g.X, +g.Y], ext: g.EXT };
}
return { ok: false, name: "", tile: [0, 0, 0], ext: "" };
};
class S3Source implements Source {
archive_name: string;
@@ -138,11 +96,11 @@ const apiResp = (
// Does not work with CloudFront events/Lambda@Edge; see README
export const handlerRaw = async (
event: APIGatewayProxyEventV2,
context: Context,
_context: Context,
tilePostprocess?: (a: ArrayBuffer, t: TileType) => ArrayBuffer
): Promise<APIGatewayProxyResult> => {
var path;
var is_api_gateway;
let path;
let is_api_gateway;
if (event.pathParameters) {
is_api_gateway = true;
if (event.pathParameters.proxy) {
@@ -158,14 +116,13 @@ export const handlerRaw = async (
return apiResp(500, "Invalid event configuration");
}
var headers: Headers = {};
// TODO: metadata and TileJSON
const headers: Headers = {};
if (process.env.CORS) {
headers["Access-Control-Allow-Origin"] = process.env.CORS;
}
const { ok, name, tile, ext } = tile_path(path, process.env.TILE_PATH);
const { ok, name, tile, ext } = tile_path(path);
if (!ok) {
return apiResp(400, "Invalid tile URL", false, headers);
@@ -175,6 +132,28 @@ export const handlerRaw = async (
const p = new PMTiles(source, CACHE, nativeDecompress);
try {
const header = await p.getHeader();
if (!tile) {
if (!process.env.PUBLIC_HOSTNAME) {
return apiResp(
400,
"PUBLIC_HOSTNAME must be set for TileJSON",
false,
headers
);
}
headers["Content-Type"] = "application/json";
const t = tileJSON(
header,
await p.getMetadata(),
process.env.PUBLIC_HOSTNAME,
name
);
return apiResp(200, JSON.stringify(t), false, headers);
}
if (tile[0] < header.minZoom || tile[0] > header.maxZoom) {
return apiResp(404, "", false, headers);
}
@@ -259,5 +238,5 @@ export const handler = async (
event: APIGatewayProxyEventV2,
context: Context
): Promise<APIGatewayProxyResult> => {
return handlerRaw(event, context);
return await handlerRaw(event, context);
};