Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Added

- Added `USE_DATETIME` environment variable to configure datetime search behavior in SFEOS. [#452](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/452)
- GET `/collections` collection search sort extension ex. `/collections?sortby=+id`. [#456](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/456)

### Changed
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ You can customize additional settings in your `.env` file:
| `STAC_ITEM_LIMIT` | Sets the environment variable for result limiting to SFEOS for the number of returned items and STAC collections. | `10` | Optional |
| `STAC_INDEX_ASSETS` | Controls if Assets are indexed when added to Elasticsearch/Opensearch. This allows asset fields to be included in search queries. | `false` | Optional |
| `ENV_MAX_LIMIT` | Configures the environment variable in SFEOS to override the default `MAX_LIMIT`, which controls the limit parameter for returned items and STAC collections. | `10,000` | Optional |
| `USE_DATETIME` | Configures the datetime search behavior in SFEOS. When enabled, searches both datetime field and falls back to start_datetime/end_datetime range for items with null datetime. When disabled, searches only by start_datetime/end_datetime range. | True | Optional |

> [!NOTE]
> The variables `ES_HOST`, `ES_PORT`, `ES_USE_SSL`, `ES_VERIFY_CERTS` and `ES_TIMEOUT` apply to both Elasticsearch and OpenSearch backends, so there is no need to rename the key names to `OS_` even if you're using OpenSearch.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from stac_fastapi.core.base_database_logic import BaseDatabaseLogic
from stac_fastapi.core.serializers import CollectionSerializer, ItemSerializer
from stac_fastapi.core.utilities import bbox2polygon, get_max_limit
from stac_fastapi.core.utilities import bbox2polygon, get_bool_env, get_max_limit
from stac_fastapi.elasticsearch.config import AsyncElasticsearchSettings
from stac_fastapi.elasticsearch.config import (
ElasticsearchSettings as SyncElasticsearchSettings,
Expand Down Expand Up @@ -310,26 +310,99 @@ def apply_datetime_filter(
Returns:
The filtered search object.
"""
# USE_DATETIME env var
# True: Search by datetime, if null search by start/end datetime
# False: Always search only by start/end datetime
USE_DATETIME = get_bool_env("USE_DATETIME", default=True)

datetime_search = return_date(datetime)

if not datetime_search:
return search, datetime_search

if "eq" in datetime_search:
# For exact matches, include:
# 1. Items with matching exact datetime
# 2. Items with datetime:null where the time falls within their range
should = [
Q(
"bool",
filter=[
Q("exists", field="properties.datetime"),
Q("term", **{"properties__datetime": datetime_search["eq"]}),
],
),
Q(
if USE_DATETIME:
if "eq" in datetime_search:
# For exact matches, include:
# 1. Items with matching exact datetime
# 2. Items with datetime:null where the time falls within their range
should = [
Q(
"bool",
filter=[
Q("exists", field="properties.datetime"),
Q(
"term",
**{"properties__datetime": datetime_search["eq"]},
),
],
),
Q(
"bool",
must_not=[Q("exists", field="properties.datetime")],
filter=[
Q("exists", field="properties.start_datetime"),
Q("exists", field="properties.end_datetime"),
Q(
"range",
properties__start_datetime={
"lte": datetime_search["eq"]
},
),
Q(
"range",
properties__end_datetime={"gte": datetime_search["eq"]},
),
],
),
]
else:
# For date ranges, include:
# 1. Items with datetime in the range
# 2. Items with datetime:null that overlap the search range
should = [
Q(
"bool",
filter=[
Q("exists", field="properties.datetime"),
Q(
"range",
properties__datetime={
"gte": datetime_search["gte"],
"lte": datetime_search["lte"],
},
),
],
),
Q(
"bool",
must_not=[Q("exists", field="properties.datetime")],
filter=[
Q("exists", field="properties.start_datetime"),
Q("exists", field="properties.end_datetime"),
Q(
"range",
properties__start_datetime={
"lte": datetime_search["lte"]
},
),
Q(
"range",
properties__end_datetime={
"gte": datetime_search["gte"]
},
),
],
),
]

return (
search.query(Q("bool", should=should, minimum_should_match=1)),
datetime_search,
)
else:
if "eq" in datetime_search:
filter_query = Q(
"bool",
must_not=[Q("exists", field="properties.datetime")],
filter=[
Q("exists", field="properties.start_datetime"),
Q("exists", field="properties.end_datetime"),
Expand All @@ -342,29 +415,10 @@ def apply_datetime_filter(
properties__end_datetime={"gte": datetime_search["eq"]},
),
],
),
]
else:
# For date ranges, include:
# 1. Items with datetime in the range
# 2. Items with datetime:null that overlap the search range
should = [
Q(
"bool",
filter=[
Q("exists", field="properties.datetime"),
Q(
"range",
properties__datetime={
"gte": datetime_search["gte"],
"lte": datetime_search["lte"],
},
),
],
),
Q(
)
else:
filter_query = Q(
"bool",
must_not=[Q("exists", field="properties.datetime")],
filter=[
Q("exists", field="properties.start_datetime"),
Q("exists", field="properties.end_datetime"),
Expand All @@ -377,13 +431,8 @@ def apply_datetime_filter(
properties__end_datetime={"gte": datetime_search["gte"]},
),
],
),
]

return (
search.query(Q("bool", should=should, minimum_should_match=1)),
datetime_search,
)
)
return search.query(filter_query), datetime_search

@staticmethod
def apply_bbox_filter(search: Search, bbox: List):
Expand Down
137 changes: 93 additions & 44 deletions stac_fastapi/opensearch/stac_fastapi/opensearch/database_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from stac_fastapi.core.base_database_logic import BaseDatabaseLogic
from stac_fastapi.core.serializers import CollectionSerializer, ItemSerializer
from stac_fastapi.core.utilities import bbox2polygon, get_max_limit
from stac_fastapi.core.utilities import bbox2polygon, get_bool_env, get_max_limit
from stac_fastapi.extensions.core.transaction.request import (
PartialCollection,
PartialItem,
Expand Down Expand Up @@ -318,21 +318,94 @@ def apply_datetime_filter(
if not datetime_search:
return search, datetime_search

if "eq" in datetime_search:
# For exact matches, include:
# 1. Items with matching exact datetime
# 2. Items with datetime:null where the time falls within their range
should = [
Q(
"bool",
filter=[
Q("exists", field="properties.datetime"),
Q("term", **{"properties__datetime": datetime_search["eq"]}),
],
),
Q(
# USE_DATETIME env var
# True: Search by datetime, if null search by start/end datetime
# False: Always search only by start/end datetime
USE_DATETIME = get_bool_env("USE_DATETIME", default=True)

if USE_DATETIME:
if "eq" in datetime_search:
# For exact matches, include:
# 1. Items with matching exact datetime
# 2. Items with datetime:null where the time falls within their range
should = [
Q(
"bool",
filter=[
Q("exists", field="properties.datetime"),
Q(
"term",
**{"properties__datetime": datetime_search["eq"]},
),
],
),
Q(
"bool",
must_not=[Q("exists", field="properties.datetime")],
filter=[
Q("exists", field="properties.start_datetime"),
Q("exists", field="properties.end_datetime"),
Q(
"range",
properties__start_datetime={
"lte": datetime_search["eq"]
},
),
Q(
"range",
properties__end_datetime={"gte": datetime_search["eq"]},
),
],
),
]
else:
# For date ranges, include:
# 1. Items with datetime in the range
# 2. Items with datetime:null that overlap the search range
should = [
Q(
"bool",
filter=[
Q("exists", field="properties.datetime"),
Q(
"range",
properties__datetime={
"gte": datetime_search["gte"],
"lte": datetime_search["lte"],
},
),
],
),
Q(
"bool",
must_not=[Q("exists", field="properties.datetime")],
filter=[
Q("exists", field="properties.start_datetime"),
Q("exists", field="properties.end_datetime"),
Q(
"range",
properties__start_datetime={
"lte": datetime_search["lte"]
},
),
Q(
"range",
properties__end_datetime={
"gte": datetime_search["gte"]
},
),
],
),
]

return (
search.query(Q("bool", should=should, minimum_should_match=1)),
datetime_search,
)
else:
if "eq" in datetime_search:
filter_query = Q(
"bool",
must_not=[Q("exists", field="properties.datetime")],
filter=[
Q("exists", field="properties.start_datetime"),
Q("exists", field="properties.end_datetime"),
Expand All @@ -345,29 +418,10 @@ def apply_datetime_filter(
properties__end_datetime={"gte": datetime_search["eq"]},
),
],
),
]
else:
# For date ranges, include:
# 1. Items with datetime in the range
# 2. Items with datetime:null that overlap the search range
should = [
Q(
"bool",
filter=[
Q("exists", field="properties.datetime"),
Q(
"range",
properties__datetime={
"gte": datetime_search["gte"],
"lte": datetime_search["lte"],
},
),
],
),
Q(
)
else:
filter_query = Q(
"bool",
must_not=[Q("exists", field="properties.datetime")],
filter=[
Q("exists", field="properties.start_datetime"),
Q("exists", field="properties.end_datetime"),
Expand All @@ -380,13 +434,8 @@ def apply_datetime_filter(
properties__end_datetime={"gte": datetime_search["gte"]},
),
],
),
]

return (
search.query(Q("bool", should=should, minimum_should_match=1)),
datetime_search,
)
)
return search.query(filter_query), datetime_search

@staticmethod
def apply_bbox_filter(search: Search, bbox: List):
Expand Down
Loading