Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -28,6 +32,8 @@
* initial release

[Unreleased]: <https://github.com/developmentseed/titiler-stacapi/compare/0.3.0..main>
[0.3.2]: <https://github.com/developmentseed/titiler-stacapi/compare/0.3.1..0.3.2>
[0.3.1]: <https://github.com/developmentseed/titiler-stacapi/compare/0.3.0..0.3.1>
[0.3.0]: <https://github.com/developmentseed/titiler-stacapi/compare/0.2.0..0.3.0>
[0.2.0]: <https://github.com/developmentseed/titiler-stacapi/compare/0.1.1..0.2.0>
[0.1.1]: <https://github.com/developmentseed/titiler-stacapi/compare/0.1.0..0.1.1>
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure we could go up to 0.21 (there are minor changes between 0.20 et 0.21)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but now it only made it compatible to 0.19. In 0.20 there were some other breaking changes.

"titiler.mosaic>=0.19.0,<0.20",
"pystac-client",
"pydantic>=2.4,<3.0",
"pydantic-settings~=2.0",
Expand Down
2 changes: 1 addition & 1 deletion titiler/stacapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""titiler.stacapi"""

__version__ = "0.3.2"
__version__ = "0.4.0"
60 changes: 31 additions & 29 deletions titiler/stacapi/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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("/")
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -260,7 +262,7 @@ def tile(
image,
output_format=format,
colormap=colormap,
**render_params,
**render_params.as_dict(),
)

headers: Dict[str, str] = {}
Expand Down Expand Up @@ -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

Expand All @@ -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",
Expand All @@ -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

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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)
9 changes: 6 additions & 3 deletions titiler/stacapi/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
)
Expand Down
2 changes: 0 additions & 2 deletions titiler/stacapi/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ class STACReader(stac.STACReader):

"""

input: pystac.Item = attr.ib()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this change?

I'm pretty sure we did this on purpose before

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was throwing following error and it is already defined in its parent class (stac.STACReader).

ValueError: No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: Attribute(name='input', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=<class 'pystac.item.Item'>, converter=None, kw_only=False, inherited=False, on_setattr=None, alias=None)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if you need some more input from out side


tms: TileMatrixSet = attr.ib(default=WEB_MERCATOR_TMS)
minzoom: int = attr.ib()
maxzoom: int = attr.ib()
Expand Down