Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ deployment/*
docs/*

deployment/aws/**
!deployment/aws/lambda/handler.py
26 changes: 26 additions & 0 deletions build-lambda.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash

# This script compiles the service into a Python Lambda build artifact

set -ex

PROJ_DIR=$(git rev-parse --show-toplevel)
BUILD_DIR=${PROJ_DIR}/build
DOCKER_TAG=titiler_build
LAMBDA_PATH=${PROJ_DIR}/package.zip

cd ${PROJ_DIR}

# Clean out old build relics
rm -rf ${BUILD_DIR}
mkdir -p $(dirname ${BUILD_DIR})
rm -f ${LAMBDA_PATH}
mkdir -p $(dirname ${LAMBDA_PATH})

# Build Docker image and copy build assets out:
docker build --no-cache -f ${PROJ_DIR}/deployment/aws/lambda/Dockerfile -t ${DOCKER_TAG} .
docker run --rm -it -v ${BUILD_DIR}:/asset-output --entrypoint /bin/bash ${DOCKER_TAG} -c 'cp -R /asset/. /asset-output/.'

# Package up into .zip:
cd ${BUILD_DIR}
zip -r ${LAMBDA_PATH} .
101 changes: 87 additions & 14 deletions deployment/aws/lambda/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,28 +1,101 @@
ARG PYTHON_VERSION=3.12
FROM --platform=linux/amd64 public.ecr.aws/lambda/python:3.12

FROM --platform=linux/amd64 public.ecr.aws/lambda/python:${PYTHON_VERSION}
# Update pip:
RUN pip install pip -U

# Install system dependencies
RUN dnf install -y \
tar \
gzip \
bzip2 \
sqlite-devel \
libtiff-devel \
git \
cmake \
gcc-c++ \
curl-devel
RUN dnf clean all

# Build PROJ from source
WORKDIR /tmp
RUN curl -L https://download.osgeo.org/proj/proj-9.6.2.tar.gz | tar -xz
RUN mkdir proj-9.6.2/build
WORKDIR /tmp/proj-9.6.2/build
RUN cmake .. -DCMAKE_INSTALL_PREFIX=/usr
RUN cmake --build . --target install -j10

# Install system dependencies to compile (numexpr)
RUN dnf install -y gcc-c++ && dnf clean all
# Build GEOS from source
WORKDIR /tmp
RUN curl -L https://download.osgeo.org/geos/geos-3.13.1.tar.bz2 | tar -xj
RUN mkdir geos-3.13.1/build
WORKDIR /tmp/geos-3.13.1/build
RUN cmake \
-DCMAKE_INSTALL_PREFIX=/usr \
-DBUILD_DOCUMENTATION=OFF \
-DBUILD_TESTING=OFF \
..
RUN make -j10
RUN make install

RUN python -m pip install pip -U
RUN python -m pip install "titiler.application==0.22.4" "mangum>=0.10.0" -t /asset --no-binary pydantic
# Build the latest GDAL from source:
WORKDIR /tmp
RUN git clone https://github.com/OSGeo/gdal.git
RUN mkdir -p gdal/build
WORKDIR /tmp/gdal/build
RUN cmake \
-DCMAKE_INSTALL_PREFIX=/usr \
-DGDAL_USE_GEOS=ON \
-DGDAL_USE_CURL=ON \
-DGDAL_USE_LIBKML=OFF \
-DGDAL_USE_GRIB=OFF \
-DBUILD_APPS=OFF \
-DCMAKE_BUILD_TYPE=MinSizeRel \
..
RUN cmake --build . --target install -j10

# Build a rasterio wheel from source, which will make use of our GDAL
# build. The wheel collects up the necessary system libraries for
# eventual bundling into a Lambda artifact.
RUN pip install build auditwheel patchelf
RUN pip wheel rasterio --no-binary rasterio -w /tmp/rasterio-build
RUN auditwheel repair /tmp/rasterio-build/rasterio-*.whl -w /tmp/rasterio-wheel
RUN pip install /tmp/rasterio-wheel/rasterio-*.whl -t /asset

# Build TiTiler from source:
WORKDIR /tmp
COPY . /titiler
RUN pip install \
# From source to pull in our tile-clipping feature:
"/titiler/src/titiler/application" \
# mangum to host the app on AWS Lambda:
"mangum>=0.10.0" typing_extensions \
# Build pydantic from source:
--no-binary pydantic \
# Bundle the output into /asset:
-t /asset

# Reduce package size and remove useless files
RUN cd /asset && find . -type f -name '*.pyc' | while read f; do n=$(echo $f | sed 's/__pycache__\///' | sed 's/.cpython-[0-9]*//'); cp $f $n; done;
RUN cd /asset && find . -type d -a -name '__pycache__' -print0 | xargs -0 rm -rf
RUN cd /asset && find . -type f -a -name '*.py' -print0 | xargs -0 rm -f
RUN find /asset -type d -a -name 'tests' -print0 | xargs -0 rm -rf
RUN rm -rdf /asset/numpy/doc/ /asset/boto3* /asset/botocore* /asset/bin /asset/geos_license /asset/Misc
WORKDIR /asset
RUN find . -type f -name '*.pyc' | while read f; do n=$(echo $f | sed 's/__pycache__\///' | sed 's/.cpython-[0-9]*//'); cp $f $n; done;
RUN find . -type d -a -name '__pycache__' -print0 | xargs -0 rm -rf
RUN find . -type d -a -name 'tests' -print0 | xargs -0 rm -rf
RUN rm -rdf numpy/doc/ boto3* botocore* bin geos_license Misc

COPY lambda/handler.py /asset/handler.py
# Copy bits into /asset
# NOTE: I *also* found that a couple Lambda environment variables need
# to be set for all this to work:
# - GDAL_DATA : "/var/task/gdal"
# - PROJ_DATA : "/var/task/proj"
WORKDIR /asset
RUN mkdir -p /asset/proj
RUN cp -r /usr/share/proj/* /asset/proj/
RUN mkdir -p /asset/gdal
RUN cp -r /usr/share/gdal/* /asset/gdal/
COPY deployment/aws/lambda/handler.py handler.py

# Ref: https://github.com/developmentseed/titiler/discussions/1108#discussioncomment-13045681
RUN cp /usr/lib64/libexpat.so.1 /asset/
RUN cp /usr/lib64/libexpat.so.1 .

WORKDIR /asset
RUN python -c "from handler import handler; print('All Good')"

CMD ["echo", "hello world"]
12 changes: 7 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ classifiers = [
]
version="0.22.4"
dependencies = [
"titiler.core==0.22.4",
"titiler.xarray==0.22.4",
"titiler.extensions==0.22.4",
"titiler.mosaic==0.22.4",
"titiler.application==0.22.4",
# Configured for volume-mount path of Lambda Dockerfile. For local
# development, update accordingly for your local filesystem path:
"titiler.core @ file:///titiler/src/titiler/core",
"titiler.xarray @ file:///titiler/src/titiler/xarray",
"titiler.extensions @ file:///titiler/src/titiler/extensions",
"titiler.mosaic @ file:///titiler/src/titiler/mosaic",
"titiler.application @ file:///titiler/src/titiler/application",
]

[project.urls]
Expand Down
8 changes: 5 additions & 3 deletions src/titiler/application/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ classifiers = [
]
dynamic = ["version"]
dependencies = [
"titiler.core==0.22.4",
"titiler.extensions[cogeo,stac]==0.22.4",
"titiler.mosaic==0.22.4",
# Configured for volume-mount path of Lambda Dockerfile. For local
# development, update accordingly for your local filesystem path:
"titiler.core @ file:///titiler/src/titiler/core",
"titiler.extensions[cogeo,stac] @ file:///titiler/src/titiler/extensions",
"titiler.mosaic @ file:///titiler/src/titiler/mosaic",
"starlette-cramjam>=0.4,<0.5",
"pydantic-settings~=2.0",
]
Expand Down
17 changes: 17 additions & 0 deletions src/titiler/application/titiler/application/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import json
import logging
import os
from logging import config as log_config
from typing import Annotated, Literal, Optional

Expand Down Expand Up @@ -43,10 +44,26 @@
from titiler.mosaic.errors import MOSAIC_STATUS_CODES
from titiler.mosaic.factory import MosaicTilerFactory

LEVELS = {
"debug": logging.DEBUG,
"info": logging.INFO,
"warning": logging.WARNING,
"error": logging.ERROR,
"critical": logging.CRITICAL,
}
# Remove any AWS-injected logger handlers to fix Lambda logging to CloudWatch
# https://stackoverflow.com/a/45624044
base = logging.getLogger()
if base.handlers:
for handler in base.handlers:
base.removeHandler(handler)
logging.basicConfig(level=LEVELS.get(os.environ.get("LOGLEVEL", "info")))
logging.getLogger("botocore.credentials").disabled = True
logging.getLogger("botocore.utils").disabled = True
logging.getLogger("rio-tiler").setLevel(logging.ERROR)

logger = logging.getLogger(__name__)
logger.info("TiTiler")

api_settings = ApiSettings()

Expand Down
2 changes: 1 addition & 1 deletion src/titiler/core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ dependencies = [
"numpy",
"pydantic~=2.0",
"rasterio",
"rio-tiler>=7.7,<8.0",
"rio-tiler@https://github.com/SenteraLLC/rio-tiler/archive/refs/tags/7.8.1-0.tar.gz",
"morecantile",
"simplejson",
"typing_extensions>=4.6.1",
Expand Down
13 changes: 13 additions & 0 deletions src/titiler/core/titiler/core/factory.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""TiTiler Router factories."""

import abc
import json
from base64 import b64decode
from typing import (
Any,
Callable,
Expand Down Expand Up @@ -226,6 +228,11 @@ def add_route_dependencies(
route.dependencies.extend(dependencies) # type: ignore


def get_feature(aoi: str) -> Feature:
"""Base64 encoded GeoJSON Feature."""
return json.loads(b64decode(aoi))


@define(kw_only=True)
class TilerFactory(BaseFactory):
"""Tiler Factory.
Expand Down Expand Up @@ -839,6 +846,10 @@ def tile(
description="Default will be automatically defined if the output image needs a mask (png) or not (jpeg)."
),
] = None,
aoi: Annotated[
Union[str, None],
Query(description="Area of interest to crop the tile."),
] = None,
src_path=Depends(self.path_dependency),
reader_params=Depends(self.reader_dependency),
tile_params=Depends(self.tile_dependency),
Expand All @@ -850,6 +861,7 @@ def tile(
env=Depends(self.environment_dependency),
):
"""Create map tile from a dataset."""
feature = get_feature(aoi) if aoi else None
tms = self.supported_tms.get(tileMatrixSetId)
with rasterio.Env(**env):
with self.reader(
Expand All @@ -860,6 +872,7 @@ def tile(
y,
z,
tilesize=scale * 256,
aoi=feature,
**tile_params.as_dict(),
**layer_params.as_dict(),
**dataset_params.as_dict(),
Expand Down
4 changes: 3 additions & 1 deletion src/titiler/extensions/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ classifiers = [
]
dynamic = ["version"]
dependencies = [
"titiler.core==0.22.4"
# Configured for volume-mount path of Lambda Dockerfile. For local
# development, update accordingly for your local filesystem path:
"titiler.core @ file:///titiler/src/titiler/core",
]

[project.optional-dependencies]
Expand Down
4 changes: 3 additions & 1 deletion src/titiler/mosaic/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ classifiers = [
]
dynamic = ["version"]
dependencies = [
"titiler.core==0.22.4",
# Configured for volume-mount path of Lambda Dockerfile. For local
# development, update accordingly for your local filesystem path:
"titiler.core @ file:///titiler/src/titiler/core",
"cogeo-mosaic>=8.0,<9.0",
]

Expand Down
6 changes: 4 additions & 2 deletions src/titiler/xarray/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ classifiers = [
]
dynamic = ["version"]
dependencies = [
"titiler.core==0.22.4",
"rio-tiler>=7.6.1,<8.0",
# Configured for volume-mount path of Lambda Dockerfile. For local
# development, update accordingly for your local filesystem path:
"titiler.core @ file:///titiler/src/titiler/core",
"rio-tiler@https://github.com/SenteraLLC/rio-tiler/archive/refs/tags/7.8.1-0.tar.gz",
"xarray",
"rioxarray",
]
Expand Down
Loading