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

add Layers to wmts and reorder dependencies #1012

Merged
merged 1 commit into from
Oct 29, 2024
Merged
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
8 changes: 8 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@

* `/bounds` endpoints now return a `crs: str` attribute in the response

* update `wmts.xml` template to support multiple layers

* re-order endpoints parameters

* avoid `lat/lon` overflow in `map` viewer

### titiler.mosaic

* Rename `reader` attribute to `backend` in `MosaicTilerFactory` **breaking change**
Expand All @@ -86,6 +92,8 @@

* Update `cogeo-mosaic` dependency to `>=8.0,<9.0`

* re-order endpoints parameters

### titiler.extensions

* Encode URL for cog_viewer and stac_viewer (author @guillemc23, https://github.com/developmentseed/titiler/pull/961)
Expand Down
2 changes: 1 addition & 1 deletion src/titiler/application/tests/routes/test_cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def test_wmts(rio, app):
"http://testserver/cog/WebMercatorQuad/WMTSCapabilities.xml?url=https"
in response.content.decode()
)
assert "<ows:Identifier>Dataset</ows:Identifier>" in response.content.decode()
assert "<ows:Identifier>default</ows:Identifier>" in response.content.decode()
assert (
"http://testserver/cog/tiles/WebMercatorQuad/{TileMatrix}/{TileCol}/{TileRow}@1x.png?url=https"
in response.content.decode()
Expand Down
2 changes: 1 addition & 1 deletion src/titiler/core/titiler/core/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ def CRSParams(
crs: Annotated[
Optional[str],
Query(
description="Coordinate Reference System`.",
description="Coordinate Reference System.",
),
] = None,
) -> Optional[CRS]:
Expand Down
97 changes: 51 additions & 46 deletions src/titiler/core/titiler/core/factory.py

Large diffs are not rendered by default.

29 changes: 19 additions & 10 deletions src/titiler/core/titiler/core/templates/map.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,20 +86,33 @@

let bounds = [...data.bounds]

var left = bounds[0],
bottom = bounds[1],
right = bounds[2],
top = bounds[3];

if (left < right) {
left = Math.max(left, {{ tms.bbox.left }})
right = Math.min(right, {{ tms.bbox.right }})
}
bottom = Math.max(bottom, {{ tms.bbox.bottom }})
top = Math.min(top, {{ tms.bbox.top }})
console.log(left, bottom, right, top)

let geo;
// handle files that span accross dateline
if (bounds[0] > bounds[2]) {
if (left > right) {
geo = {
"type": "FeatureCollection",
"features": [
bboxPolygon([-180, bounds[1], bounds[2], bounds[3]]),
bboxPolygon([bounds[0], bounds[1], 180, bounds[3]]),
bboxPolygon([-180, bottom, right, top]),
bboxPolygon([left, bottom, 180, top]),
]
}
} else {
geo = {
"type": "FeatureCollection",
"features": [bboxPolygon(bounds)]
"features": [bboxPolygon([left, bottom, right, top])]
}
}
console.log(geo)
Expand All @@ -110,13 +123,9 @@
map.fitBounds(aoi.getBounds());

// Bounds crossing dateline
if (bounds[0] > bounds[2]) {
bounds[0] = bounds[0] - 360
if (left > right) {
left = right - 360
}
var left = bounds[0],
bottom = bounds[1],
right = bounds[2],
top = bounds[3];

L.tileLayer(
data.tiles[0], {
Expand Down
18 changes: 10 additions & 8 deletions src/titiler/core/titiler/core/templates/wmts.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,27 @@
</ows:Operation>
</ows:OperationsMetadata>
<Contents>
{% for layer in layers %}
<Layer>
<ows:Title>{{ title }}</ows:Title>
<ows:Identifier>{{ layer_name }}</ows:Identifier>
<ows:Abstract>{{ title }}</ows:Abstract>
<ows:Title>{{ layer.title }}</ows:Title>
<ows:Identifier>{{ layer.name }}</ows:Identifier>
<ows:Abstract>{{ layer.name }}</ows:Abstract>
<ows:{{ bbox_crs_type }} crs="{{ bbox_crs_uri }}">
<ows:LowerCorner>{{ bounds[0] }} {{ bounds[1] }}</ows:LowerCorner>
<ows:UpperCorner>{{ bounds[2] }} {{ bounds[3] }}</ows:UpperCorner>
<ows:LowerCorner>{{ layer.bounds[0] }} {{ layer.bounds[1] }}</ows:LowerCorner>
<ows:UpperCorner>{{ layer.bounds[2] }} {{ layer.bounds[3] }}</ows:UpperCorner>
</ows:{{ bbox_crs_type }}>
<Style isDefault="true">
<ows:Identifier>default</ows:Identifier>
</Style>
<Format>{{ media_type }}</Format>
<TileMatrixSetLink>
<TileMatrixSet>{{ tms.id }}</TileMatrixSet>
<TileMatrixSet>{{ tileMatrixSetId }}</TileMatrixSet>
</TileMatrixSetLink>
<ResourceURL format="{{ media_type }}" resourceType="tile" template="{{ tiles_endpoint | escape }}" />
<ResourceURL format="{{ media_type }}" resourceType="tile" template="{{ layer.tiles_url }}?{{ layer.query_string | escape }}" />
</Layer>
{% endfor %}
<TileMatrixSet>
<ows:Identifier>{{ tms.id }}</ows:Identifier>
<ows:Identifier>{{ tileMatrixSetId }}</ows:Identifier>
<ows:SupportedCRS>{{ supported_crs }}</ows:SupportedCRS>
{% for item in tileMatrix %}
{{ item | safe }}
Expand Down
2 changes: 1 addition & 1 deletion src/titiler/extensions/titiler/extensions/wms.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,13 +284,13 @@ def register(self, factory: TilerFactory): # noqa: C901
def wms( # noqa: C901
request: Request,
# vendor (titiler) parameters
reader_params=Depends(factory.reader_dependency),
layer_params=Depends(factory.layer_dependency),
dataset_params=Depends(factory.dataset_dependency),
post_process=Depends(factory.process_dependency),
rescale=Depends(RescalingParams),
color_formula=Depends(ColorFormulaParams),
colormap=Depends(factory.colormap_dependency),
reader_params=Depends(factory.reader_dependency),
env=Depends(factory.environment_dependency),
):
"""Return a WMS query for a single COG.
Expand Down
55 changes: 30 additions & 25 deletions src/titiler/mosaic/titiler/mosaic/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
)
from titiler.core.factory import DEFAULT_TEMPLATES, BaseFactory, img_endpoint_params
from titiler.core.models.mapbox import TileJSON
from titiler.core.resources.enums import ImageType, MediaType, OptionalHeader
from titiler.core.resources.enums import ImageType, OptionalHeader
from titiler.core.resources.responses import GeoJSONResponse, JSONResponse, XMLResponse
from titiler.core.utils import render_image
from titiler.mosaic.models.responses import Point
Expand Down Expand Up @@ -316,6 +316,8 @@ def tile(
"Default will be automatically defined if the output image needs a mask (png) or not (jpeg).",
] = None,
src_path=Depends(self.path_dependency),
backend_params=Depends(self.backend_dependency),
reader_params=Depends(self.reader_dependency),
layer_params=Depends(self.layer_dependency),
dataset_params=Depends(self.dataset_dependency),
pixel_selection=Depends(self.pixel_selection_dependency),
Expand All @@ -325,8 +327,6 @@ def tile(
color_formula=Depends(self.color_formula_dependency),
colormap=Depends(self.colormap_dependency),
render_params=Depends(self.render_dependency),
backend_params=Depends(self.backend_dependency),
reader_params=Depends(self.reader_dependency),
env=Depends(self.environment_dependency),
):
"""Create map tile from a COG."""
Expand Down Expand Up @@ -410,7 +410,6 @@ def tilejson(
description="Identifier selecting one of the TileMatrixSetId supported."
),
],
src_path=Depends(self.path_dependency),
tile_format: Annotated[
Optional[ImageType],
Query(
Expand All @@ -431,6 +430,9 @@ def tilejson(
Optional[int],
Query(description="Overwrite default maxzoom."),
] = None,
src_path=Depends(self.path_dependency),
backend_params=Depends(self.backend_dependency),
reader_params=Depends(self.reader_dependency),
layer_params=Depends(self.layer_dependency),
dataset_params=Depends(self.dataset_dependency),
pixel_selection=Depends(self.pixel_selection_dependency),
Expand All @@ -440,8 +442,6 @@ def tilejson(
color_formula=Depends(self.color_formula_dependency),
colormap=Depends(self.colormap_dependency),
render_params=Depends(self.render_dependency),
backend_params=Depends(self.backend_dependency),
reader_params=Depends(self.reader_dependency),
env=Depends(self.environment_dependency),
):
"""Return TileJSON document for a COG."""
Expand Down Expand Up @@ -504,7 +504,6 @@ def map_viewer(
description="Identifier selecting one of the TileMatrixSetId supported."
),
],
src_path=Depends(self.path_dependency),
tile_format: Annotated[
Optional[ImageType],
Query(
Expand All @@ -525,6 +524,9 @@ def map_viewer(
Optional[int],
Query(description="Overwrite default maxzoom."),
] = None,
src_path=Depends(self.path_dependency),
backend_params=Depends(self.backend_dependency),
reader_params=Depends(self.reader_dependency),
layer_params=Depends(self.layer_dependency),
dataset_params=Depends(self.dataset_dependency),
pixel_selection=Depends(self.pixel_selection_dependency),
Expand All @@ -534,8 +536,6 @@ def map_viewer(
color_formula=Depends(self.color_formula_dependency),
colormap=Depends(self.colormap_dependency),
render_params=Depends(self.render_dependency),
backend_params=Depends(self.backend_dependency),
reader_params=Depends(self.reader_dependency),
env=Depends(self.environment_dependency),
):
"""Return TileJSON document for a dataset."""
Expand Down Expand Up @@ -571,7 +571,6 @@ def wmts(
description="Identifier selecting one of the TileMatrixSetId supported."
),
],
src_path=Depends(self.path_dependency),
tile_format: Annotated[
ImageType,
Query(description="Output image type. Default is png."),
Expand All @@ -596,6 +595,9 @@ def wmts(
description="Use EPSG code, not opengis.net, for the ows:SupportedCRS in the TileMatrixSet (set to True to enable ArcMap compatability)"
),
] = False,
src_path=Depends(self.path_dependency),
backend_params=Depends(self.backend_dependency),
reader_params=Depends(self.reader_dependency),
layer_params=Depends(self.layer_dependency),
dataset_params=Depends(self.dataset_dependency),
pixel_selection=Depends(self.pixel_selection_dependency),
Expand All @@ -605,8 +607,6 @@ def wmts(
color_formula=Depends(self.color_formula_dependency),
colormap=Depends(self.colormap_dependency),
render_params=Depends(self.render_dependency),
backend_params=Depends(self.backend_dependency),
reader_params=Depends(self.reader_dependency),
env=Depends(self.environment_dependency),
):
"""OGC WMTS endpoint."""
Expand Down Expand Up @@ -635,8 +635,6 @@ def wmts(
for (key, value) in request.query_params._list
if key.lower() not in qs_key_to_remove
]
if qs:
tiles_url += f"?{urlencode(qs)}"

tms = self.supported_tms.get(tileMatrixSetId)
with rasterio.Env(**env):
Expand Down Expand Up @@ -671,6 +669,18 @@ def wmts(
else:
supported_crs = tms.crs.srs

layers = [
{
"title": src_path
if isinstance(src_path, str)
else "TiTiler Mosaic",
"name": "default",
"tiles_url": tiles_url,
"query_string": urlencode(qs, doseq=True) if qs else None,
"bounds": bounds,
},
]

bbox_crs_type = "WGS84BoundingBox"
bbox_crs_uri = "urn:ogc:def:crs:OGC:2:84"
if tms.rasterio_geographic_crs != WGS84_CRS:
Expand All @@ -681,20 +691,15 @@ def wmts(
request,
name="wmts.xml",
context={
"tiles_endpoint": tiles_url,
"bounds": bounds,
"tileMatrixSetId": tms.id,
"tileMatrix": tileMatrix,
"tms": tms,
"supported_crs": supported_crs,
"bbox_crs_type": bbox_crs_type,
"bbox_crs_uri": bbox_crs_uri,
"title": src_path
if isinstance(src_path, str)
else "TiTiler Mosaic",
"layer_name": "Mosaic",
"layers": layers,
"media_type": tile_format.mediatype,
},
media_type=MediaType.xml.value,
media_type="application/xml",
)

############################################################################
Expand All @@ -714,11 +719,11 @@ def point(
lon: Annotated[float, Path(description="Longitude")],
lat: Annotated[float, Path(description="Latitude")],
src_path=Depends(self.path_dependency),
backend_params=Depends(self.backend_dependency),
reader_params=Depends(self.reader_dependency),
coord_crs=Depends(CoordCRSParams),
layer_params=Depends(self.layer_dependency),
dataset_params=Depends(self.dataset_dependency),
backend_params=Depends(self.backend_dependency),
reader_params=Depends(self.reader_dependency),
env=Depends(self.environment_dependency),
):
"""Get Point value for a Mosaic."""
Expand Down Expand Up @@ -768,9 +773,9 @@ def assets_for_bbox(
maxx: Annotated[float, Path(description="Bounding box max X")],
maxy: Annotated[float, Path(description="Bounding box max Y")],
src_path=Depends(self.path_dependency),
coord_crs=Depends(CoordCRSParams),
backend_params=Depends(self.backend_dependency),
reader_params=Depends(self.reader_dependency),
coord_crs=Depends(CoordCRSParams),
env=Depends(self.environment_dependency),
):
"""Return a list of assets which overlap a bounding box"""
Expand Down
Loading