TiPg Vector Tiles server

TiPg default application comes with both OGC Tiles and Features endpoints but some users might just want one or the other. TiPg is built around the notion of endpoints factories which then makes easy to build custom applications with the desired set of endpoints.

The code below shows how to create a simple OGC Vector tiles service.

"""OGC Vector Tiles service."""

from contextlib import asynccontextmanager

import jinja2

from tipg.collections import register_collection_catalog
from tipg.database import close_db_connection, connect_to_db
from tipg.errors import DEFAULT_STATUS_CODES, add_exception_handlers
from tipg.factory import OGCTilesFactory
from tipg.middleware import CacheControlMiddleware, CatalogUpdateMiddleware
from tipg.settings import CustomSQLSettings, DatabaseSettings, PostgresSettings

from fastapi import FastAPI

from starlette.middleware.cors import CORSMiddleware
from starlette.templating import Jinja2Templates
from starlette_cramjam.middleware import CompressionMiddleware

postgres_settings = PostgresSettings()
db_settings = DatabaseSettings()
custom_sql_settings = CustomSQLSettings()


@asynccontextmanager
async def lifespan(app: FastAPI):
    """FastAPI Lifespan."""
    # Create Connection Pool
    await connect_to_db(
        app,
        settings=postgres_settings,
        schemas=db_settings.schemas,
        user_sql_files=custom_sql_settings.sql_files,
    )

    # Register Collection Catalog
    await register_collection_catalog(
        app,
        schemas=db_settings.schemas,
        tables=db_settings.tables,
        exclude_tables=db_settings.exclude_tables,
        exclude_table_schemas=db_settings.exclude_table_schemas,
        functions=db_settings.functions,
        exclude_functions=db_settings.exclude_functions,
        exclude_function_schemas=db_settings.exclude_function_schemas,
        spatial=db_settings.only_spatial_tables,
    )

    yield

    # Close the Connection Pool
    await close_db_connection(app)


app = FastAPI(
    title="TiPG Vector Tiles Server",
    openapi_url="/api",
    docs_url="/api.html",
    lifespan=lifespan,
)

templates = Jinja2Templates(
    directory="",  # we need to set a dummy directory variable, see https://github.com/encode/starlette/issues/1214
    loader=jinja2.ChoiceLoader([jinja2.PackageLoader("tipg", "templates")]),
)  # type: ignore

ogc_tiles = OGCTilesFactory(
    title="TiPG Vector Tiles Server",
    templates=templates,
    with_common=True,
    with_viewer=True,
)
app.include_router(ogc_tiles.router)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["GET"],
    allow_headers=["*"],
)
app.add_middleware(CacheControlMiddleware, cachecontrol="public, max-age=3600")
app.add_middleware(CompressionMiddleware)
app.add_middleware(
    CatalogUpdateMiddleware,
    func=register_collection_catalog,
    ttl=300,
    schemas=db_settings.schemas,
    tables=db_settings.tables,
    exclude_tables=db_settings.exclude_tables,
    exclude_table_schemas=db_settings.exclude_table_schemas,
    functions=db_settings.functions,
    exclude_functions=db_settings.exclude_functions,
    exclude_function_schemas=db_settings.exclude_function_schemas,
    spatial=db_settings.only_spatial_tables,
)

add_exception_handlers(app, DEFAULT_STATUS_CODES)


@app.get(
    "/healthz",
    description="Health Check.",
    summary="Health Check.",
    operation_id="healthCheck",
    tags=["Health Check"],
)
def ping():
    """Health check."""
    return {"ping": "pong!"}