mirror of
https://github.com/protomaps/PMTiles.git
synced 2026-02-04 10:51:07 +00:00
99 lines
3.1 KiB
Python
99 lines
3.1 KiB
Python
import base64
|
|
from functools import lru_cache
|
|
import gzip
|
|
import json
|
|
import os
|
|
|
|
# Exists inside all lambda functions
|
|
import boto3
|
|
from botocore.exceptions import ClientError
|
|
|
|
# create_lambda_function.py will vendor the relevant file
|
|
from pmtiles.reader import Reader
|
|
from util import pmtiles_path, parse_tile_path
|
|
|
|
s3 = boto3.client("s3")
|
|
|
|
# Given a 512MB lambda function, use half of the memory for the cache,
|
|
# assuming the average root/leaf/tile size is 512 KB
|
|
@lru_cache(maxsize=500)
|
|
def get_object_bytes(key, offset, length):
|
|
end = offset + length - 1
|
|
return (
|
|
s3.get_object(
|
|
Bucket=os.environ["BUCKET"],
|
|
Key=key,
|
|
Range=f"bytes={offset}-{end}",
|
|
)
|
|
.get("Body")
|
|
.read()
|
|
)
|
|
|
|
|
|
# Assumes event is a API Gateway V2 or Lambda Function URL formatted dict
|
|
# and returns API Gateway V2 / Lambda Function dict responses
|
|
# Does not work with CloudFront events/Lambda@Edge; see README
|
|
def lambda_handler(event, context):
|
|
path = None
|
|
is_api_gateway = False
|
|
if event.get("pathParameters"):
|
|
is_api_gateway = True
|
|
# API Gateway (HTTP or REST)
|
|
if "proxy" in event["pathParameters"]:
|
|
path = "/" + event["pathParameters"]["proxy"]
|
|
else:
|
|
return {
|
|
"statusCode": 500,
|
|
"body": "Proxy integration missing tile_path parameter",
|
|
}
|
|
else:
|
|
# Lambda Function URL
|
|
path = event.get("rawPath")
|
|
|
|
name, tile = parse_tile_path(os.environ.get("TILE_PATH"), path)
|
|
|
|
if not tile:
|
|
return {"statusCode": 400, "body": "Invalid tile URL"}
|
|
|
|
def get_bytes(offset, length):
|
|
return get_object_bytes(
|
|
pmtiles_path(os.environ.get("PMTILES_PATH"), name), offset, length
|
|
)
|
|
|
|
reader = Reader(get_bytes)
|
|
try:
|
|
minzoom = int(reader.header().metadata["minzoom"])
|
|
maxzoom = int(reader.header().metadata["maxzoom"])
|
|
if tile.z < minzoom or tile.z > maxzoom:
|
|
return {"statusCode": 404, "body": "Tile not found"}
|
|
|
|
tile_data = reader.get(tile.z, tile.x, tile.y)
|
|
if not tile_data:
|
|
return {"statusCode": 204}
|
|
except ClientError as e:
|
|
error_code = e.response["Error"]["Code"]
|
|
if error_code == "AccessDenied":
|
|
return {"statusCode": 404, "body": "Archive not found"}
|
|
else:
|
|
raise e
|
|
|
|
headers = {"Content-Type": "application/protobuf"}
|
|
|
|
if reader.header().metadata.get("compression") == "gzip":
|
|
if is_api_gateway:
|
|
# API Gateway requires a compressed response to correctly return binary data
|
|
# instead of base64
|
|
headers["Content-Encoding"] = "gzip"
|
|
else:
|
|
# CloudFront requires decompressed responses from lambda
|
|
# in order to implement the Compressed CacheOptimized policy correctly
|
|
# as well as Brotli support
|
|
tile_data = gzip.decompress(tile_data)
|
|
|
|
return {
|
|
"statusCode": 200,
|
|
"body": base64.b64encode(tile_data),
|
|
"isBase64Encoded": True,
|
|
"headers": headers,
|
|
}
|