diff --git a/.github/workflows/tests/test_raster.py b/.github/workflows/tests/test_raster.py index 17556ca..0bd21cd 100644 --- a/.github/workflows/tests/test_raster.py +++ b/.github/workflows/tests/test_raster.py @@ -16,21 +16,23 @@ def test_raster_api(): def test_mosaic_api(): """test mosaic.""" query = {"collections": ["noaa-emergency-response"], "filter-lang": "cql-json"} - resp = httpx.post(f"{raster_endpoint}/mosaic/register", json=query) + resp = httpx.post(f"{raster_endpoint}/searches/register", json=query) assert resp.headers["content-type"] == "application/json" assert resp.status_code == 200 - assert resp.json()["searchid"] + assert resp.json()["id"] assert resp.json()["links"] - searchid = resp.json()["searchid"] + searchid = resp.json()["id"] - resp = httpx.get(f"{raster_endpoint}/mosaic/{searchid}/-85.6358,36.1624/assets") + resp = httpx.get(f"{raster_endpoint}/searches/{searchid}/-85.6358,36.1624/assets") assert resp.status_code == 200 assert len(resp.json()) == 1 assert list(resp.json()[0]) == ["id", "bbox", "assets", "collection"] assert resp.json()[0]["id"] == "20200307aC0853900w361030" - resp = httpx.get(f"{raster_endpoint}/mosaic/{searchid}/tiles/15/8589/12849/assets") + resp = httpx.get( + f"{raster_endpoint}/searches/{searchid}/tiles/15/8589/12849/assets" + ) assert resp.status_code == 200 assert len(resp.json()) == 1 assert list(resp.json()[0]) == ["id", "bbox", "assets", "collection"] @@ -38,7 +40,37 @@ def test_mosaic_api(): z, x, y = 15, 8589, 12849 resp = httpx.get( - f"{raster_endpoint}/mosaic/{searchid}/tiles/{z}/{x}/{y}", + f"{raster_endpoint}/searches/{searchid}/tiles/{z}/{x}/{y}", + params={"assets": "cog"}, + headers={"Accept-Encoding": "br, gzip"}, + timeout=10.0, + ) + assert resp.status_code == 200 + assert resp.headers["content-type"] == "image/jpeg" + assert "content-encoding" not in resp.headers + + +def test_mosaic_collection_api(): + """test mosaic collection.""" + resp = httpx.get( + f"{raster_endpoint}/collections/noaa-emergency-response/-85.6358,36.1624/assets" + ) + assert resp.status_code == 200 + assert len(resp.json()) == 1 + assert list(resp.json()[0]) == ["id", "bbox", "assets", "collection"] + assert resp.json()[0]["id"] == "20200307aC0853900w361030" + + resp = httpx.get( + f"{raster_endpoint}/collections/noaa-emergency-response/tiles/15/8589/12849/assets" + ) + assert resp.status_code == 200 + assert len(resp.json()) == 1 + assert list(resp.json()[0]) == ["id", "bbox", "assets", "collection"] + assert resp.json()[0]["id"] == "20200307aC0853900w361030" + + z, x, y = 15, 8589, 12849 + resp = httpx.get( + f"{raster_endpoint}/collections/noaa-emergency-response/tiles/{z}/{x}/{y}", params={"assets": "cog"}, headers={"Accept-Encoding": "br, gzip"}, timeout=10.0, @@ -102,11 +134,11 @@ def test_mosaic_search(): }, ] for search in searches: - resp = httpx.post(f"{raster_endpoint}/mosaic/register", json=search) + resp = httpx.post(f"{raster_endpoint}/searches/register", json=search) assert resp.status_code == 200 - assert resp.json()["searchid"] + assert resp.json()["id"] - resp = httpx.get(f"{raster_endpoint}/mosaic/list") + resp = httpx.get(f"{raster_endpoint}/searches/list") assert resp.headers["content-type"] == "application/json" assert resp.status_code == 200 assert ( @@ -122,9 +154,11 @@ def test_mosaic_search(): assert len(links) == 2 assert links[0]["rel"] == "self" assert links[1]["rel"] == "next" - assert links[1]["href"] == f"{raster_endpoint}/mosaic/list?limit=10&offset=10" + assert links[1]["href"] == f"{raster_endpoint}/searches/list?limit=10&offset=10" - resp = httpx.get(f"{raster_endpoint}/mosaic/list", params={"limit": 1, "offset": 1}) + resp = httpx.get( + f"{raster_endpoint}/searches/list", params={"limit": 1, "offset": 1} + ) assert resp.status_code == 200 assert resp.json()["context"]["matched"] > 10 assert resp.json()["context"]["limit"] == 1 @@ -133,33 +167,33 @@ def test_mosaic_search(): links = resp.json()["links"] assert len(links) == 3 assert links[0]["rel"] == "self" - assert links[0]["href"] == f"{raster_endpoint}/mosaic/list?limit=1&offset=1" + assert links[0]["href"] == f"{raster_endpoint}/searches/list?limit=1&offset=1" assert links[1]["rel"] == "next" - assert links[1]["href"] == f"{raster_endpoint}/mosaic/list?limit=1&offset=2" + assert links[1]["href"] == f"{raster_endpoint}/searches/list?limit=1&offset=2" assert links[2]["rel"] == "prev" - assert links[2]["href"] == f"{raster_endpoint}/mosaic/list?limit=1&offset=0" + assert links[2]["href"] == f"{raster_endpoint}/searches/list?limit=1&offset=0" # Filter on mosaic metadata - resp = httpx.get(f"{raster_endpoint}/mosaic/list", params={"owner": "vincent"}) + resp = httpx.get(f"{raster_endpoint}/searches/list", params={"owner": "vincent"}) assert resp.status_code == 200 assert resp.json()["context"]["matched"] == 7 assert resp.json()["context"]["limit"] == 10 assert resp.json()["context"]["returned"] == 7 # sortBy - resp = httpx.get(f"{raster_endpoint}/mosaic/list", params={"sortby": "lastused"}) + resp = httpx.get(f"{raster_endpoint}/searches/list", params={"sortby": "lastused"}) assert resp.status_code == 200 - resp = httpx.get(f"{raster_endpoint}/mosaic/list", params={"sortby": "usecount"}) + resp = httpx.get(f"{raster_endpoint}/searches/list", params={"sortby": "usecount"}) assert resp.status_code == 200 - resp = httpx.get(f"{raster_endpoint}/mosaic/list", params={"sortby": "-owner"}) + resp = httpx.get(f"{raster_endpoint}/searches/list", params={"sortby": "-owner"}) assert resp.status_code == 200 assert ( "owner" not in resp.json()["searches"][0]["search"]["metadata"] ) # some mosaic don't have owners - resp = httpx.get(f"{raster_endpoint}/mosaic/list", params={"sortby": "owner"}) + resp = httpx.get(f"{raster_endpoint}/searches/list", params={"sortby": "owner"}) assert resp.status_code == 200 assert "owner" in resp.json()["searches"][0]["search"]["metadata"] @@ -201,3 +235,15 @@ def test_collections(): collections = resp.json() assert len(collections) == 1 assert collections[0]["id"] == "noaa-emergency-response" + + +def test_cog_endpoints(): + """test /cog endpoints""" + resp = httpx.get( + f"{raster_endpoint}/cog/info", + params={ + "url": "https://noaa-eri-pds.s3.us-east-1.amazonaws.com/2020_Nashville_Tornado/20200307a_RGB/20200307aC0854500w361030n.tif", + }, + ) + assert resp.status_code == 200 + assert resp.headers["content-type"] == "application/json" diff --git a/.gitignore b/.gitignore index 3652e61..e54f557 100644 --- a/.gitignore +++ b/.gitignore @@ -113,3 +113,8 @@ node_modules/ # Demo files to ignore demo/cmip6/CMIP6_daily_*stac_items.ndjson + + +# browser compiled code and default config +infrastructure/aws/stac-browser +infrastructure/aws/browser_config.js \ No newline at end of file diff --git a/README.md b/README.md index bdc5df2..371b62a 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ - **OGC Features and Vector Tiles** API built on top of [https://github.com/developmentseed/tipg](https://github.com/developmentseed/tipg) +- **A STAC Catalog browsing UI** based on the radiant earth browser : [https://github.com/radiantearth/stac-browser](https://github.com/radiantearth/stac-browser) + --- ## 🌍 eoAPI: An Open-Source Community Project @@ -57,22 +59,19 @@ Then you can start exploring your dataset with: - the STAC Metadata service [http://localhost:8081](http://localhost:8081) - the Raster service [http://localhost:8082](http://localhost:8082) + - the browser UI [http://localhost:8085](http://localhost:8085) If you've added a vector dataset to the `public` schema in the Postgres database, they will be available through the **Vector** service at [http://localhost:8083](http://localhost:8083). -## Deployment +## Deployment with standard runtimes This repository has current runtimes that are consistently updated with new functionality. -The services can be deployed locally via docker with `docker compose up`. - -Two Infrastructure as Code (IaC) repositories are available: -- [eoapi-cdk](https://github.com/developmentseed/eoapi-cdk): A set of AWS CDK constructs to deploy eoAPI services -- [eoapi-k8s](https://github.com/developmentseed/eoapi-k8s): IaC and Helm charts for deploying eoAPI services on AWS and GCP +### Local deployment -Finally, [eoapi-template](https://github.com/developmentseed/eoapi-template) is an AWS CDK app that shows how to configure the eoapi-cdk constructs. +The services can be deployed altogether locally with `docker compose up`. -Alternatively, you may install the libraries locally: +Alternatively, you may install the libraries and launch the applications manually :
@@ -109,7 +108,19 @@ Note: python libraries might have incompatible dependencies, which you can resol
-## Custom runtimes +### Deployment on the cloud + +#### Kubernetes + +[eoapi-k8s](https://github.com/developmentseed/eoapi-k8s) contains IaC and Helm charts for deploying eoAPI services on AWS and GCP. + +#### AWS CDK + +[eoapi-cdk](https://github.com/developmentseed/eoapi-cdk) defines a set of AWS CDK constructs that can be used to deploy eoAPI services on AWS. This repository itself makes use of these in `infrastructure/aws`. An official example usage of these constructs can be found at [eoapi-template](https://github.com/developmentseed/eoapi-template). + + + +## Deployment with custom runtimes The eoAPI repository hosts customized versions of each base service which can work in parallel or in combination with each other. diff --git a/docker-compose.custom.yml b/docker-compose.custom.yml index 8e257f1..09880d1 100644 --- a/docker-compose.custom.yml +++ b/docker-compose.custom.yml @@ -1,6 +1,18 @@ version: '3' services: + stac-browser: + profiles: + - gunicorn + build: + context: dockerfiles + dockerfile: Dockerfile.browser + ports: + - "${MY_DOCKER_IP:-127.0.0.1}:8085:8085" + depends_on: + - stac + - database + - raster stac: container_name: eoapi.stac profiles: @@ -34,6 +46,9 @@ services: # https://github.com/developmentseed/eoAPI/issues/16 # - TITILER_ENDPOINT=raster - TITILER_ENDPOINT=http://127.0.0.1:8082 + # PgSTAC extensions + # - EOAPI_STAC_EXTENSIONS=["filter", "query", "sort", "fields", "pagination", "context", "transaction"] + # - EOAPI_STAC_CORS_METHODS='GET,POST,PUT,OPTIONS' depends_on: - database - raster @@ -159,6 +174,9 @@ services: # https://github.com/developmentseed/eoAPI/issues/16 # - TITILER_ENDPOINT=raster - TITILER_ENDPOINT=http://127.0.0.1:8082 + # PgSTAC extensions + # - EOAPI_STAC_EXTENSIONS=["filter", "query", "sort", "fields", "pagination", "context", "transaction"] + # - EOAPI_STAC_CORS_METHODS='GET,POST,PUT,OPTIONS' depends_on: - database - raster-uvicorn diff --git a/docker-compose.yml b/docker-compose.yml index b6f54c4..f43fec0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,19 @@ version: '3' services: + + # change to official image when available https://github.com/radiantearth/stac-browser/pull/386 + stac-browser: + build: + context: dockerfiles + dockerfile: Dockerfile.browser + ports: + - "${MY_DOCKER_IP:-127.0.0.1}:8085:8085" + depends_on: + - stac-fastapi + - titiler-pgstac + - database + stac-fastapi: # Note: # the official ghcr.io/stac-utils/stac-fastapi-pgstac image uses python 3.8 and uvicorn @@ -43,7 +56,7 @@ services: # At the time of writing, rasterio and psycopg wheels are not available for arm64 arch # so we force the image to be built with linux/amd64 platform: linux/amd64 - image: ghcr.io/stac-utils/titiler-pgstac:0.8.0 + image: ghcr.io/stac-utils/titiler-pgstac:1.0.0a3 ports: - "${MY_DOCKER_IP:-127.0.0.1}:8082:8082" environment: @@ -89,7 +102,7 @@ services: - ./dockerfiles/scripts:/tmp/scripts tipg: - image: ghcr.io/developmentseed/tipg:0.4.4 + image: ghcr.io/developmentseed/tipg:0.5.0 ports: - "${MY_DOCKER_IP:-127.0.0.1}:8083:8083" environment: diff --git a/dockerfiles/Dockerfile.browser b/dockerfiles/Dockerfile.browser new file mode 100644 index 0000000..9848025 --- /dev/null +++ b/dockerfiles/Dockerfile.browser @@ -0,0 +1,34 @@ +# Copyright Radiant Earth Foundation + +FROM node:lts-alpine3.18 AS build-step +ARG DYNAMIC_CONFIG=true + +WORKDIR /app + +RUN apk add --no-cache git +RUN git clone https://github.com/radiantearth/stac-browser.git . +# remove the default config.js +RUN rm config.js +RUN npm install +# replace the default config.js with our config file +COPY ./browser_config.js ./config.js +RUN \[ "${DYNAMIC_CONFIG}" == "true" \] && sed -i 's//