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//