Skip to content

Commit

Permalink
Endpoints to get a list of assets which overlap a point, box, or quad…
Browse files Browse the repository at this point in the history
…key (#351)

* mercantile

* test fixes

* changelog, docs and mercantile req

* changelog, docs and mercantile req

* remove autoformatted changes

* remove autoformatted changes

* intersecting not overlapping

* Update CHANGES.md

* Update CHANGES.md

* Update src/titiler/mosaic/tests/test_factory.py

Co-authored-by: Vincent Sarago <vincent.sarago@gmail.com>
  • Loading branch information
mackdelany and vincentsarago authored Aug 6, 2021
1 parent 81aa0c5 commit c4e3200
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 5 deletions.
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Release Notes

## 0.3.5 (TBD)

### titiler.mosaic

* add `/{quadkey}/assets`, `/{lon},{lat}/assets`, `/{minx},{miny},{maxx},{maxy}/assets` GET endpoints to return a list of assets that intersect a given geometry (author @mackdelany, https://github.com/developmentseed/titiler/pull/351)

## 0.3.4 (2021-08-02)

### titiler.core
Expand Down
3 changes: 3 additions & 0 deletions docs/endpoints/mosaic.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ Read Mosaic Info/Metadata and create Web map Tiles from a multiple COG. The `mos
| `GET` | `/mosaicjson/[{TileMatrixSetId}]/tilejson.json` | JSON | return a Mapbox TileJSON document
| `GET` | `/mosaicjson/{TileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities
| `GET` | `/mosaicjson/point/{lon},{lat}` | JSON | return pixel value from a MosaicJSON dataset
| `GET` | `/mosaicjson/{quadkey}/assets` | JSON | return list of assets intersecting a quadkey
| `GET` | `/mosaicjson/{lon},{lat}/assets` | JSON | return list of assets intersecting a point
| `GET` | `/mosaicjson/{minx},{miny},{maxx},{maxy}/assets` | JSON | return list of assets intersecting a bounding box

## Description

Expand Down
5 changes: 1 addition & 4 deletions src/titiler/mosaic/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
with open("README.md") as f:
long_description = f.read()

inst_reqs = [
"titiler.core",
"cogeo-mosaic>=3.0,<3.1",
]
inst_reqs = ["titiler.core", "cogeo-mosaic>=3.0,<3.1", "mercantile"]
extra_reqs = {
"test": ["pytest", "pytest-cov", "pytest-asyncio", "requests"],
}
Expand Down
31 changes: 30 additions & 1 deletion src/titiler/mosaic/tests/test_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def test_MosaicTilerFactory():
optional_headers=[OptionalHeader.server_timing, OptionalHeader.x_assets],
router_prefix="mosaic",
)
assert len(mosaic.router.routes) == 19
assert len(mosaic.router.routes) == 22
assert mosaic.tms_dependency == WebMercatorTMSParams

app = FastAPI()
Expand Down Expand Up @@ -142,3 +142,32 @@ def test_MosaicTilerFactory():
"/mosaic/validate", json=MosaicJSON.from_urls(assets).dict(),
)
assert response.status_code == 200

response = client.get("/mosaic/0302302/assets", params={"url": mosaic_file},)
assert response.status_code == 200
assert all(
filepath.split("/")[-1] in ["cog1.tif"] for filepath in response.json()
)

response = client.get("/mosaic/-71,46/assets", params={"url": mosaic_file})
assert response.status_code == 200
assert all(
filepath.split("/")[-1] in ["cog1.tif", "cog2.tif"]
for filepath in response.json()
)

response = client.get(
"/mosaic/-75.9375,43.06888777416962,-73.125,45.089035564831015/assets",
params={"url": mosaic_file},
)
assert response.status_code == 200
assert all(
filepath.split("/")[-1] in ["cog1.tif", "cog2.tif"]
for filepath in response.json()
)

response = client.get(
"/mosaic/10,10,11,11/assets", params={"url": mosaic_file},
)
assert response.status_code == 200
assert response.json() == []
64 changes: 64 additions & 0 deletions src/titiler/mosaic/titiler/mosaic/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Callable, Dict, Optional, Type
from urllib.parse import urlencode, urlparse

import mercantile
import rasterio
from cogeo_mosaic.backends import BaseBackend, MosaicBackend
from cogeo_mosaic.models import Info as mosaicInfo
Expand Down Expand Up @@ -64,6 +65,7 @@ def register_routes(self):
self.wmts()
self.point()
self.validate()
self.assets()

############################################################################
# /read
Expand Down Expand Up @@ -484,3 +486,65 @@ def validate(self):
def validate(body: MosaicJSON):
"""Validate a MosaicJSON"""
return True

def assets(self):
"""Register /assets endpoint."""

@self.router.get(
r"/{minx},{miny},{maxx},{maxy}/assets",
responses={200: {"description": "Return list of COGs in bounding box"}},
)
def bbox(
src_path=Depends(self.path_dependency),
minx: float = Query(None, description="Left side of bounding box"),
miny: float = Query(None, description="Bottom of bounding box"),
maxx: float = Query(None, description="Right side of bounding box"),
maxy: float = Query(None, description="Top of bounding box"),
):
"""Return a list of assets which overlap a bounding box"""
with self.reader(src_path, **self.backend_options) as mosaic:
tl_tile = mercantile.tile(minx, maxy, mosaic.minzoom)
br_tile = mercantile.tile(maxx, miny, mosaic.minzoom)
tiles = [
(x, y, mosaic.minzoom)
for x in range(tl_tile.x, br_tile.x + 1)
for y in range(tl_tile.y, br_tile.y + 1)
]
assets = list(
{
asset
for asset_list in [mosaic.assets_for_tile(*t) for t in tiles]
for asset in asset_list
}
)

return assets

@self.router.get(
r"/{lng},{lat}/assets",
responses={200: {"description": "Return list of COGs"}},
)
def lonlat(
src_path=Depends(self.path_dependency),
lng: float = Query(None, description="Longitude"),
lat: float = Query(None, description="Latitude"),
):
"""Return a list of assets which overlap a point"""
with self.reader(src_path, **self.backend_options) as mosaic:
assets = mosaic.assets_for_point(lng, lat)

return assets

@self.router.get(
r"/{quadkey}/assets",
responses={200: {"description": "Return list of COGs"}},
)
def quadkey(
src_path=Depends(self.path_dependency),
quadkey: str = Query(None, description="Quadkey to return COGS for."),
):
"""Return a list of assets which overlap a given quadkey"""
with self.reader(src_path, **self.backend_options) as mosaic:
assets = mosaic.assets_for_tile(*mercantile.quadkey_to_tile(quadkey))

return assets

0 comments on commit c4e3200

Please sign in to comment.