Skip to content

Commit

Permalink
Merge pull request #43 from NASA-IMPACT/feature/41-planet
Browse files Browse the repository at this point in the history
Add planet mosaic endpoint, close #41
  • Loading branch information
drewbo authored Aug 5, 2020
2 parents 44497c6 + d3529ed commit 2b51a40
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 0 deletions.
2 changes: 2 additions & 0 deletions covid_api/api/api_v1/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
groups,
tiles,
metadata,
planet,
)

api_router = APIRouter()
Expand All @@ -22,3 +23,4 @@
api_router.include_router(sites.router, tags=["sites"])
api_router.include_router(groups.router, tags=["indicator groups"])
api_router.include_router(detections.router, tags=["detections"])
api_router.include_router(planet.router, tags=["planet"])
81 changes: 81 additions & 0 deletions covid_api/api/api_v1/endpoints/planet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""API planet mosaic tiles."""

from typing import Any, Dict

from functools import partial

from fastapi import APIRouter, Depends, Query, Path
from starlette.concurrency import run_in_threadpool

from rio_tiler.utils import render

from covid_api.api import utils
from covid_api.db.memcache import CacheLayer
from covid_api.ressources.enums import ImageType
from covid_api.ressources.common import mimetype
from covid_api.ressources.responses import TileResponse


_render = partial(run_in_threadpool, render)
_tile = partial(run_in_threadpool, utils.planet_mosaic_tile)

router = APIRouter()
responses = {
200: {
"content": {
"image/png": {},
"image/jpg": {},
"image/webp": {},
"image/tiff": {},
"application/x-binary": {},
},
"description": "Return an image.",
}
}
tile_routes_params: Dict[str, Any] = dict(
responses=responses, tags=["planet"], response_class=TileResponse
)


@router.get(r"/planet/{z}/{x}/{y}", **tile_routes_params)
async def tile(
z: int = Path(..., ge=0, le=30, description="Mercator tiles's zoom level"),
x: int = Path(..., description="Mercator tiles's column"),
y: int = Path(..., description="Mercator tiles's row"),
scenes: str = Query(..., description="Comma separated Planets scenes to mosaic."),
cache_client: CacheLayer = Depends(utils.get_cache),
) -> TileResponse:
"""Handle /planet requests."""
timings = []
headers: Dict[str, str] = {}

tile_hash = utils.get_hash(**dict(z=z, x=x, y=y, scenes=scenes, planet=True))

content = None
if cache_client:
try:
content, ext = cache_client.get_image_from_cache(tile_hash)
headers["X-Cache"] = "HIT"
except Exception:
content = None

if not content:
with utils.Timer() as t:
tile, mask = await _tile(scenes, x, y, z)
timings.append(("Read", t.elapsed))

content = await _render(tile, mask)

timings.append(("Format", t.elapsed))

if cache_client and content:
cache_client.set_image_cache(tile_hash, (content, ImageType.png))

if timings:
headers["X-Server-Timings"] = "; ".join(
["{} - {:0.2f}".format(name, time * 1000) for (name, time) in timings]
)

return TileResponse(
content, media_type=mimetype[ImageType.png.value], headers=headers
)
35 changes: 35 additions & 0 deletions covid_api/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@
import time
import json
import hashlib
import math
import random
from io import BytesIO


import numpy as np
from shapely.geometry import shape, box
from rasterstats.io import bounds_window
from rasterio.io import MemoryFile
import requests

from starlette.requests import Request

Expand All @@ -28,6 +34,7 @@

from covid_api.db.memcache import CacheLayer
from covid_api.models.timelapse import Feature
from covid_api.core.config import PLANET_API_KEY


def get_cache(request: Request) -> CacheLayer:
Expand Down Expand Up @@ -679,3 +686,31 @@ def get_custom_cmap(cname) -> Dict:


ColorMapName = Enum("ColorMapNames", [(a, a) for a in COLOR_MAP_NAMES]) # type: ignore


def planet_mosaic_tile(scenes, x, y, z):
"""return a mosaicked tile for a set of planet scenes"""
mosaic_tile = np.zeros((4, 256, 256), dtype=np.uint8)
for scene in scenes.split(","):
api_num = math.floor(random.random() * 3) + 1
url = f"https://tiles{api_num}.planet.com/data/v1/PSScene3Band/{scene}/{z}/{x}/{y}.png?api_key={PLANET_API_KEY}"
r = requests.get(url)
with MemoryFile(BytesIO(r.content)) as memfile:
with memfile.open() as src:
data = src.read()
# any place we don't have data yet, add some
mosaic_tile = np.where(
mosaic_tile[3] == 0, mosaic_tile + data, mosaic_tile
)

# if the tile is full, stop
if np.count_nonzero(mosaic_tile[3]) == mosaic_tile[3].size:
break

# salt the resulting image
salt = np.random.randint(0, 3, (256, 256), dtype=np.uint8)
mosaic_tile[:3] = np.where(
mosaic_tile[:3] < 254, mosaic_tile[:3] + salt, mosaic_tile[:3]
)

return mosaic_tile[:3], mosaic_tile[3]
2 changes: 2 additions & 0 deletions covid_api/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@

INDICATOR_BUCKET = os.environ.get("INDICATOR_BUCKET", "covid-eo-data")
DT_FORMAT = "%Y-%m-%d"

PLANET_API_KEY = os.getenv("PLANET_API_KEY")

0 comments on commit 2b51a40

Please sign in to comment.