Skip to content

update titiler/titiler-pgstac #252

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

Draft
wants to merge 2 commits 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
187 changes: 164 additions & 23 deletions pctiler/pctiler/endpoints/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@
from typing import Annotated, Optional
from urllib.parse import quote_plus, urljoin

import jinja2
import fastapi
import pystac
from fastapi import Body, Depends, Query, Request, Response
from pydantic import Field
from fastapi import Body, Depends, Query, Request, Response, Path
from fastapi.templating import Jinja2Templates
from geojson_pydantic.features import Feature
from html_sanitizer.sanitizer import Sanitizer
from starlette.responses import HTMLResponse
from titiler.core.dependencies import CoordCRSParams, DstCRSParams
from titiler.core.factory import MultiBaseTilerFactory, img_endpoint_params
from titiler.core.resources.enums import ImageType
from titiler.core.models.mapbox import TileJSON
from titiler.pgstac.dependencies import get_stac_item

from pccommon.config import get_render_config
Expand All @@ -22,12 +25,6 @@
from pctiler.endpoints.dependencies import get_endpoint_function
from pctiler.reader import ItemSTACReader, ReaderParams

try:
from importlib.resources import files as resources_files # type: ignore
except ImportError:
# Try backported to PY<39 `importlib_resources`.
from importlib_resources import files as resources_files # type: ignore

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -79,11 +76,10 @@ async def _fetch() -> dict:
return pystac.Item.from_dict(_item)


# TODO: mypy fails in python 3.9, we need to find a proper way to do this
templates = Jinja2Templates(
directory=str(resources_files(__package__) / "templates") # type: ignore
jinja2_env = jinja2.Environment(
loader=jinja2.ChoiceLoader([jinja2.PackageLoader(__package__, "templates")])
)

templates = Jinja2Templates(env=jinja2_env)

pc_tile_factory = MultiBaseTilerFactory(
reader=ItemSTACReader,
Expand Down Expand Up @@ -135,17 +131,20 @@ def map(
},
)


# crop/feature endpoint compat with titiler<0.15 (`/crop` was renamed `/feature`)
@pc_tile_factory.router.post(
r"/crop",
operation_id=f"{self.operation_prefix}postDataForGeoJSONCrop",
**img_endpoint_params,
)
@pc_tile_factory.router.post(
r"/crop.{format}",
operation_id=f"{self.operation_prefix}postDataForGeoJSONWithFormatCrop",
**img_endpoint_params,
)
@pc_tile_factory.router.post(
r"/crop/{width}x{height}.{format}",
operation_id=f"{self.operation_prefix}postDataForGeoJSONWithSizesAndFormatCrop",
**img_endpoint_params,
)
def geojson_crop( # type: ignore
Expand All @@ -155,7 +154,9 @@ def geojson_crop( # type: ignore
],
format: Annotated[
ImageType,
"Default will be automatically defined if the output image needs a mask (png) or not (jpeg).", # noqa: E501,F722
Field(
description="Default will be automatically defined if the output image needs a mask (png) or not (jpeg)."
),
] = None, # type: ignore[assignment]
src_path=Depends(pc_tile_factory.path_dependency),
coord_crs=Depends(CoordCRSParams),
Expand All @@ -164,14 +165,6 @@ def geojson_crop( # type: ignore
dataset_params=Depends(pc_tile_factory.dataset_dependency),
image_params=Depends(pc_tile_factory.img_part_dependency),
post_process=Depends(pc_tile_factory.process_dependency),
rescale=Depends(pc_tile_factory.rescale_dependency),
color_formula: Annotated[
Optional[str],
Query(
title="Color Formula", # noqa: F722
description="rio-color formula (info: https://github.com/mapbox/rio-color)", # noqa: E501,F722
),
] = None,
colormap=Depends(pc_tile_factory.colormap_dependency),
render_params=Depends(pc_tile_factory.render_dependency),
reader_params=Depends(pc_tile_factory.reader_dependency),
Expand All @@ -191,11 +184,159 @@ def geojson_crop( # type: ignore
dataset_params=dataset_params,
image_params=image_params,
post_process=post_process,
rescale=rescale,
color_formula=color_formula,
colormap=colormap,
render_params=render_params,
reader_params=reader_params,
env=env,
)
return result


# /tiles endpoint compat with titiler<0.15, Optional `tileMatrixSetId`
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added this because I thought we needed default WebMercatorQuad tilematrixset back.

We can remove if not needed

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's keep it, I see in our logs that we do have callers that request tiles without a TMS in the path.

For others, you can verify with this query in pc-api-appinsights:

ProductionRequests
| where name == "GET" and service == "tiler"
| where not(url  has "/preview.png")
| where url has "item/tiles" and not(url has "tiles/WebMercatorQuad")

@pc_tile_factory.router.get(
"/tiles/{z}/{x}/{y}",
operation_id=f"{pc_tile_factory.operation_prefix}getWebMercatorQuadTile",
**img_endpoint_params,
)
@pc_tile_factory.router.get(
"/tiles/{z}/{x}/{y}.{format}",
operation_id=f"{pc_tile_factory.operation_prefix}getWebMercatorQuadTileWithFormat",
**img_endpoint_params,
)
@pc_tile_factory.router.get(
"/tiles/{z}/{x}/{y}@{scale}x",
operation_id=f"{pc_tile_factory.operation_prefix}getWebMercatorQuadTileWithScale",
**img_endpoint_params,
)
@pc_tile_factory.router.get(
"/tiles/{z}/{x}/{y}@{scale}x.{format}",
operation_id=f"{pc_tile_factory.operation_prefix}getWebMercatorQuadTileWithFormatAndScale",
**img_endpoint_params,
)
def tile_compat(
request: fastapi.Request,
z: Annotated[
int,
Path(
description="Identifier (Z) selecting one of the scales defined in the TileMatrixSet and representing the scaleDenominator the tile.",
),
],
x: Annotated[
int,
Path(
description="Column (X) index of the tile on the selected TileMatrix. It cannot exceed the MatrixHeight-1 for the selected TileMatrix.",
),
],
y: Annotated[
int,
Path(
description="Row (Y) index of the tile on the selected TileMatrix. It cannot exceed the MatrixWidth-1 for the selected TileMatrix.",
),
],
scale: Annotated[
int,
Field(
gt=0, le=4, description="Tile size scale. 1=256x256, 2=512x512..."
),
] = 1,
format: Annotated[
ImageType,
Field(
description="Default will be automatically defined if the output image needs a mask (png) or not (jpeg)."
),
] = None,
src_path=Depends(pc_tile_factory.path_dependency),
reader_params=Depends(pc_tile_factory.reader_dependency),
tile_params=Depends(pc_tile_factory.tile_dependency),
layer_params=Depends(pc_tile_factory.layer_dependency),
dataset_params=Depends(pc_tile_factory.dataset_dependency),
post_process=Depends(pc_tile_factory.process_dependency),
colormap=Depends(pc_tile_factory.colormap_dependency),
render_params=Depends(pc_tile_factory.render_dependency),
env=Depends(pc_tile_factory.environment_dependency),
) -> Response:
"""tiles endpoints compat."""
endpoint = get_endpoint_function(
pc_tile_factory.router, path="/tiles/{tileMatrixSetId}/{z}/{x}/{y}", method=request.method
)
result = endpoint(
z=z,
x=x,
y=y,
tileMatrixSetId="WebMercatorQuad",
scale=scale,
format=format,
src_path=src_path,
reader_params=reader_params,
tile_params=tile_params,
layer_params=layer_params,
dataset_params=dataset_params,
post_process=post_process,
colormap=colormap,
render_params=render_params,
env=env,
)
return result


# /tilejson.json endpoint compat with titiler<0.15, Optional `tileMatrixSetId`
@pc_tile_factory.router.get(
"/tilejson.json",
response_model=TileJSON,
responses={200: {"description": "Return a tilejson"}},
response_model_exclude_none=True,
operation_id=f"{pc_tile_factory.operation_prefix}getWebMercatorQuadTileJSON",
)
def tilejson_compat(
request: fastapi.Request,
tile_format: Annotated[
Optional[ImageType],
Query(
description="Default will be automatically defined if the output image needs a mask (png) or not (jpeg).",
),
] = None,
tile_scale: Annotated[
int,
Query(
gt=0, lt=4, description="Tile size scale. 1=256x256, 2=512x512..."
),
] = 1,
minzoom: Annotated[
Optional[int],
Query(description="Overwrite default minzoom."),
] = None,
maxzoom: Annotated[
Optional[int],
Query(description="Overwrite default maxzoom."),
] = None,
src_path=Depends(pc_tile_factory.path_dependency),
reader_params=Depends(pc_tile_factory.reader_dependency),
tile_params=Depends(pc_tile_factory.tile_dependency),
layer_params=Depends(pc_tile_factory.layer_dependency),
dataset_params=Depends(pc_tile_factory.dataset_dependency),
post_process=Depends(pc_tile_factory.process_dependency),
colormap=Depends(pc_tile_factory.colormap_dependency),
render_params=Depends(pc_tile_factory.render_dependency),
env=Depends(pc_tile_factory.environment_dependency),
) -> Response:
"""tilejson endpoint compat."""
endpoint = get_endpoint_function(
pc_tile_factory.router, path="/{tileMatrixSetId}/tilejson.json", method=request.method
)
result = endpoint(
tileMatrixSetId="WebMercatorQuad",
tile_format=tile_format,
tile_scale=tile_scale,
minzoom=minzoom,
maxzoom=maxzoom,
src_path=src_path,
reader_params=reader_params,
tile_params=tile_params,
layer_params=layer_params,
dataset_params=dataset_params,
post_process=post_process,
colormap=colormap,
render_params=render_params,
env=env,
)
return result
59 changes: 38 additions & 21 deletions pctiler/pctiler/endpoints/pg_mosaic.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from dataclasses import dataclass, field
from typing import Annotated, List, Literal, Optional

from fastapi import APIRouter, Depends, FastAPI, Query, Request, Response
from fastapi import APIRouter, Depends, FastAPI, Query, Request, Response, Path
from fastapi.responses import ORJSONResponse
from psycopg_pool import ConnectionPool
from pydantic import Field
from titiler.core import dependencies
from titiler.core.dependencies import ColorFormulaParams
from titiler.core.factory import img_endpoint_params
from titiler.core.resources.enums import ImageType
from titiler.pgstac.dependencies import SearchIdParams, TmsTileParams
from titiler.pgstac.dependencies import SearchIdParams
from titiler.pgstac.extensions import searchInfoExtension
from titiler.pgstac.factory import MosaicTilerFactory

Expand Down Expand Up @@ -40,7 +40,7 @@ def __init__(self, request: Request):


pgstac_mosaic_factory = MosaicTilerFactory(
reader=PGSTACBackend,
backend=PGSTACBackend,
path_dependency=SearchIdParams,
colormap_dependency=PCColorMapParams,
layer_dependency=AssetsBidxExprParams,
Expand Down Expand Up @@ -86,7 +86,7 @@ def mosaic_info(

legacy_mosaic_router = APIRouter()


# Compat with titiler-pgstac<0.3.0, (`/tiles/{search_id}/...` was renamed `/{search_id}/tiles/...`)
@legacy_mosaic_router.get("/tiles/{search_id}/{z}/{x}/{y}", **img_endpoint_params)
@legacy_mosaic_router.get(
"/tiles/{search_id}/{z}/{x}/{y}.{format}", **img_endpoint_params
Expand Down Expand Up @@ -115,57 +115,74 @@ def mosaic_info(
def tile_routes( # type: ignore
request: Request,
search_id=Depends(pgstac_mosaic_factory.path_dependency),
tile=Depends(TmsTileParams),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

TmsTileParams is not used anymore in titiler-pgstac so it might disappear 🙈

Copy link
Contributor

Choose a reason for hiding this comment

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

Not a problem!

z: Annotated[
int,
Path(
description="Identifier (Z) selecting one of the scales defined in the TileMatrixSet and representing the scaleDenominator the tile.",
),
],
x: Annotated[
int,
Path(
description="Column (X) index of the tile on the selected TileMatrix. It cannot exceed the MatrixHeight-1 for the selected TileMatrix.",
),
],
y: Annotated[
int,
Path(
description="Row (Y) index of the tile on the selected TileMatrix. It cannot exceed the MatrixWidth-1 for the selected TileMatrix.",
),
],
tileMatrixSetId: Annotated[ # type: ignore
Literal[tuple(pgstac_mosaic_factory.supported_tms.list())],
f"Identifier selecting one of the TileMatrixSetId supported (default: '{pgstac_mosaic_factory.default_tms}')", # noqa: E501,F722
] = pgstac_mosaic_factory.default_tms,
f"Identifier selecting one of the TileMatrixSetId supported (default: 'WebMercatorQuad')", # noqa: E501,F722
] = "WebMercatorQuad",
scale: Annotated[ # type: ignore
Optional[Annotated[int, Field(gt=0, le=4)]],
"Tile size scale. 1=256x256, 2=512x512...", # noqa: E501,F722
] = None,
format: Annotated[
Optional[ImageType],
"Default will be automatically defined if the output image needs a mask (png) or not (jpeg).", # noqa: E501,F722
Field(
description="Default will be automatically defined if the output image needs a mask (png) or not (jpeg)."
),
] = None,
backend_params=Depends(pgstac_mosaic_factory.backend_dependency),
reader_params=Depends(pgstac_mosaic_factory.reader_dependency),
assets_accessor_params=Depends(pgstac_mosaic_factory.assets_accessor_dependency),
layer_params=Depends(pgstac_mosaic_factory.layer_dependency),
dataset_params=Depends(pgstac_mosaic_factory.dataset_dependency),
pixel_selection=Depends(pgstac_mosaic_factory.pixel_selection_dependency),
tile_params=Depends(pgstac_mosaic_factory.tile_dependency),
post_process=Depends(pgstac_mosaic_factory.process_dependency),
rescale=Depends(pgstac_mosaic_factory.rescale_dependency),
color_formula=Depends(ColorFormulaParams),
colormap=Depends(pgstac_mosaic_factory.colormap_dependency),
render_params=Depends(pgstac_mosaic_factory.render_dependency),
pgstac_params=Depends(pgstac_mosaic_factory.pgstac_dependency),
backend_params=Depends(pgstac_mosaic_factory.backend_dependency),
reader_params=Depends(pgstac_mosaic_factory.reader_dependency),
env=Depends(pgstac_mosaic_factory.environment_dependency),
) -> Response:
"""Create map tile."""
endpoint = get_endpoint_function(
pgstac_mosaic_factory.router,
path="/tiles/{z}/{x}/{y}",
path="/tiles/{tileMatrixSetId}/{z}/{x}/{y}",
method=request.method,
)
result = endpoint(
search_id=search_id,
tile=tile,
z=z,
x=x,
y=y,
tileMatrixSetId=tileMatrixSetId,
scale=scale,
format=format,
tile_params=tile_params,
backend_params=backend_params,
reader_params=reader_params,
assets_accessor_params=assets_accessor_params,
layer_params=layer_params,
dataset_params=dataset_params,
pixel_selection=pixel_selection,
tile_params=tile_params,
post_process=post_process,
rescale=rescale,
color_formula=color_formula,
colormap=colormap,
render_params=render_params,
pgstac_params=pgstac_params,
backend_params=backend_params,
reader_params=reader_params,
env=env,
)
return result
Loading
Loading