Skip to content

Commit b2acc5f

Browse files
authored
replace cql2 package with pygeofilter (#22)
* use pytest_lazy_fixtures for lazyfixtures; remove the cql2 and replace it with pygeofilter * remove pytest<8 restriction and see what it do * try using pytest-lazy-fixtures instead of pytest-lazy-fixture * refractor utm_search and test it more, add more test_search as well as test_array for color.py * utilize parser of cql from the deps remove custom code * update depndabot * gitignore sorted * move all package settings into pyproject.toml
1 parent 07dc37d commit b2acc5f

File tree

20 files changed

+817
-276
lines changed

20 files changed

+817
-276
lines changed

.coveragerc

Lines changed: 0 additions & 5 deletions
This file was deleted.

.github/dependabot.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@ updates:
1515
- dependency-name: "croniter"
1616
- dependency-name: "lxml"
1717
- dependency-name: "mapchete[complete]"
18-
- dependency-name: "opencv-python"
18+
- dependency-name: "opencv-python-headless"
1919
- dependency-name: "Pillow"
2020
- dependency-name: "pydantic"
21+
- dependency-name: "pygeofilter"
2122
- dependency-name: "pystac[urllib3]"
2223
- dependency-name: "pystac-client"
24+
- dependency-name: "pytest"
25+
- dependency-name: "pytest-coverage"
26+
- dependency-name: "pytest-lazy-fixtures"
2327
- dependency-name: "retry"
2428
- dependency-name: "rtree"
2529
- dependency-name: "scipy"

.gitignore

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
.eggs
2-
*.egg-info
3-
*.pyc
1+
build/
42
.cache
5-
htmlcov
63
.coverage
74
.coverage.*
8-
build/
95
dist/
10-
.pytest*
6+
*.egg-info
7+
.eggs
8+
examples/sentinel-2*/
119
*.gfs
12-
.vscode/
10+
htmlcov
11+
.mypy_cache
12+
*.pyc
1313
__pycache__
14-
examples/sentinel-2*/
14+
.pytest*
15+
.ruff_cache
16+
.vscode/

mapchete_eo/search/base.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
import json
33
import logging
44
from abc import ABC, abstractmethod
5-
from typing import Any, Callable, Dict, Generator, List, Optional, Type, Union
5+
from typing import Any, Callable, Dict, Generator, List, Optional, Set, Type, Union
66

7-
from cql2 import Expr
7+
from pygeofilter.parsers.ecql import parse as parse_ecql
8+
from pygeofilter.backends.native.evaluate import NativeEvaluator
89
from pydantic import BaseModel
910
from mapchete.path import MPath, MPathLike
1011
from mapchete.types import Bounds
@@ -17,6 +18,8 @@
1718
from shapely.geometry.base import BaseGeometry
1819

1920
from mapchete_eo.io.assets import get_assets, get_metadata_assets
21+
from mapchete_eo.product import blacklist_products
22+
from mapchete_eo.settings import mapchete_eo_settings
2023
from mapchete_eo.types import TimeRange
2124

2225
logger = logging.getLogger(__name__)
@@ -53,6 +56,11 @@ class CollectionSearcher(ABC):
5356
config_cls: Type[BaseModel]
5457
collection: str
5558
stac_item_modifiers: Optional[List[Callable[[Item], Item]]] = None
59+
blacklist: Set[str] = (
60+
blacklist_products(mapchete_eo_settings.blacklist)
61+
if mapchete_eo_settings.blacklist
62+
else set()
63+
)
5664

5765
def __init__(
5866
self,
@@ -70,17 +78,21 @@ def client(self) -> CollectionClient: ...
7078
@cached_property
7179
def eo_bands(self) -> List[str]: ...
7280

73-
@abstractmethod
81+
@property
82+
def config(self) -> BaseModel:
83+
return self.config_cls()
84+
7485
@cached_property
75-
def id(self) -> str: ...
86+
def id(self) -> str:
87+
return self.client.id
7688

77-
@abstractmethod
7889
@cached_property
79-
def description(self) -> str: ...
90+
def description(self) -> str:
91+
return self.client.description
8092

81-
@abstractmethod
8293
@cached_property
83-
def stac_extensions(self) -> List[str]: ...
94+
def stac_extensions(self) -> List[str]:
95+
return self.client.stac_extensions
8496

8597
@abstractmethod
8698
def search(
@@ -240,9 +252,12 @@ def filter_items(
240252
and passed down to the individual search approaches via said config and this Function.
241253
"""
242254
if query:
243-
expr = Expr(query)
255+
ast = parse_ecql(query)
256+
evaluator = NativeEvaluator(use_getattr=False)
257+
filter_func = evaluator.evaluate(ast)
244258
for item in items:
245-
if expr.matches(item.properties):
259+
# pystac items store metadata in 'properties'
260+
if filter_func(item.properties):
246261
yield item
247262
else:
248263
yield from items

mapchete_eo/search/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,21 @@ def deprecate_max_cloud_cover(cls, values: Dict[str, Any]) -> Dict[str, Any]:
5050
path=MPath(
5151
"https://sentinel-s2-l2a-stac.s3.amazonaws.com/sentinel-s2-l2a.json"
5252
),
53+
endpoint="s3://sentinel-s2-l2a-stac",
5354
),
5455
S2_L1C=dict(
5556
id="sentinel-s2-l1c",
5657
path=MPath(
5758
"https://sentinel-s2-l1c-stac.s3.amazonaws.com/sentinel-s2-l1c.json"
5859
),
60+
endpoint="s3://sentinel-s2-l1c-stac",
5961
),
6062
S1_GRD=dict(
6163
id="sentinel-s1-l1c",
6264
path=MPath(
6365
"https://sentinel-s1-l1c-stac.s3.amazonaws.com/sentinel-s1-l1c.json"
6466
),
67+
endpoint="s3://sentinel-s1-l1c-stac",
6568
),
6669
)
6770
search_index: Optional[MPathLike] = None

mapchete_eo/search/stac_search.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import logging
44
from datetime import datetime
55
from functools import cached_property
6-
from typing import Any, Dict, Generator, Iterator, List, Optional, Set, Union
6+
from typing import Any, Dict, Generator, Iterator, List, Optional, Union
77

88
from mapchete import Timer
99
from mapchete.tile import BufferedTilePyramid
@@ -13,22 +13,15 @@
1313
from shapely.geometry import shape, box
1414
from shapely.geometry.base import BaseGeometry
1515

16-
from mapchete_eo.product import blacklist_products
1716
from mapchete_eo.search.base import CollectionSearcher, StaticCollectionWriterMixin
1817
from mapchete_eo.search.config import StacSearchConfig, patch_invalid_assets
19-
from mapchete_eo.settings import mapchete_eo_settings
2018
from mapchete_eo.types import TimeRange
2119

2220
logger = logging.getLogger(__name__)
2321

2422

2523
class STACSearchCollection(StaticCollectionWriterMixin, CollectionSearcher):
2624
collection: str
27-
blacklist: Set[str] = (
28-
blacklist_products(mapchete_eo_settings.blacklist)
29-
if mapchete_eo_settings.blacklist
30-
else set()
31-
)
3225
config_cls = StacSearchConfig
3326

3427
@cached_property
@@ -45,18 +38,6 @@ def eo_bands(self) -> List[str]:
4538
logger.debug("cannot find eo:bands definition from collections")
4639
return []
4740

48-
@cached_property
49-
def id(self) -> str:
50-
return self.client.id
51-
52-
@cached_property
53-
def description(self) -> str:
54-
return self.client.description
55-
56-
@cached_property
57-
def stac_extensions(self) -> List[str]:
58-
return self.client.stac_extensions
59-
6041
def search(
6142
self,
6243
time: Optional[Union[TimeRange, List[TimeRange]]] = None,

mapchete_eo/search/stac_static.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,6 @@ def eo_bands(self) -> List[str]:
6565
logger.debug("cannot find eo:bands definition")
6666
return []
6767

68-
@cached_property
69-
def id(self) -> str:
70-
return self.client.id
71-
72-
@cached_property
73-
def description(self) -> str:
74-
return self.client.description
75-
76-
@cached_property
77-
def stac_extensions(self) -> List[str]:
78-
return self.client.stac_extensions
79-
8068
def search(
8169
self,
8270
time: Optional[Union[TimeRange, List[TimeRange]]] = None,

mapchete_eo/search/utm_search.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,51 @@
11
import datetime
22
from functools import cached_property
33
import logging
4-
from typing import Any, Dict, Generator, List, Optional, Set, Union
4+
from typing import Any, Dict, Generator, List, Optional, Union
55

66
from mapchete.io.vector import fiona_open
77
from mapchete.path import MPath, MPathLike
88
from mapchete.types import Bounds, BoundsLike
99
from pystac.collection import Collection
1010
from pystac.item import Item
11+
from pystac_client import CollectionClient
1112
from shapely.errors import GEOSException
1213
from shapely.geometry import shape
1314
from shapely.geometry.base import BaseGeometry
1415

1516
from mapchete_eo.exceptions import ItemGeometryError
16-
from mapchete_eo.product import blacklist_products
1717
from mapchete_eo.search.base import (
1818
CollectionSearcher,
1919
StaticCollectionWriterMixin,
2020
filter_items,
2121
)
2222
from mapchete_eo.search.config import UTMSearchConfig
2323
from mapchete_eo.search.s2_mgrs import S2Tile, s2_tiles_from_bounds
24-
from mapchete_eo.settings import mapchete_eo_settings
2524
from mapchete_eo.time import day_range, to_datetime
2625
from mapchete_eo.types import TimeRange
2726

2827
logger = logging.getLogger(__name__)
2928

3029

3130
class UTMSearchCatalog(StaticCollectionWriterMixin, CollectionSearcher):
32-
endpoint: str
33-
id: str
34-
day_subdir_schema: str
35-
stac_json_endswith: str
36-
description: str
37-
stac_extensions: List[str]
38-
blacklist: Set[str] = (
39-
blacklist_products(mapchete_eo_settings.blacklist)
40-
if mapchete_eo_settings.blacklist
41-
else set()
42-
)
4331
config_cls = UTMSearchConfig
4432

33+
@cached_property
34+
def endpoint(self) -> Optional[str]:
35+
for collection_properties in self.config.sinergise_aws_collections.values():
36+
if collection_properties["id"] == self.collection.split("/")[-1].replace(
37+
".json", ""
38+
):
39+
return collection_properties.get("endpoint")
40+
return None
41+
42+
day_subdir_schema: str = "{year}/{month:02d}/{day:02d}"
43+
stac_json_endswith: str = "T{tile_id}.json"
44+
45+
@cached_property
46+
def client(self) -> CollectionClient:
47+
return next(self.get_collections())
48+
4549
@cached_property
4650
def eo_bands(self) -> List[str]: # pragma: no cover
4751
for (
@@ -85,8 +89,9 @@ def _raw_search(
8589
time: Optional[Union[TimeRange, List[TimeRange]]] = None,
8690
bounds: Optional[Bounds] = None,
8791
area: Optional[BaseGeometry] = None,
88-
config: UTMSearchConfig = UTMSearchConfig(),
92+
config: Optional[UTMSearchConfig] = None,
8993
) -> Generator[Item, None, None]:
94+
config = config or UTMSearchConfig()
9095
if time is None:
9196
raise ValueError("time must be given")
9297
if area is not None and area.is_empty:

pyproject.toml

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ classifiers = [
2424
]
2525
dependencies = [
2626
"click",
27-
"cql2",
27+
"pygeofilter",
2828
"croniter",
2929
"lxml",
3030
"mapchete[complete]>=2025.10.0",
@@ -36,7 +36,6 @@ dependencies = [
3636
"retry",
3737
"rtree",
3838
"scipy",
39-
"retry",
4039
"tqdm",
4140
"xarray",
4241
]
@@ -47,9 +46,9 @@ docs = [
4746
"sphinx-rtd-theme"
4847
]
4948
test = [
50-
"pytest<8",
49+
"pytest",
5150
"pytest-coverage",
52-
"pytest-lazy-fixture"
51+
"pytest-lazy-fixtures"
5352
]
5453

5554
[project.entry-points."mapchete.cli.commands"]
@@ -76,4 +75,16 @@ include = [
7675
]
7776

7877
[tool.mypy]
79-
ignore_missing_imports = true
78+
ignore_missing_imports = true
79+
80+
[tool.coverage.run]
81+
branch = true
82+
source = ["mapchete_eo"]
83+
84+
[tool.pytest.ini_options]
85+
addopts = "--durations 20 --verbose --nf --cov=mapchete_eo --cov-report=term-missing:skip-covered"
86+
testpaths = ["tests"]
87+
markers = [
88+
"remote: marks tests which require acces to remote resources (deselect with '-m \"not remote\"')",
89+
"use_cdse_test_env: enables CDSE S3 environment access",
90+
]

pytest.ini

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)