Skip to content

Commit

Permalink
implement geolocation filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
mmaelicke committed Jan 30, 2025
1 parent 82d1e63 commit 87c2d35
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 15 deletions.
6 changes: 3 additions & 3 deletions metacatalog_api/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ def register_token(user: models.Author | None = None, valid_until: datetime | No
print(f"Generated a new token. Save this token in a save space as it will not be displayed again:\n{new_key}\n")


def entries(offset: int = 0, limit: int = None, ids: int | List[int] = None, full_text: bool = True, search: str = None, variable: str | int = None, title: str = None) -> list[models.Metadata]:
def entries(offset: int = 0, limit: int = None, ids: int | List[int] = None, full_text: bool = True, search: str = None, variable: str | int = None, title: str = None, geolocation: str = None) -> list[models.Metadata]:
# check if we filter or search
with connect() as session:
if search is not None:
search_results = db.search_entries(session, search, limit=limit, offset=offset, variable=variable, full_text=full_text)
search_results = db.search_entries(session, search, limit=limit, offset=offset, variable=variable, full_text=full_text, geolocation=geolocation)

if len(search_results) == 0:
return []
Expand All @@ -86,7 +86,7 @@ def entries(offset: int = 0, limit: int = None, ids: int | List[int] = None, ful
elif ids is not None:
results = db.get_entries_by_id(session, ids, limit=limit, offset=offset)
else:
results = db.get_entries(session, limit=limit, offset=offset, variable=variable, title=title)
results = db.get_entries(session, limit=limit, offset=offset, variable=variable, title=title, geolocation=geolocation)

return results

Expand Down
56 changes: 48 additions & 8 deletions metacatalog_api/db.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
from typing import List
from pathlib import Path
import warnings

from sqlmodel import Session, text
from sqlmodel import Session, text, func
from sqlmodel import select, exists, col, or_, and_
from psycopg2.errors import UndefinedTable
from sqlalchemy.exc import ProgrammingError
from pydantic_geojson import FeatureCollectionModel
from pydantic import BaseModel

from metacatalog_api import models
from sqlmodel import select, exists, col
from metacatalog_api.extra import geocoder

DB_VERSION = 4
SQL_DIR = Path(__file__).parent / "sql"
Expand Down Expand Up @@ -71,9 +73,37 @@ def check_installed(session: Session, schema: str = 'public') -> bool:
return False


def get_entries(session: Session, limit: int = None, offset: int = None, variable: str | int = None, title: str = None) -> list[models.Metadata]:
# build the base query
sql = select(models.EntryTable)
def get_entries(session: Session, limit: int = None, offset: int = None, variable: str | int = None, title: str = None, geolocation: str = None) -> list[models.Metadata]:
if geolocation is not None:
try:
geolocation = geocoder.geolocation_to_postgres_wkt(geolocation=geolocation, tolerance=0.5)
except Exception as e:
warnings.warn(f"Could not resolve geolocation to WKT, continue without geolocation filter: {geolocation}.")
geolocation = None

if geolocation is not None:
geolocation = func.st_setSRID(func.st_geomfromtext(geolocation), 4326)

# build the base query
sql = (
select(models.EntryTable)
.join(models.DatasourceTable, isouter=True)
.join(models.SpatialScaleTable, isouter=True)
.where(
or_(
and_(
col(models.EntryTable.location).is_not(None),
func.st_within(models.EntryTable.location, geolocation)
),
and_(
col(models.SpatialScaleTable.extent).is_not(None),
func.st_intersects(models.SpatialScaleTable.extent, geolocation)
)
)
)
)
else:
sql = select(models.EntryTable)

# handle variable filter
if isinstance(variable, int):
Expand Down Expand Up @@ -145,20 +175,30 @@ class SearchResult(BaseModel):
weight: int


def search_entries(session: Session, search: str, full_text: bool = True, limit: int = None, offset: int = None, variable: int | str = None) -> list[SearchResult]:
def search_entries(session: Session, search: str, full_text: bool = True, limit: int = None, offset: int = None, variable: int | str = None, geolocation: str = None) -> list[SearchResult]:
# build the limit and offset
lim = f" LIMIT {limit} " if limit is not None else ""
off = f" OFFSET {offset} " if offset is not None else ""
filt = ""
params = {"lim": lim, "off": off}
# handle variable filter
if isinstance(variable, int):
filt = " WHERE entries.variable_id = :variable "
filt = " AND entries.variable_id = :variable "
params["variable"] = variable
elif isinstance(variable, str):
variable = get_variables(session, name=variable)
filt = " WHERE entries.variable_id in (:variabe) "
filt = " AND entries.variable_id in (:variable) "
params["variable"] = [v.id for v in variable]

if geolocation is not None:
try:
geolocation = geocoder.geolocation_to_postgres_wkt(geolocation=geolocation, tolerance=0.5)
except Exception as e:
warnings.warn(f"Could not resolve geolocation to WKT, continue without geolocation filter: {geolocation}.")
geolocation = None
if geolocation is None:
geolocation = "POLYGON ((-90 -180, 90 -180, 90 180, -90 180, -90 -180))"
params["geolocation"] = geolocation

# handle full text search
if full_text:
Expand Down
Empty file.
4 changes: 2 additions & 2 deletions metacatalog_api/router/api/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@

@read_router.get('/entries')
@read_router.get('/entries.json')
def get_entries(offset: int = 0, limit: int = 100, search: str = None, full_text: bool = True, title: str = None, description: str = None, variable: str = None):
def get_entries(offset: int = 0, limit: int = 100, search: str = None, full_text: bool = True, title: str = None, description: str = None, variable: str = None, geolocation: str = None):

# sanitize the search
if search is not None and search.strip() == '':
search = None

# call the function
entries = core.entries(offset, limit, search=search, full_text=full_text, title=title, variable=variable)
entries = core.entries(offset, limit, search=search, full_text=full_text, title=title, variable=variable, geolocation=geolocation)

return entries

Expand Down
10 changes: 9 additions & 1 deletion metacatalog_api/sql/ftl_search_entries.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
WITH filtered_entries AS (
SELECT * FROM entries
SELECT entries.* FROM entries
LEFT JOIN datasources ON datasources.id=entries.datasource_id
LEFT JOIN spatial_scales ON spatial_scales.id=datasources.id
WHERE
(
(spatial_scales.extent is not null and st_intersects(spatial_scales.extent, st_setSRID(st_geomfromtext(:geolocation), 4326)))
OR
(entries.location is not null and st_within(entries.location, st_setSRID(st_geomfromtext(:geolocation), 4326)))
)
{filter}
),
weights AS (
Expand Down
10 changes: 9 additions & 1 deletion metacatalog_api/sql/search_entries.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
WITH filtered_entries AS (
SELECT * FROM entries
SELECT entries.* FROM entries
LEFT JOIN datasources ON datasources.id=entries.datasource_id
LEFT JOIN spatial_scales ON spatial_scales.id=datasources.id
WHERE
(
(spatial_scales.extent is not null and st_intersects(spatial_scales.extent, st_setSRID(st_geomfromtext(:geolocation), 4326)))
OR
(entries.location is not null and st_within(entries.location, st_setSRID(st_geomfromtext(:geolocation), 4326)))
)
{filter}
),
weights AS (
Expand Down

0 comments on commit 87c2d35

Please sign in to comment.