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

[WIP] Make the mosaic map endpoint more interactive #89

Closed
wants to merge 7 commits into from
Closed
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,5 @@ cdk.out/
node_modules/

.pgdata

.vscode/
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,11 @@ virtualenv .venv
source .venv/bin/activate

python -m pip install "psycopg[binary,pool]" uvicorn
python -m pip install runtime/eoapi/{SERVICE} # SERVICE should be one of `raster, vector, stac`
python -m pip install -e runtime/eoapi/{SERVICE} # SERVICE should be one of `raster, vector, stac`

export DATABASE_URL=postgresql://username:[email protected]:5439/postgis # Connect to the database of your choice

.venv/bin/uvicorn eoapi.{SERVICE}.app:app --port 8000 --reload
.venv/bin/uvicorn eoapi.${SERVICE}.app:app --port 8000 --reload --reload-include *.html
```

Note: services might have incompatible dependencies which you can resolve by using virtual environement per service
Expand Down
55 changes: 26 additions & 29 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,27 @@ services:
- PORT=8081
- ENVIRONMENT=local
# https://github.com/tiangolo/uvicorn-gunicorn-docker#web_concurrency
- WEB_CONCURRENCY=10
- WEB_CONCURRENCY=4
# https://github.com/tiangolo/uvicorn-gunicorn-docker#workers_per_core
# - WORKERS_PER_CORE=1
# https://github.com/tiangolo/uvicorn-gunicorn-docker#max_workers
# - MAX_WORKERS=10
- MAX_WORKERS=4
# Postgres connection
- POSTGRES_USER=username
- POSTGRES_PASS=password
- POSTGRES_DBNAME=postgis
- POSTGRES_HOST_READER=database
- POSTGRES_HOST_WRITER=database
- POSTGRES_PORT=5432
- POSTGRES_HOST_READER=${POSTGRES_HOST}
- POSTGRES_HOST_WRITER=${POSTGRES_HOST}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASS=${POSTGRES_PASS}
- POSTGRES_DBNAME=${POSTGRES_DBNAME}
- POSTGRES_PORT=${POSTGRES_PORT}
- DB_MIN_CONN_SIZE=1
- DB_MAX_CONN_SIZE=10
# https://github.com/developmentseed/eoAPI/issues/16
# - TITILER_ENDPOINT=raster
- TITILER_ENDPOINT=http://127.0.0.1:8082
depends_on:
- database
# - database
- raster
command:
bash -c "bash /tmp/scripts/wait-for-it.sh -t 120 -h database -p 5432 && /start.sh"
command: bash -c "bash /start.sh"
volumes:
- ./dockerfiles/scripts:/tmp/scripts

Expand All @@ -61,11 +60,11 @@ services:
# https://github.com/tiangolo/uvicorn-gunicorn-docker#max_workers
- MAX_WORKERS=10
# Postgres connection
- POSTGRES_USER=username
- POSTGRES_PASS=password
- POSTGRES_DBNAME=postgis
- POSTGRES_HOST=database
- POSTGRES_PORT=5432
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASS=${POSTGRES_PASS}
- POSTGRES_DBNAME=${POSTGRES_DBNAME}
- POSTGRES_HOST=${POSTGRES_HOST}
- POSTGRES_PORT=${POSTGRES_PORT}
- DB_MIN_CONN_SIZE=1
- DB_MAX_CONN_SIZE=10
# - DB_MAX_QUERIES=10
Expand All @@ -87,10 +86,9 @@ services:
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
# API Config
- EOAPI_RASTER_ENABLE_MOSAIC_SEARCH=TRUE
depends_on:
- database
command:
bash -c "bash /tmp/scripts/wait-for-it.sh -t 120 -h database -p 5432 && /start.sh"
# depends_on:
# - database
command: bash -c "/start.sh"
volumes:
- ./dockerfiles/scripts:/tmp/scripts

Expand All @@ -112,17 +110,16 @@ services:
# https://github.com/tiangolo/uvicorn-gunicorn-docker#max_workers
# - MAX_WORKERS=10
# Postgres connection
- POSTGRES_USER=username
- POSTGRES_PASS=password
- POSTGRES_DBNAME=postgis
- POSTGRES_HOST=database
- POSTGRES_PORT=5432
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASS=${POSTGRES_PASS}
- POSTGRES_DBNAME=${POSTGRES_DBNAME}
- POSTGRES_HOST=${POSTGRES_HOST}
- POSTGRES_PORT=${POSTGRES_PORT}
- DB_MIN_CONN_SIZE=1
- DB_MAX_CONN_SIZE=10
command:
bash -c "bash /tmp/scripts/wait-for-it.sh -t 120 -h database -p 5432 && /start.sh"
depends_on:
- database
command: bash -c "bash /start.sh"
# depends_on:
# - database
volumes:
- ./dockerfiles/scripts:/tmp/scripts

Expand Down
2 changes: 1 addition & 1 deletion infrastructure/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ The example commands here will deploy a CloudFormation stack called `eoAPI-stagi

3. Update settings

Set environment variable or hard code in `infrastructure/aws/.env` file (e.g `CDK_EOAPI_DB_PGSTAC_VERSION=0.7.1`).
Set environment variable or hard code in `infrastructure/aws/.env` file (e.g `CDK_EOAPI_DB_PGSTAC_VERSION=0.7.4`).

**Important**:

Expand Down
22 changes: 17 additions & 5 deletions runtime/eoapi/raster/eoapi/raster/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import pystac
from eoapi.raster import __version__ as eoapi_raster_version
from eoapi.raster.config import ApiSettings
from eoapi.raster.extension import mosaicViewerExtension
from fastapi import Depends, FastAPI, Query
from jinja2 import ChoiceLoader, PackageLoader
from psycopg import OperationalError
from psycopg_pool import PoolTimeout
from starlette.middleware.cors import CORSMiddleware
Expand All @@ -25,10 +27,10 @@
from titiler.pgstac.reader import PgSTACReader

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

logging.getLogger("botocore.credentials").disabled = True
logging.getLogger("botocore.utils").disabled = True
Expand All @@ -37,7 +39,14 @@
settings = ApiSettings()

# 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
templates = Jinja2Templates( # type: ignore
directory="",
loader=ChoiceLoader(
[
PackageLoader(__package__, "templates"),
]
),
)


if settings.debug:
Expand All @@ -49,16 +58,19 @@
add_exception_handlers(app, DEFAULT_STATUS_CODES)
add_exception_handlers(app, MOSAIC_STATUS_CODES)


###############################################################################
# MOSAIC Endpoints
###############################################################################
mosaic = MosaicTilerFactory(
optional_headers=optional_headers,
router_prefix="/mosaic",
add_statistics=True,
# add /map viewer
add_viewer=True,
# add /mosaic/list endpoint
add_mosaic_list=True,
extensions=[
mosaicViewerExtension(),
],
)


Expand Down
83 changes: 83 additions & 0 deletions runtime/eoapi/raster/eoapi/raster/extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""mosaicViewerExtension for /map endpoint"""
from dataclasses import dataclass
from typing import Literal
from urllib.parse import urlencode

from fastapi import Depends, Query
from jinja2 import ChoiceLoader, PackageLoader
from starlette.requests import Request
from starlette.responses import HTMLResponse
from starlette.templating import Jinja2Templates
from titiler.core.factory import FactoryExtension
from titiler.pgstac.factory import BaseTilerFactory

templates = Jinja2Templates( # type: ignore
directory="",
loader=ChoiceLoader(
[
PackageLoader(__package__, "templates"),
]
),
)


@dataclass
class mosaicViewerExtension(FactoryExtension):
"""Add /viewer endpoint to the TilerFactory."""

def register(self, factory: BaseTilerFactory):
"""Register endpoint to the tiler factory."""

@factory.router.get("/{searchid}/viewer", response_class=HTMLResponse)
@factory.router.get(
"/{searchid}/{TileMatrixSetId}/viewer", response_class=HTMLResponse
)
def map_viewer(
request: Request,
searchid=Depends(factory.path_dependency),
TileMatrixSetId: Literal["WebMercatorQuad"] = Query(
"WebMercatorQuad",
description="TileMatrixSet Name (default: 'WebMercatorQuad')",
), # noqa
):
"""Return a simple map viewer."""
# tilejson url for adding tile layer to slippy map
tilejson_url = factory.url_for(
request, "tilejson", searchid=searchid, TileMatrixSetId=TileMatrixSetId
)

# assets_url with placeholders for bounding box coordinates
# TODO: can we recover the bounding box here instead of requesting it in javascript?
assets_url = factory.url_for(
request,
"assets_for_tile",
searchid=searchid,
TileMatrixSetId=TileMatrixSetId,
# parameters to be populated in mosaic-viewer.html
x="${tileCoordinates.x}",
y="${tileCoordinates.y}",
z="${tileCoordinates.z}",
)

info_url = factory.url_for(
request,
"info_search",
searchid=searchid,
)

if request.query_params._list:
tilejson_url += f"?{urlencode(request.query_params._list)}"

tms = factory.supported_tms.get(TileMatrixSetId)
return templates.TemplateResponse(
name="mosaic-viewer.html",
context={
"request": request,
"tilejson_url": tilejson_url,
"assets_url": assets_url,
"info_url": info_url,
"tms": tms,
"resolutions": [tms._resolution(matrix) for matrix in tms],
},
media_type="text/html",
)
Loading