TiTiler

repository·main·Indexed 22 days ago

https://github.com/developmentseed/titiler

A modern dynamic tile server built on FastAPI and Rasterio/GDAL. TiTiler serves tiles from Cloud Optimized GeoTIFFs (COG), STAC collections, Zarr/NetCDF datasets, and MosaicJSON files. It is distributed as a set of namespace packages including titiler.core, titiler.xarray, titiler.extensions, titiler.mosaic, and titiler.application.

Tokens
91.7K
Snippets
287
Records
347
Agent score
77%

What's inside TiTiler

  1. titiler.core package structure

    main

    The titiler.core package is organized into several functional modules:

    • algorithm/: Contains algorithms for data processing, including base.py (ABC Base Class for custom algorithms), dem.py (elevation data), and index.py (band index algorithms).
    • models/: Pydantic models for responses, including mapbox.py (Mapbox TileJSON) and OGC.py (Open GeoSpatial Consortium models like TileMatrixSets).
    • resources/: Enumerations (e.g., enums.py) and custom Starlette responses (responses.py).
    • templates/: HTML/XML templates for a Leaflet-based map viewer (map.html) and OGC WMTS documents (wmts.xml).
    • factory.py: Contains the TilerFactory used to generate dynamic tiler endpoints.
    • routing.py: Custom APIRoute class.
    • dependencies.py: FastAPI dependencies.
    • errors.py: Error handler factory.
    • middleware.py: Starlette middlewares.
    • telemetry.py: OpenTelemetry tracing functions.
    • utils.py: Utility functions.
  2. Overview of TiTiler packages

    main

    TiTiler is split into several specialized namespace packages. Choose the package that matches your data source requirements:

    PackageDescription
    titiler.coreLibraries for creating dynamic tilers for Cloud Optimized GeoTIFF (COG) and SpatioTemporal Asset Catalog (STAC).
    titiler.xarrayLibraries for creating dynamic tilers for multi-dimensional Zarr/NetCDF datasets.
    titiler.extensionsExtensions for Tiler Factories.
    titiler.mosaicLibraries for creating dynamic tilers for MosaicJSON (requires cogeo-mosaic).
    titiler.applicationA demo package containing a FastAPI application with full support for COG, Zarr, STAC, and MosaicJSON.
  3. Migrate mosaic /point endpoint consumers

    main

    The response model for the mosaic /point endpoint has been completely restructured in TiTiler 1.0. Instead of a list of tuples, it now returns an assets list containing AssetPoint objects. Each asset object contains name, values, band_names, and an optional band_descriptions field.

    # Before (0.26)
    response = {
        "coordinates": [-122.5, 37.5],
        "values": [
            ("asset1", [100.0, 200.0], ["B1", "B2"]),
            ("asset2", [150.0, 200.0], ["B1", "B2"])
        ]
    }
    
    # Now (1.0)
    response = {
        "coordinates": [-122.5, 37.5],
        "assets": [
            {
                "name": "asset1",
                "values": [100.0, 200.0],
                "band_names": ["B1", "B2"],
                "band_descriptions": None
            },
            {
                "name": "asset2",
                "values": [150.0, 200.0],
                "band_names": ["B1", "B2"],
                "band_descriptions": None
            }
        ]
    }
  4. How TiTiler works on AWS Lambda

    main

    TiTiler is built on FastAPI, which expects standard HTTP requests. Since AWS Lambda and API Gateway provide data via event and context JSON objects rather than HTTP, you must wrap the FastAPI application using the mangum module to translate API Gateway events into HTTP requests.

    To create a Lambda handler, import Mangum and your TiTiler app, then wrap the app instance.

    from mangum import Mangum
    from titiler.main import app
    
    handler = Mangum(app, enable_lifespan=False)
  5. Understand TileMatrixSets and Projections in TiTiler

    main

    A TileMatrixSet (TMS) defines the grid (coordinate system and zoom levels) used for slippy map tiles. While the Web Mercator grid is the standard for most web maps, TiTiler allows you to serve tiles in various other projections to avoid distortion in specific geographic regions (e.g., using UPSArcticWGS84Quad for polar regions).

    TiTiler uses rio-tiler and morecantile to handle the low-level TileMatrixSet logic. If the default sets are insufficient, you can implement custom TMS support.

  6. Implement a Custom Cache Decorator for Non-Async Methods

    main

    Since aiocache.cached does not natively support non-async methods, you can implement a custom cached class that inherits from aiocache.cached. This implementation uses starlette.concurrency.run_in_threadpool to safely execute synchronous functions in a thread pool, ensuring they don't block the event loop.

    Key Features of this implementation:

    • Async/Sync Support: Detects if the function is a coroutine and handles it accordingly.
    • Cache Hit Headers: Automatically adds an X-Cache: HIT header to starlette.responses.Response objects when a cache hit occurs.
    • Configurable Write: Supports cache_read, cache_write, and aiocache_wait_for_write (to allow fire-and-forget cache updates).
    import asyncio
    import aiocache
    from starlette.concurrency import run_in_threadpool
    from starlette.responses import Response
    from fastapi.dependencies.utils import is_coroutine_callable
    
    class cached(aiocache.cached):
        async def get_from_cache(self, key):
            try:
                value = await self.cache.get(key)
                if isinstance(value, Response):
                    value.headers["X-Cache"] = "HIT"
                return value
            except Exception:
                aiocache.logger.exception("Couldn't retrieve %s, unexpected error", key)
    
        async def decorator(self, f, *args, cache_read=True, cache_write=True, aiocache_wait_for_write=True, **kwargs):
            key = self.get_cache_key(f, args, kwargs)
    
            if cache_read:
                value = await self.get_from_cache(key)
                if value is not None:
                    return value
    
            if is_coroutine_callable(f):
                result = await f(*args, **kwargs)
            else:
                result = await run_in_threadpool(f, *args, **kwargs)
    
            if cache_write:
                if aiocache_wait_for_write:
                    await self.set_in_cache(key, result)
                else:
                    asyncio.ensure_future(self.set_in_cache(key, result))
    
            return result
  7. Handle Data Type Changes in TiTiler 1.0

    main

    In TiTiler 1.0, when no output format is explicitly specified, the server returns UINT8 datatype for JPEG and PNG formats by default. This is a breaking change from version 0.26.

    If your application requires specific data types (e.g., UINT16 for high-bit depth imagery), you must explicitly specify the format in your request parameters.

    # If your data needs specific datatypes, explicitly specify the format
    # Example: Request with explicit format control
    # In this case, if the input data is in uint16, the output png will be in UINT16
    response = requests.get("/tiles/1/2/3.png?url=data_in_uint16.tif")
  8. Define endpoint inputs using Dependencies in titiler Factories

    main

    In titiler Factories, dependencies are used to define the inputs for each endpoint, which also automatically generates the corresponding OpenAPI documentation.

    To create a custom dependency, define a dataclass that inherits from titiler.core.dependencies.DefaultDependency. You can use typing.Annotated combined with FastAPI's Query or Path to add descriptions and constraints to your parameters. These parameters will then be injected into your FastAPI endpoint using Depends().

    from typing import Annotated
    from dataclasses import dataclass
    from fastapi import Depends, FastAPI, Query
    from titiler.core.dependencies import DefaultDependency
    from rio_tiler.io import Reader
    
    @dataclass
    class ImageParams(DefaultDependency):
        max_size: Annotated[
            int, Query(description="Maximum image size to read onto.")
        ] = 1024
    
    app = FastAPI()
    
    @app.get("/preview.png")
    def preview(
        url: str = Query(..., description="data set URL"),
        params: ImageParams = Depends(),
    ):
        with Reader(url) as cog:
            # Use .as_dict() to pass parameters to the reader
            img = cog.preview(**params.as_dict())
        ...
  9. What are TiTiler endpoint factories?

    main

    TiTiler's endpoint factories are helper functions designed to create a FastAPI APIRouter containing a minimal, predefined set of endpoints.

    Most factories are built around rio_tiler.io.BaseReader, which provides the underlying methods for accessing datasets like COG or STAC.

    Key implementation details:

    • Default Readers: TilerFactory uses Reader by default, while MosaicTilerFactory uses MosaicBackend.
    • Configuration: Factories utilize FastAPI dependency injection to define and manage endpoint options.
  10. Optimize GDAL file discovery with GDAL_DISABLE_READDIR_ON_OPEN

    main

    The GDAL_DISABLE_READDIR_ON_OPEN setting is critical for controlling the number of requests GDAL makes when opening datasets.

    • FALSE (Default): GDAL attempts to list all files in a directory. This is required if your dataset relies on external sidecar files (e.g., .ovr overview files in an AWS S3 bucket like landsat-pds).
    • EMPTY_DIR: Tells GDAL to assume the directory is empty except for the requested file. This prevents LIST requests, reducing latency and costs (especially for requester-pays buckets). Use this in all cases where sidecar files are not required.
  11. Understand the difference between Static and Dynamic tiling

    main

    TiTiler is designed for Dynamic Tiling, which differs from traditional Static Tiling in how raster data is prepared for web map clients.

    Static Tiling

    In static tiling, tiles are pre-rendered files stored on a disk or CDN. The process involves:

    1. Rescaling non-Uint8 data to an integer range (0-255).
    2. Reprojecting data to Web Mercator (or the target projection).
    3. Splitting data into fixed tile sizes (e.g., 256x256 or 512x512) across multiple zoom levels.

    Pros: Fast loading; simple to serve via web servers or CDNs. Cons: High storage overhead (many tiny files); fixed parameters (projection, rescaling) that cannot be changed without re-rendering; potential for serving tiles that are never actually viewed.

    Dynamic Tiling (TiTiler)

    Dynamic tiling uses a tile server to access raw data (like Cloud Optimized Geotiffs - COGs) and apply operations on the fly. The process involves:

    1. Opening the file and reading internal metadata.
    2. Reading only the specific internal parts needed for the requested tile.
    3. Applying data rescaling, colormaps, and image encoding (JPEG, PNG, WEBP) dynamically.

    Pros: Access to raw data; support for multiple projections; user-defined rescaling and colormaps; band selection and math; dynamic mosaics with multiple datasets. Cons: Higher latency (requires multiple GET requests to fetch data parts); increased server complexity; harder to update (requires re-writing the source COG).

  12. Understand the titiler.application package structure

    main

    The titiler.application package is organized as follows:

    • titiler/application/templates/index.html: The landing page for the demo application.
    • titiler/application/main.py: The main FastAPI application entry point.
    • titiler/application/settings.py: Configuration for the demo application, including cache and CORS settings.
    titiler/
     └── application/
        ├── tests/                   - Tests suite
        └── titiler/application/     - `application` namespace package
            ├── templates/
            |   └── index.html       - Landing page
            ├── main.py              - Main FastAPI application
            └── settings.py          - demo settings (cache, cors...)