diff --git a/CHANGELOG.md b/CHANGELOG.md index b23a2db..27a06bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +## [0.4.0] - 2025-06-30 + +* Upgrade to become compatible with titiler.core/titiler.mosaic v0.19 (author @jverrydt, https://github.com/developmentseed/titiler-stacapi/pull/32) + ## [0.3.2] - 2025-05-19 * Align ows:Title, Identifier and Abstract in WMTS GetCapabilities (author @jverrydt, https://github.com/developmentseed/titiler-stacapi/pull/31) @@ -28,6 +32,8 @@ * initial release [Unreleased]: +[0.3.2]: +[0.3.1]: [0.3.0]: [0.2.0]: [0.1.1]: diff --git a/pyproject.toml b/pyproject.toml index 47c2db1..aca9366 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,8 +24,8 @@ classifiers = [ ] dependencies = [ "orjson", - "titiler.core>=0.17.0,<0.19", - "titiler.mosaic>=0.17.0,<0.19", + "titiler.core>=0.19.0,<0.20", + "titiler.mosaic>=0.19.0,<0.20", "pystac-client", "pydantic>=2.4,<3.0", "pydantic-settings~=2.0", diff --git a/titiler/stacapi/__init__.py b/titiler/stacapi/__init__.py index f1a1cba..be36dfa 100644 --- a/titiler/stacapi/__init__.py +++ b/titiler/stacapi/__init__.py @@ -1,3 +1,3 @@ """titiler.stacapi""" -__version__ = "0.3.2" +__version__ = "0.4.0" diff --git a/titiler/stacapi/factory.py b/titiler/stacapi/factory.py index d2e436a..3e05b8d 100644 --- a/titiler/stacapi/factory.py +++ b/titiler/stacapi/factory.py @@ -4,13 +4,13 @@ import json import os from copy import copy -from dataclasses import dataclass, field from enum import Enum from typing import Any, Callable, Dict, List, Literal, Optional, Type from urllib.parse import urlencode import jinja2 import rasterio +from attrs import define, field from cachetools import TTLCache, cached from cachetools.keys import hashkey from cogeo_mosaic.backends import BaseBackend @@ -38,7 +38,7 @@ DefaultDependency, TileParams, ) -from titiler.core.factory import BaseTilerFactory, img_endpoint_params +from titiler.core.factory import TilerFactory, img_endpoint_params from titiler.core.models.mapbox import TileJSON from titiler.core.resources.enums import ImageType, MediaType, OptionalHeader from titiler.core.resources.responses import GeoJSONResponse, XMLResponse @@ -95,17 +95,17 @@ def get_dependency_params(*, dependency: Callable, query_params: Dict) -> Any: return -@dataclass -class MosaicTilerFactory(BaseTilerFactory): +@define(kw_only=True) +class MosaicTilerFactory(TilerFactory): """Custom MosaicTiler for STACAPI Mosaic Backend.""" path_dependency: Callable[..., APIParams] = STACApiParams search_dependency: Callable[..., Dict] = STACSearchParams - # In this factory, `reader` should be a Mosaic Backend + # In this factory, `backend` should be a Mosaic Backend # https://developmentseed.org/cogeo-mosaic/advanced/backends/ - reader: Type[BaseBackend] = STACAPIBackend + backend: Type[BaseBackend] = STACAPIBackend # Because the endpoints should work with STAC Items, # the `layer_dependency` define which query parameters are mandatory/optional to `display` images @@ -124,6 +124,8 @@ class MosaicTilerFactory(BaseTilerFactory): templates: Jinja2Templates = DEFAULT_TEMPLATES + optional_headers: List[OptionalHeader] = field(factory=list) + def get_base_url(self, request: Request) -> str: """return endpoints base url.""" base_url = str(request.base_url).rstrip("/") @@ -219,12 +221,12 @@ def tile( tms = self.supported_tms.get(tileMatrixSetId) with rasterio.Env(**env): - with self.reader( + with self.backend( url=api_params["api_url"], headers=api_params.get("headers", {}), tms=tms, - reader_options={**reader_params}, - **backend_params, + reader_options={**reader_params.as_dict()}, + **backend_params.as_dict(), ) as src_dst: if MOSAIC_STRICT_ZOOM and ( z < src_dst.minzoom or z > src_dst.maxzoom @@ -242,9 +244,9 @@ def tile( tilesize=scale * 256, pixel_selection=pixel_selection, threads=MOSAIC_THREADS, - **tile_params, - **layer_params, - **dataset_params, + **tile_params.as_dict(), + **layer_params.as_dict(), + **dataset_params.as_dict(), ) if post_process: @@ -260,7 +262,7 @@ def tile( image, output_format=format, colormap=colormap, - **render_params, + **render_params.as_dict(), ) headers: Dict[str, str] = {} @@ -725,15 +727,15 @@ def get_layer_from_collections( # noqa: C901 return layers -@dataclass -class OGCWMTSFactory(BaseTilerFactory): +@define(kw_only=True) +class OGCWMTSFactory(TilerFactory): """Create /wmts endpoint""" path_dependency: Callable[..., APIParams] = STACApiParams # In this factory, `reader` should be a Mosaic Backend # https://developmentseed.org/cogeo-mosaic/advanced/backends/ - reader: Type[BaseBackend] = STACAPIBackend + backend: Type[BaseBackend] = STACAPIBackend query_dependency: Callable[..., Any] = STACQueryParams @@ -751,7 +753,7 @@ class OGCWMTSFactory(BaseTilerFactory): backend_dependency: Type[DefaultDependency] = DefaultDependency supported_format: List[str] = field( - default_factory=lambda: [ + factory=lambda: [ "image/png", "image/jpeg", "image/jpg", @@ -761,7 +763,7 @@ class OGCWMTSFactory(BaseTilerFactory): ] ) - supported_version: List[str] = field(default_factory=lambda: ["1.0.0"]) + supported_version: List[str] = field(factory=lambda: ["1.0.0"]) templates: Jinja2Templates = DEFAULT_TEMPLATES @@ -799,7 +801,7 @@ def get_tile( # noqa: C901 y = int(req["tilerow"]) tms = self.supported_tms.get(tms_id) - with self.reader( + with self.backend( url=stac_url, headers=headers, tms=tms, @@ -863,9 +865,9 @@ def get_tile( # noqa: C901 search_query=search_query, pixel_selection=pixel_selection, threads=MOSAIC_THREADS, - **tile_params, - **layer_params, - **dataset_params, + **tile_params.as_dict(), + **layer_params.as_dict(), + **dataset_params.as_dict(), ) if post_process := get_dependency_params( @@ -1382,12 +1384,12 @@ def WMTS_getTile( tms = self.supported_tms.get(tileMatrixSetId) with rasterio.Env(**env): - with self.reader( + with self.backend( url=api_params["api_url"], headers=api_params.get("headers", {}), tms=tms, - reader_options={**reader_params}, - **backend_params, + reader_options={**reader_params.as_dict()}, + **backend_params.as_dict(), ) as src_dst: if MOSAIC_STRICT_ZOOM and ( z < src_dst.minzoom or z > src_dst.maxzoom @@ -1405,9 +1407,9 @@ def WMTS_getTile( tilesize=256, pixel_selection=pixel_selection, threads=MOSAIC_THREADS, - **tile_params, - **layer_params, - **dataset_params, + **tile_params.as_dict(), + **layer_params.as_dict(), + **dataset_params.as_dict(), ) if post_process: @@ -1423,7 +1425,7 @@ def WMTS_getTile( image, output_format=format, colormap=colormap, - **render_params, + **render_params.as_dict(), ) return Response(content, media_type=media_type) diff --git a/titiler/stacapi/main.py b/titiler/stacapi/main.py index 610801a..e487409 100644 --- a/titiler/stacapi/main.py +++ b/titiler/stacapi/main.py @@ -4,8 +4,10 @@ from typing import Any, Dict, List, Optional import jinja2 +import morecantile from fastapi import Depends, FastAPI from fastapi.responses import ORJSONResponse +from morecantile import TileMatrixSets from starlette.middleware.cors import CORSMiddleware from starlette.requests import Request from starlette.templating import Jinja2Templates @@ -85,9 +87,11 @@ ############################################################################### # OGC WMTS Endpoints +supported_tms = TileMatrixSets( + {"WebMercatorQuad": morecantile.tms.get("WebMercatorQuad")} +) wmts = OGCWMTSFactory( - path_dependency=STACApiParams, - templates=templates, + path_dependency=STACApiParams, templates=templates, supported_tms=supported_tms ) app.include_router( wmts.router, @@ -119,7 +123,6 @@ stac = MultiBaseTilerFactory( reader=STACReader, path_dependency=ItemIdParams, - optional_headers=optional_headers, router_prefix="/collections/{collection_id}/items/{item_id}", add_viewer=True, ) diff --git a/titiler/stacapi/reader.py b/titiler/stacapi/reader.py index 10ef9d7..9a2d4f0 100644 --- a/titiler/stacapi/reader.py +++ b/titiler/stacapi/reader.py @@ -25,8 +25,6 @@ class STACReader(stac.STACReader): """ - input: pystac.Item = attr.ib() - tms: TileMatrixSet = attr.ib(default=WEB_MERCATOR_TMS) minzoom: int = attr.ib() maxzoom: int = attr.ib()