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

Endpoints to get a list of assets which overlap a point, box, or quadkey #351

Merged
merged 10 commits into from
Aug 6, 2021
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
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
}
)
Copy link
Member

Choose a reason for hiding this comment

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

❤️


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