Skip to content

Commit bdde0b5

Browse files
committed
Hook up the complex query geodini endpoint
1 parent acd04de commit bdde0b5

File tree

2 files changed

+24
-12
lines changed

2 files changed

+24
-12
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ dependencies = [
1717
"pydantic",
1818
"openai",
1919
"pydantic-ai",
20+
"shapely",
2021
]
2122

2223
[tool.setuptools]

stac_search/agents/items_search.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
import requests
99
from pydantic_ai import Agent, RunContext
1010
from pystac_client import Client
11-
from pydantic import BaseModel
11+
from pydantic import BaseModel, ConfigDict
12+
from shapely.geometry import shape, mapping
1213

1314
from stac_search.agents.collections_search import (
1415
collection_search,
@@ -182,8 +183,7 @@ class FilterExpr(BaseModel):
182183
op: str
183184
args: List[FilterArg]
184185

185-
class Config:
186-
arbitrary_types_allowed = True
186+
model_config = ConfigDict(arbitrary_types_allowed=True, extra="ignore")
187187

188188

189189
FilterExpr.update_forward_refs()
@@ -225,6 +225,13 @@ class Config:
225225
"eo:cloud_cover" - Cloud cover percentage on a scale of 0 to 100. Cloudless imagery means cloud cover less than 10.
226226
227227
Return None if the query doesn't require a CQL2 filter or if you can't determine the filter or if the property is not supported.
228+
229+
Some examples of CQL2 filters:
230+
- "cloudless imagery" -> {"op": "lte", "args": [{"property": "eo:cloud_cover"}, 10]}
231+
- "imagery from 2023 to 2024 over France with less than 10 percent cloud cover" -> {"op": "and", "args": [{"op": "lte", "args": [{"property": "eo:cloud_cover"}, 10]}]}
232+
- "imagery over Brazil with cloud cover between 10 and 20" -> {"op": "and", "args": [{"op": "gte", "args": [{"property": "eo:cloud_cover"}, 10]}, {"op": "lte", "args": [{"property": "eo:cloud_cover"}, 20]}]}
233+
234+
Return the filter dictionary itself as a JSON object. No additional keys or values; just the filter dictionary.
228235
""",
229236
)
230237

@@ -235,20 +242,17 @@ async def construct_cql2_filter(ctx: RunContext[Context]) -> FilterExpr | None:
235242

236243

237244
def get_polygon_from_geodini(location: str):
238-
geodini_api = f"{GEODINI_API}/search"
245+
geodini_api = f"{GEODINI_API}/search_complex"
239246
response = requests.get(
240247
geodini_api,
241-
params={
242-
"query": location,
243-
"limit": 10,
244-
"include_geometry": True,
245-
"smart_parse": True,
246-
"rank": True,
247-
},
248+
params={"query": location},
248249
)
249-
result = response.json().get("most_probable", None)
250+
result = response.json().get("result", None)
250251
if result:
251252
polygon = result.get("geometry")
253+
logger.info(
254+
f"Found polygon for {location}: {pformat(mapping(shape(polygon).simplify(tolerance=0.1)))}"
255+
)
252256
return polygon
253257
return None
254258

@@ -307,6 +311,13 @@ async def item_search(ctx: Context) -> ItemSearchResult:
307311
if polygon:
308312
logger.info(f"Found polygon for {results.data.location}")
309313
params["intersects"] = polygon
314+
else:
315+
return ItemSearchResult(
316+
items=None,
317+
search_params=params,
318+
aoi=None,
319+
explanation=f"No polygon found for {results.data.location}",
320+
)
310321

311322
if ctx.return_search_params_only:
312323
logger.info("Returning STAC query parameters only")

0 commit comments

Comments
 (0)