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

Fixed searchid bug, added unit tests, improved main readme #236

Merged
merged 25 commits into from
Aug 30, 2024
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
14 changes: 7 additions & 7 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ jobs:
- uses: actions/checkout@v3

- name: Set Azurite Default Key
run: |
echo "AZURITE_ACCOUNT_KEY=${{ secrets.AZURITE_ACCOUNT_KEY }}" >> $GITHUB_ENV
echo "Using Azurite default key: $AZURITE_ACCOUNT_KEY"
run: echo "AZURITE_ACCOUNT_KEY=$(curl https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite | grep "Account key:" | cut -b 24-111)" >> $GITHUB_ENV
srijan55 marked this conversation as resolved.
Show resolved Hide resolved

- name: Verify Azurite Key was retrieved correctly
if: "!startsWith(env.AZURITE_ACCOUNT_KEY, 'Eby8')"
run: echo Failed to find key at learn.microsoft.com && exit 1

- name: Run cibuild
run: ./scripts/cibuild
Expand All @@ -29,9 +31,7 @@ jobs:
cache: "pip"

- name: Set Azurite Default Key
run: |
echo "AZURITE_ACCOUNT_KEY=${{ secrets.AZURITE_ACCOUNT_KEY }}" >> $GITHUB_ENV
echo "Using Azurite default key: $AZURITE_ACCOUNT_KEY"
run: echo "AZURITE_ACCOUNT_KEY=$(curl https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite | grep "Account key:" | cut -b 24-111)" >> $GITHUB_ENV

- name: Validate
- name: API Validator
run: ./scripts/validate
25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ This repository contains two components of the Planetary Computer APIs: the STAC
The `pcstac` project provides a STAC API which indexes Microsoft's publicly available [geospatial data](https://planetarycomputer.microsoft.com/catalog) and an API for searching through this large collection.
The `pctiler` provides visualization and data access capabilities for the data in the Planetary Computer.

## Azure Functions
## Azure Functions (a.k.a. Funcs)

This repository also contains Azure Functions that provide additional endpoints for working with Planetary Computer data and metadata. This includes Function endpoints for generating images and animations based on STAC searches, using the tiler to render mosaiced data from Collections
This repository also contains Azure Functions that provide additional endpoints for working with Planetary Computer data and metadata. This includes Function endpoints for generating images and animations based on STAC searches, using the tiler to render mosaiced data from Collections.

## Collection configuration

Expand Down Expand Up @@ -61,26 +61,27 @@ This project uses a variation on [scripts to rule them all](https://github.com/g

#### Environment setup and building images

Before setting up the local environment, ensure that you have set the AZURITE_ACCOUNT_KEY environment variable.
The account key can be found in the [Azurite GitHub repository](https://github.com/Azure/Azurite?tab=readme-ov-file#usage-with-azure-storage-sdks-or-tools)
Before setting up the local environment, you must set the AZURITE_ACCOUNT_KEY environment variable to the Azurite default
account key, a string that can be [found here](https://github.com/Azure/Azurite?tab=readme-ov-file#usage-with-azure-storage-sdks-or-tools).

For example, you can set the environment variable in your terminal with:

```console
> export AZURITE_ACCOUNT_KEY=<azurite_account_key>
export AZURITE_ACCOUNT_KEY=<azurite_account_key>
```

To set up a local environment, use
To set up a local environment, use:

```console
> ./scripts/setup
./scripts/setup
```

This will build containers, apply database migrations, and load the development data.

After migrations and development database loading are in place, you can rebuild the docker images with
After migrations and development database loading are in place, you can rebuild the docker images with:

```console
> ./scripts/update
./scripts/update
```

`pip` dependencies in `setup.py` are collected and installed through requirements files.
Expand All @@ -99,11 +100,13 @@ az login
To run the servers, use

```console
> ./scripts/server
./scripts/server
```

This will bring up the development database, STAC API, Tiler, Azure Functions, and other services.

To test the tiler, try going to <http://localhost:8080/data/mosaic/info?collection=naip>.

#### Testing and and formatting

To run tests, use
Expand Down Expand Up @@ -148,4 +151,4 @@ See the [Helm chart repository](https://microsoft.github.io/planetary-computer-a

### Functions

See the [Function package repository](https://microsoft.github.io/planetary-computer-apis) published to GitHub pages for the published Azure Functions.
See the [Function package repository](https://microsoft.github.io/planetary-computer-apis) published to GitHub pages for the published Azure Functions.
6 changes: 4 additions & 2 deletions pctiler/pctiler/endpoints/pg_mosaic.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from titiler.core.factory import img_endpoint_params
from titiler.core.resources.enums import ImageType
from titiler.pgstac.dependencies import SearchIdParams, TmsTileParams
from titiler.pgstac.extensions import searchInfoExtension
from titiler.pgstac.factory import MosaicTilerFactory

from pccommon.config import get_collection_config
Expand All @@ -22,7 +23,6 @@

@dataclass
class AssetsBidxExprParams(dependencies.AssetsBidxExprParams):

collection: str = Query(None, description="STAC Collection ID")


Expand All @@ -45,9 +45,11 @@ def __init__(self, request: Request):
colormap_dependency=PCColorMapParams,
layer_dependency=AssetsBidxExprParams,
reader_dependency=ReaderParams,
router_prefix=get_settings().mosaic_endpoint_prefix + "/{search_id}",
router_prefix=get_settings().mosaic_endpoint_prefix
+ "/{search_id}", # reverts /searches back to /mosaic
backend_dependency=BackendParams,
add_statistics=False,
extensions=[searchInfoExtension()],
777arc marked this conversation as resolved.
Show resolved Hide resolved
)


Expand Down
85 changes: 79 additions & 6 deletions pctiler/tests/endpoints/test_pg_mosaic.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,47 @@

@pytest.fixture
async def register_search(client: AsyncClient) -> REGISTER_TYPE:

cql = {
"filter-lang": "cql2-json",
"filter": {
"op": "and",
"args": [{"op": "=", "args": [{"property": "collection"}, "naip"]}],
},
}
expected_content_hash = "8b989f86a149628eabfde894fb965982"
response = await client.post("/mosaic/register", json=cql)
expected_content_hash = response.json()["searchid"]
resp = response.json()

return (expected_content_hash, resp)


async def test_register_search_with_geom(client: AsyncClient) -> None:
geom = {
"type": "Polygon",
"coordinates": [
[
[-79.09062791441062, 43.08554661560049],
[-79.0629876337021, 43.08554661560049],
[-79.0629876337021, 43.067969831431895],
[-79.09062791441062, 43.067969831431895],
[-79.09062791441062, 43.08554661560049],
]
],
}
cql = {
"filter-lang": "cql2-json",
"filter": {
"op": "and",
"args": [
{"op": "=", "args": [{"property": "collection"}, "naip"]},
{"op": "s_intersects", "args": [{"property": "geometry"}, geom]},
],
},
}
response = await client.post("/mosaic/register", json=cql)
assert response.status_code == 200
assert response.json()["searchid"] == "2607eab51afd5d626da8d50d9df3bbf0"


@pytest.mark.asyncio
async def test_mosaic_info(client: AsyncClient) -> None:
response = await client.get("/mosaic/info?collection=naip")
Expand All @@ -36,18 +62,20 @@ async def test_mosaic_info(client: AsyncClient) -> None:

@pytest.mark.asyncio
async def test_register(client: AsyncClient, register_search: REGISTER_TYPE) -> None:

expected_content_hash, register_resp = register_search

# Test that `searchid` which has been removed in titiler remains in pctiler,
# and that the search hash remains consistent
assert register_resp["searchid"] == expected_content_hash
# Test that the links have had the {tileMatrixSetId} template string removed
assert len(register_resp["links"]) == 2
assert len(register_resp["links"]) == 3
assert register_resp["links"][0]["href"].endswith(
f"/mosaic/{expected_content_hash}/info"
) # gets added by searchInfoExtension
assert register_resp["links"][1]["href"].endswith(
f"/mosaic/{expected_content_hash}/tilejson.json"
)
assert register_resp["links"][1]["href"].endswith(
assert register_resp["links"][2]["href"].endswith(
f"/mosaic/{expected_content_hash}/WMTSCapabilities.xml"
)

Expand Down Expand Up @@ -101,6 +129,51 @@ async def test_mosaic_tile_routes(
assert response.status_code == 200


async def test_info_path_with_searchid(
client: AsyncClient, register_search: REGISTER_TYPE
) -> None:
# route source code is in titiler/mosaic/titiler/mosaic/factory.py#L157
# the searchId functionality is added by titiler-pgstac
route = "mosaic/{searchId}/info"
expected_content_hash, _ = register_search
formatted_route = route.format(searchId=expected_content_hash)
url = (
f"/{formatted_route}?asset_bidx=image%7C1%2C2%2C3&assets=image&collection=naip"
)
response = await client.get(url)
assert response.status_code == 200


async def test_info_path_with_bad_searchid(client: AsyncClient) -> None:
route = "mosaic/{searchId}/info"

# does not match the one we registered in the fixture
expected_content_hash = "9b989f86a149628eabfde894fb965982"

formatted_route = route.format(searchId=expected_content_hash)
url = (
f"/{formatted_route}?asset_bidx=image%7C1%2C2%2C3&assets=image&collection=naip"
)
response = await client.get(url)
assert response.status_code == 404


async def test_bad_searchid(client: AsyncClient) -> None:
route = "mosaic/tiles/{searchId}/{z}/{x}/{y}"

# does not match the one we registered in the fixture
expected_content_hash = "9b989f86a149628eabfde894fb965982"

formatted_route = route.format(
searchId=expected_content_hash, z=16, x=17218, y=26838
)
url = (
f"/{formatted_route}?asset_bidx=image%7C1%2C2%2C3&assets=image&collection=naip"
)
response = await client.get(url)
assert response.status_code == 404


@pytest.mark.asyncio
@pytest.mark.parametrize(
"route",
Expand Down
19 changes: 19 additions & 0 deletions scripts/format-local
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
isort --overwrite-in-place pccommon
isort --overwrite-in-place pccommon/pccommon
isort --overwrite-in-place pccommon/tests
isort --overwrite-in-place pcfuncs
isort --overwrite-in-place scripts/bin
isort --overwrite-in-place pctiler/pctiler
isort --overwrite-in-place pctiler/tests
isort --overwrite-in-place pcstac/pcstac
isort --overwrite-in-place pcstac/tests
black pccommon/pccommon
black pccommon/tests
black pcfuncs
black scripts/bin
black pcstac/pcstac
black pcstac/tests
black pccommon
black pctiler/pctiler
black pctiler/tests
Loading