Skip to content

Commit

Permalink
Add enpoint to get tiles filtered by geojson
Browse files Browse the repository at this point in the history
  • Loading branch information
BielStela committed Sep 10, 2024
1 parent 523e30d commit d0e7a69
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 14 deletions.
1 change: 1 addition & 0 deletions api/app/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class Settings(BaseSettings):
auth_token: str
tiff_path: str
grid_tiles_path: str
tile_to_cell_resolution_diff: int = 5


@lru_cache
Expand Down
76 changes: 63 additions & 13 deletions api/app/routers/grid.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import logging
import os
import pathlib
from functools import lru_cache
from typing import Annotated

import h3
import h3ronpy.polars # noqa: F401
import polars as pl
import shapely
from fastapi import APIRouter, Depends, HTTPException, Path, Query
from fastapi.params import Body
from fastapi.responses import ORJSONResponse
from geojson_pydantic import Feature
from h3 import H3CellError
from h3ronpy.polars.vector import geometry_to_cells
from pydantic import ValidationError
from starlette.responses import Response

Expand All @@ -19,17 +25,8 @@
grid_router = APIRouter()


@grid_router.get(
"/tile/{tile_index}",
summary="Get a grid tile",
)
async def grid_tile(
tile_index: Annotated[str, Path(description="The `h3` index of the tile")],
columns: list[str] = Query(
[], description="Colum/s to include in the tile. If empty, it returns only cell indexes."
),
) -> Response:
"""Get a tile of h3 cells with specified data columns"""
def tile_from_fs(columns, tile_index) -> tuple[pl.DataFrame, int]:
"""Get the tile from filesystem filtered by column and the resolution of the tile index"""
try:
z = h3.api.basic_str.h3_get_resolution(tile_index)
except H3CellError:
Expand All @@ -38,10 +35,63 @@ async def grid_tile(
if not os.path.exists(tile_path):
raise HTTPException(status_code=404, detail=f"Tile {tile_path} not found")
try:
tile_file = pl.read_ipc(tile_path, columns=["cell", *columns]).write_ipc(None)
tile = pl.read_ipc(tile_path, columns=["cell", *columns])
except pl.exceptions.ColumnNotFoundError:
raise HTTPException(status_code=400, detail="One or more of the specified columns is not valid") from None
return Response(tile_file.getvalue(), media_type="application/octet-stream")
return tile, z


@grid_router.get(
"/tile/{tile_index}",
summary="Get a grid tile",
)
def get_grid_tile(
tile_index: Annotated[str, Path(description="The `h3` index of the tile")],
columns: list[str] = Query(
[], description="Colum/s to include in the tile. If empty, it returns only cell indexes."
),
) -> Response:
"""Get a tile of h3 cells with specified data columns"""
tile, _ = tile_from_fs(columns, tile_index)
tile_buffer = tile.write_ipc(None)
return Response(tile_buffer.getvalue(), media_type="application/octet-stream")


# @lru_cache
# def cells_in_geojson(geometry, cell_resolution: int) -> pl.Series:
# """Return the cells that fill the polygon area in the geojson"""
# cells = polyfill_geojson(geojson, cell_resolution)
# return pl.Series("shape_cells", cells, dtype=pl.UInt64)


@lru_cache
def cells_in_geojson(geometry, cell_resolution: int) -> pl.Series:
"""Return the cells that fill the polygon area in the geojson"""
cells = geometry_to_cells(geometry, cell_resolution)
return pl.Series("shape_cells", cells, dtype=pl.UInt64)


@grid_router.post("/tile/{tile_index}", summary="Get a grid tile with cells contained inside the GeoJSON")
def post_grid_tile(
tile_index: Annotated[str, Path(description="The `h3` index of the tile")],
geojson: Annotated[Feature, Body(description="GeoJSON Feature.")],
columns: list[str] = Query(
[], description="Colum/s to include in the tile. If empty, it returns only cell indexes."
),
) -> Response:
tile, tile_index_res = tile_from_fs(columns, tile_index)
cell_res = tile_index_res + get_settings().tile_to_cell_resolution_diff
geom = shapely.from_geojson(geojson.model_dump_json())
cells = cells_in_geojson(geom, cell_res)
tile = (
tile.with_columns(pl.col("cell").h3.cells_parse())
.filter(pl.col("cell").is_in(cells))
.with_columns(pl.col("cell").h3.cells_to_string())
)
if tile.is_empty():
raise HTTPException(status_code=404, detail="No data in region")
tile_buffer = tile.write_ipc(None)
return Response(tile_buffer.getvalue(), media_type="application/octet-stream")


@grid_router.get(
Expand Down
1 change: 1 addition & 0 deletions api/requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ h3
pydantic-extra-types
polars
sqlalchemy
h3ronpy
22 changes: 21 additions & 1 deletion api/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ cligj==0.7.2
color-operations==0.1.3
# via rio-tiler
exactextract==0.2.0.dev0
# via -r requirements.in
fastapi==0.110.1
# via titiler-core
# via
# -r requirements.in
# titiler-core
geojson-pydantic==1.0.2
# via titiler-core
greenlet==3.0.3
Expand All @@ -45,6 +48,9 @@ h11==0.14.0
# httpcore
# uvicorn
h3==3.7.7
# via -r requirements.in
h3ronpy==0.21.0
# via -r requirements.in
httpcore==1.0.5
# via httpx
httpx==0.27.0
Expand All @@ -66,13 +72,20 @@ numexpr==2.10.0
numpy==1.26.4
# via
# color-operations
# h3ronpy
# numexpr
# pyarrow
# rasterio
# rio-tiler
# shapely
# snuggs
# titiler-core
orjson==3.10.0
# via -r requirements.in
polars==1.1.0
# via -r requirements.in
pyarrow==17.0.0
# via h3ronpy
pydantic==2.6.4
# via
# fastapi
Expand All @@ -85,7 +98,9 @@ pydantic==2.6.4
pydantic-core==2.16.3
# via pydantic
pydantic-extra-types==2.9.0
# via -r requirements.in
pydantic-settings==2.2.1
# via -r requirements.in
pyparsing==3.1.2
# via snuggs
pyproj==3.6.1
Expand All @@ -104,6 +119,8 @@ rio-tiler==6.4.5
# via titiler-core
setuptools==69.2.0
# via rasterio
shapely==2.0.6
# via h3ronpy
simplejson==3.19.2
# via titiler-core
six==1.16.0
Expand All @@ -115,9 +132,11 @@ sniffio==1.3.1
snuggs==1.4.7
# via rasterio
sqlalchemy==2.0.31
# via -r requirements.in
starlette==0.37.2
# via fastapi
titiler-core==0.18.0
# via -r requirements.in
typing-extensions==4.11.0
# via
# fastapi
Expand All @@ -126,3 +145,4 @@ typing-extensions==4.11.0
# sqlalchemy
# titiler-core
uvicorn==0.29.0
# via -r requirements.in

0 comments on commit d0e7a69

Please sign in to comment.