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

feat: STAC Render Extension support #1038

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions src/titiler/application/titiler/application/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
cogViewerExtension,
stacExtension,
stacViewerExtension,
renderExtension,
)
from titiler.mosaic.errors import MOSAIC_STATUS_CODES
from titiler.mosaic.factory import MosaicTilerFactory
Expand Down Expand Up @@ -122,6 +123,7 @@ def validate_access_token(access_token: str = Security(api_key_query)):
router_prefix="/stac",
extensions=[
stacViewerExtension(),
renderExtension(),
alekzvik marked this conversation as resolved.
Show resolved Hide resolved
],
)

Expand Down
1 change: 1 addition & 0 deletions src/titiler/extensions/titiler/extensions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
__version__ = "0.19.1"

from .cogeo import cogValidateExtension # noqa
from .render import renderExtension # noqa
from .stac import stacExtension # noqa
from .viewer import cogViewerExtension, stacViewerExtension # noqa
from .wms import wmsExtension # noqa
123 changes: 123 additions & 0 deletions src/titiler/extensions/titiler/extensions/render.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""render Extension."""
vincentsarago marked this conversation as resolved.
Show resolved Hide resolved

from pprint import pprint
from typing import Annotated, Dict, List, Optional
from urllib.parse import urlencode

from attrs import define
from fastapi import Depends, HTTPException, Path, Request
from fastapi.dependencies.utils import get_dependant, request_params_to_args
from pydantic import BaseModel, RootModel

from titiler.core.factory import FactoryExtension, MultiBaseTilerFactory


class RenderItem(BaseModel):
"""Render item for stac render extension."""

assets: List[str]
title: Optional[str] = None
rescale: Optional[list[Annotated[List[float], 2]]] = None
nodata: Optional[float] = None
colormap_name: Optional[str] = None
colormap: Optional[Dict] = None
color_formula: Optional[str] = None
resampling: Optional[str] = None
expression: Optional[str] = None
minmax_zoom: Optional[Annotated[List[int], 2]] = None
alekzvik marked this conversation as resolved.
Show resolved Hide resolved


RenderItemList = RootModel[Dict[str, RenderItem]]


class RenderItemWithLink(RenderItem):
"""Same as RenderItem with url and params."""

url: str
params: str


@define
class renderExtension(FactoryExtension):
"""Add /renders endpoint to a STAC TilerFactory."""

def register(self, factory: MultiBaseTilerFactory):
"""Register endpoint to the tiler factory."""

@factory.router.get(
"/renders",
response_model=RenderItemList,
response_model_exclude_none=True,
name="List STAC renders",
)
def render_list(src_path=Depends(factory.path_dependency)):
with factory.reader(src_path) as src:
renders = src.item.properties.get("renders", {})
pprint(renders)
return renders
alekzvik marked this conversation as resolved.
Show resolved Hide resolved

@factory.router.get(
"/renders/{render_id}",
response_model=RenderItemWithLink,
response_model_exclude_none=True,
name="Show STAC render",
alekzvik marked this conversation as resolved.
Show resolved Hide resolved
)
def render(
request: Request,
render_id: str = Path(
description="render id",
),
src_path=Depends(factory.path_dependency),
):
with factory.reader(src_path) as src:
renders = src.item.properties.get("renders", {})
if render_id not in renders:
raise HTTPException(status_code=404, detail="Render not found")
render = renders[render_id]
alekzvik marked this conversation as resolved.
Show resolved Hide resolved

url_params = {
"tileMatrixSetId": "{tileMatrixSetId}",
"z": "{z}",
"x": "{x}",
"y": "{y}",
}

url = factory.url_for(request, "tile", **url_params)

# List of dependencies a `/tile` URL should validate
# Note: Those dependencies should only require Query() inputs
tile_dependencies = [
factory.layer_dependency,
factory.dataset_dependency,
# Image rendering Dependencies
factory.rescale_dependency,
factory.color_formula_dependency,
factory.colormap_dependency,
factory.render_dependency,
]

final_query = {
"url": src_path,
}
for dependency in tile_dependencies:
dep = get_dependant(path="", call=dependency)
if dep.query_params:
# call the dependency with the query-parameters values
query_values, errors = request_params_to_args(
dep.query_params, render
)
_ = dependency(**query_values)
final_query.update(query_values)

return {
**render,
"url": str(url),
"params": urlencode(
{
key: value
for key, value in final_query.items()
if value is not None
},
doseq=True,
),
}
Loading