Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] sketch pmtiles support #213

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ dev = [
server = [
"uvicorn[standard]>=0.12.0,<0.19.0",
]
pmtiles = [
"aiopmtiles @ git+https://github.com/developmentseed/aiopmtiles.git",
]
docs = [
"black>=23.10.1",
"mkdocs",
Expand Down
75 changes: 72 additions & 3 deletions tipg/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,26 @@
from tipg.filter.filters import bbox_to_wkt
from tipg.logger import logger
from tipg.model import Extent
from tipg.resources.enums import MediaType
from tipg.settings import (
DatabaseSettings,
FeaturesSettings,
MVTSettings,
PMTilesSettings,
TableConfig,
TableSettings,
)

from fastapi import FastAPI

from starlette.requests import Request
from starlette.responses import Response

try:
import aiopmtiles
except ImportError: # pragma: nocover
aiopmtiles = None # type: ignore


mvt_settings = MVTSettings()
features_settings = FeaturesSettings()
Expand Down Expand Up @@ -352,6 +361,54 @@ class Catalog(TypedDict):
last_updated: datetime.datetime


class PMTilesCollection(Collection):
"""Model for a PMTiles archive."""

@model_validator(mode="before")
@classmethod
def check_aiopmtiles(cls, data: Any) -> Any:
"""Make sure aiopmtiles is available."""
assert (
aiopmtiles is not None
), "aiopmtiles must be installed to use PMTilesCollection"
return data

async def features(
self,
request: Request,
**kwargs,
) -> ItemList:
"""Items for PmTiles."""
return ItemList(
items=[],
matched=0,
next=None,
prev=None,
)

async def get_tile(
self,
request: Request,
*,
tms: TileMatrixSet,
tile: Tile,
**kwargs,
) -> Response: # type: ignore
"""Tile for PmTiles."""
async with aiopmtiles.Reader(self.table) as src:
# TODO: Check TMS support, assume they are in WebMercatorQuad
tile = await src.get_tile(tile.z, tile.x, tile.y)
headers: Dict[str, str] = {}
if src.tile_compression.value != 1: # None
headers["Content-Encoding"] = src.tile_compression.name.lower()

return Response(
tile,
media_type=MediaType[src.tile_type.name.lower()].value,
headers=headers,
)


class PgCollection(Collection):
"""Model for DB Table and Function."""

Expand Down Expand Up @@ -924,7 +981,7 @@ async def get_tile(
async def pg_get_collection_index( # noqa: C901
db_pool: asyncpg.BuildPgPool,
settings: Optional[DatabaseSettings] = None,
) -> List[Collection]:
) -> List[PgCollection]:
"""Fetch Table and Functions index."""
if not settings:
settings = DatabaseSettings()
Expand Down Expand Up @@ -961,7 +1018,7 @@ async def pg_get_collection_index( # noqa: C901
datetime_extent=settings.datetime_extent,
)

collections: List[Collection] = []
collections: List[PgCollection] = []
table_settings = TableSettings()
table_confs = table_settings.table_config
fallback_key_names = table_settings.fallback_key_names
Expand Down Expand Up @@ -1028,14 +1085,26 @@ async def pg_get_collection_index( # noqa: C901
return collections


def pmtiles_get_collection(
settings: Optional[DatabaseSettings] = None,
) -> List[PMTilesCollection]:
"""return a list of PMTiles collections."""
...


async def register_collection_catalog(
app: FastAPI,
db_settings: Optional[DatabaseSettings] = None,
pmtiles_settings: Optional[PMTilesSettings] = None,
) -> None:
"""Register Table catalog."""
db_collections = await pg_get_collection_index(app.state.pool, settings=db_settings)

pmtiles_collections = []
if aiopmtiles:
pmtiles_collections = pmtiles_get_collection(settings=pmtiles_settings)

app.state.collection_catalog = Catalog(
collections={col.id: col for col in [*db_collections]},
collections={col.id: col for col in [*db_collections, *pmtiles_collections]},
last_updated=datetime.datetime.now(),
)
2 changes: 2 additions & 0 deletions tipg/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -1667,6 +1667,8 @@ async def collection_get_tile(
geom=geom_column,
dt=datetime_column,
)
if isinstance(tile, Response):
return tile

return Response(tile, media_type=MediaType.mvt.value)

Expand Down
14 changes: 14 additions & 0 deletions tipg/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,17 @@ def sql_files(self) -> Optional[List[pathlib.Path]]:
return list(self.custom_sql_directory.glob("*.sql"))

return None


class PMTilesSettings(BaseSettings):
"""TiPg PMTiles settings."""

paths: Optional[List[str]] = None
sources: Optional[Dict[str, str]] = None

model_config = {
"env_prefix": "TIPG_PMTILES",
"env_file": ".env",
"env_nested_delimiter": "__",
"extra": "ignore",
}
Loading