mirror of
https://github.com/protomaps/PMTiles.git
synced 2026-02-04 10:51:07 +00:00
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:
88
serverless/aws/package-lock.json
generated
88
serverless/aws/package-lock.json
generated
@@ -15,6 +15,7 @@
|
||||
"@types/aws-lambda": "^8.10.108",
|
||||
"@types/node": "^18.11.2",
|
||||
"esbuild": "^0.15.11",
|
||||
"esbuild-runner": "^2.2.2",
|
||||
"typescript": "^4.8.4"
|
||||
}
|
||||
},
|
||||
@@ -1351,6 +1352,12 @@
|
||||
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz",
|
||||
"integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA=="
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.15.11",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.11.tgz",
|
||||
@@ -1644,6 +1651,28 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-runner": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-runner/-/esbuild-runner-2.2.2.tgz",
|
||||
"integrity": "sha512-fRFVXcmYVmSmtYm2mL8RlUASt2TDkGh3uRcvHFOKNr/T58VrfVeKD9uT9nlgxk96u0LS0ehS/GY7Da/bXWKkhw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"source-map-support": "0.5.21",
|
||||
"tslib": "2.4.0"
|
||||
},
|
||||
"bin": {
|
||||
"esr": "bin/esr.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"esbuild": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-runner/node_modules/tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/esbuild-sunos-64": {
|
||||
"version": "0.15.11",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.11.tgz",
|
||||
@@ -1723,6 +1752,25 @@
|
||||
"url": "https://paypal.me/naturalintelligence"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-support": {
|
||||
"version": "0.5.21",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strnum": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
|
||||
@@ -2870,6 +2918,12 @@
|
||||
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz",
|
||||
"integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA=="
|
||||
},
|
||||
"buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true
|
||||
},
|
||||
"esbuild": {
|
||||
"version": "0.15.11",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.11.tgz",
|
||||
@@ -3012,6 +3066,24 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-runner": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-runner/-/esbuild-runner-2.2.2.tgz",
|
||||
"integrity": "sha512-fRFVXcmYVmSmtYm2mL8RlUASt2TDkGh3uRcvHFOKNr/T58VrfVeKD9uT9nlgxk96u0LS0ehS/GY7Da/bXWKkhw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"source-map-support": "0.5.21",
|
||||
"tslib": "2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
|
||||
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"esbuild-sunos-64": {
|
||||
"version": "0.15.11",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.11.tgz",
|
||||
@@ -3048,6 +3120,22 @@
|
||||
"strnum": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.5.21",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"strnum": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
"@types/aws-lambda": "^8.10.108",
|
||||
"@types/node": "^18.11.2",
|
||||
"esbuild": "^0.15.11",
|
||||
"esbuild-runner": "^2.2.2",
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"tsc": "tsc --noEmit --watch",
|
||||
"build": "esbuild src/index.ts --target=es2020 --outfile=dist/index.mjs --format=esm --bundle --platform=node --target=node18 --external:@aws-sdk/client-s3 --external:@aws-sdk/node-http-handler --banner:js=//$(git describe --always) && cd dist && zip lambda_function.zip index.mjs"
|
||||
"build": "esbuild src/index.ts --target=es2020 --outfile=dist/index.mjs --format=esm --bundle --platform=node --target=node18 --external:@aws-sdk/client-s3 --external:@aws-sdk/node-http-handler --banner:js=//$(git describe --always) && cd dist && zip lambda_function.zip index.mjs",
|
||||
"test": "node -r esbuild-runner/register ../shared/index.test.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.213.0",
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user