Skip to content

Commit 3462f0a

Browse files
authored
Merge pull request #373 from sentinel-hub/develop
Release v1.7.15
2 parents 28aa4cc + fa09e67 commit 3462f0a

27 files changed

Lines changed: 338 additions & 554 deletions

.github/workflows/ci_action.yml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ jobs:
6060
strategy:
6161
matrix:
6262
python-version:
63-
- "3.8"
6463
- "3.10"
6564
- "3.11"
65+
- "3.12"
6666
include:
6767
# A flag marks whether full or partial tests should be run
6868
# We don't run integration tests on pull requests from outside repos, because they don't have secrets
@@ -90,10 +90,8 @@ jobs:
9090
pip install -e .[DEV,ML]
9191
pip install gdal==$(gdal-config --version)
9292
93-
- name: Set up local cluster # we need to install async-timeout until ray 2.9.0 fixes the issue
94-
run: |
95-
pip install async-timeout
96-
ray start --head
93+
- name: Set up local cluster
94+
run: ray start --head
9795

9896
- name: Run fast tests
9997
if: ${{ !matrix.full_test_suite }}
@@ -127,7 +125,7 @@ jobs:
127125
- name: Mirror + trigger CI
128126
uses: SvanBoxel/gitlab-mirror-and-ci-action@master
129127
with:
130-
args: "https://hello.planet.com/code/eo/code/eo-grow"
128+
args: "https://hello.planet.com/code/solution-enablement/eo/code/eo-grow"
131129
env:
132130
FOLLOW_TAGS: "true"
133131
GITLAB_HOSTNAME: "hello.planet.com/code"

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## [Version 1.7.15] - 2025-09-17
2+
3+
- Switch to Batch Process API V2 in BatchDownloadPipeline
4+
- Remove BatchAreaManager
5+
- Add config validation tests
6+
17
## [Version 1.7.14] - 2024-11-08
28

39
- Improve batch download retries in case of partial downloads.

docs/source/high-level-overview.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,6 @@ The `UtmZoneAreaManager` is probably the most commonly used area manager and mos
108108

109109
For users which have a very specific way of splitting the AOI in mind, we provide the `CustomGridAreaManager`, which accepts a grid file of an already split AOI. The user only needs to provide the grid file folder key and name, along with the `name_column` parameter, which points to the column containing the patch names to be used. The folder key by default points to the `input_data` location, but could be any other location defined by the storage structure. Read the [API docs](reference/eogrow.core.area.custom_grid.html#eogrow.core.area.custom_grid.CustomGridAreaManager.Schema) on the `CustomGridAreaManager` for more info.
110110

111-
#### Batch Area Manager
112-
113-
For users working with [Sentinel Hub Batch API](https://docs.sentinel-hub.com/api/latest/api/batch/), we have prepared the `BatchAreaManager`, which splits the area according to [Sentinel Hub tiling grids](https://docs.sentinel-hub.com/api/latest/api/batch/#tiling-grids). This area manager is meant for larger projects focusing on larger areas.
114-
115-
The interface of the `BatchAreaManager` relies heavily on the predefined configuration options defined for the Batch API, so be sure to provide sensible values for the parameters. For example, the `tiling_grid_id` and `resolution` parameters should correspond to values stated in the [docs](https://docs.sentinel-hub.com/api/latest/api/batch/#tiling-grids).
116-
117-
For existing projects involving Batch API, it is possible to provide the `batch_id` parameter, which will search for existing grids corresponding to the batch request. If the `batch_id` is not provided (this is by default), the `BatchAreaManager` will generate a new batch job with the given parameters. Read the [API docs](reference/eogrow.core.area.batch.html#eogrow.core.area.batch.BatchAreaManager.Schema) on the `BatchAreaManager` for more info.
118-
119111
### Logging Manager
120112

121113
The logging manager ensures that logging handlers are set up and configured correctly. It allows adjusting which packages to log to files, which to stdout, and which to ignore. It is unlikely you'll ever need to access any of it's methods directly. Use the standard `LOGGER = logging.getLogger(__name__)` for logging.

eogrow/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""The main module of the eo-grow package."""
22

3-
__version__ = "1.7.14"
3+
__version__ = "1.7.15"

eogrow/core/area/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
"""A submodule with for implementations of area managers."""
22

3-
from .batch import BatchAreaManager
43
from .custom_grid import CustomGridAreaManager
54
from .utm import UtmZoneAreaManager

eogrow/core/area/base.py

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,12 @@ def get_grid(self, filtered: bool = True) -> dict[CRS, gpd.GeoDataFrame]:
6666
grid_path = fs.path.combine(self.storage.get_cache_folder(), self.get_grid_cache_filename())
6767

6868
if self.storage.filesystem.exists(grid_path):
69-
grid = self._load_grid(grid_path)
69+
LOGGER.info("Loading grid from %s", grid_path)
70+
grid = load_grid(grid_path, self.storage)
7071
else:
7172
grid = self._create_grid()
72-
self._save_grid(grid, grid_path)
73+
LOGGER.info("Saving grid to %s", grid_path)
74+
save_grid(grid, grid_path, self.storage)
7375

7476
if filtered and self.config.patch_names is not None:
7577
folder_path = self.storage.get_folder(self.config.patch_names.input_folder_key)
@@ -97,32 +99,6 @@ def _create_grid(self) -> dict[CRS, gpd.GeoDataFrame]:
9799
definitions. EOPatch names are stored in a column with identifier `self.NAME_COLUMN`.
98100
"""
99101

100-
def _load_grid(self, grid_path: str) -> dict[CRS, gpd.GeoDataFrame]:
101-
"""A method that loads the bounding box grid from the cache folder."""
102-
LOGGER.info("Loading grid from %s", grid_path)
103-
104-
grid = {}
105-
with LocalFile(grid_path, mode="r", filesystem=self.storage.filesystem) as local_file:
106-
for crs_layer in fiona.listlayers(local_file.path):
107-
data = gpd.read_file(local_file.path, layer=crs_layer, engine=self.storage.config.geopandas_backend)
108-
grid[CRS(data.crs)] = data
109-
110-
return grid
111-
112-
def _save_grid(self, grid: dict[CRS, gpd.GeoDataFrame], grid_path: str) -> None:
113-
"""A method that saves the bounding box grid to the cache folder."""
114-
LOGGER.info("Saving grid to %s", grid_path)
115-
116-
with LocalFile(grid_path, mode="w", filesystem=self.storage.filesystem) as local_file:
117-
for crs_grid in grid.values():
118-
crs_grid.to_file(
119-
local_file.path,
120-
driver="GPKG",
121-
encoding="utf-8",
122-
layer=f"Grid EPSG:{crs_grid.crs.to_epsg()}",
123-
engine=self.storage.config.geopandas_backend,
124-
)
125-
126102
@abstractmethod
127103
def get_grid_cache_filename(self) -> str:
128104
"""Provides a filename that is used for caching the grid, including the file extensions (likely .gpkg).
@@ -149,3 +125,27 @@ def get_geometry_from_file(
149125
area_df = gpd.read_file(local_file.path, engine=geopandas_engine)
150126
area_shape = shapely.ops.unary_union(area_df.geometry)
151127
return Geometry(area_shape, CRS(area_df.crs))
128+
129+
130+
def load_grid(grid_path: str, storage: StorageManager) -> dict[CRS, gpd.GeoDataFrame]:
131+
"""A method that loads the bounding box grid from the cache folder."""
132+
grid = {}
133+
with LocalFile(grid_path, mode="r", filesystem=storage.filesystem) as local_file:
134+
for crs_layer in fiona.listlayers(local_file.path):
135+
data = gpd.read_file(local_file.path, layer=crs_layer, engine=storage.config.geopandas_backend)
136+
grid[CRS(data.crs)] = data
137+
138+
return grid
139+
140+
141+
def save_grid(grid: dict[CRS, gpd.GeoDataFrame], grid_path: str, storage: StorageManager) -> None:
142+
"""A method that saves the bounding box grid to the cache folder."""
143+
with LocalFile(grid_path, mode="w", filesystem=storage.filesystem) as local_file:
144+
for crs_grid in grid.values():
145+
crs_grid.to_file(
146+
local_file.path,
147+
driver="GPKG",
148+
encoding="utf-8",
149+
layer=f"Grid EPSG:{crs_grid.crs.to_epsg()}",
150+
engine=storage.config.geopandas_backend,
151+
)

eogrow/core/area/batch.py

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

eogrow/core/area/custom_grid.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from sentinelhub import CRS, Geometry
1313

1414
from ...utils.vector import concat_gdf
15-
from .base import BaseAreaManager
15+
from .base import BaseAreaManager, load_grid
1616

1717
LOGGER = logging.getLogger(__name__)
1818

@@ -35,7 +35,7 @@ class Schema(BaseAreaManager.Schema):
3535

3636
def _create_grid(self) -> dict[CRS, gpd.GeoDataFrame]:
3737
grid_path = fs.path.combine(self.storage.get_folder(self.config.grid_folder_key), self.config.grid_filename)
38-
grid = self._load_grid(grid_path)
38+
grid = load_grid(grid_path, self.storage)
3939

4040
for crs, crs_gdf in grid.items():
4141
# Correct name of eopatch-name-column, drop all non-significant ones

eogrow/core/area/utm.py

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -51,32 +51,14 @@ def _create_grid(self) -> dict[CRS, GeoDataFrame]:
5151
"""Uses UtmZoneSplitter to create a grid"""
5252
area_geometry = self.get_area_geometry()
5353
LOGGER.info("Splitting area geometry into UTM zone grid")
54-
splitter = UtmZoneSplitter(
55-
[area_geometry.geometry],
56-
crs=area_geometry.crs,
57-
bbox_size=(self.config.patch.size_x, self.config.patch.size_y),
58-
offset=(self.config.offset_x, self.config.offset_y),
59-
)
60-
61-
bbox_list, info_list = splitter.get_bbox_list(), splitter.get_info_list()
62-
63-
absolute_buffer = self.config.patch.buffer_x, self.config.patch.buffer_y
64-
if absolute_buffer != (0, 0):
65-
bbox_list = [bbox.buffer(absolute_buffer, relative=False) for bbox in bbox_list]
66-
67-
crs_to_patches = defaultdict(list)
68-
zfill_length = len(str(len(bbox_list) - 1))
69-
for i, (bbox, info) in enumerate(zip(bbox_list, info_list)):
70-
i_x, i_y = info["index_x"], info["index_y"]
71-
name = f"eopatch-id-{i:0{zfill_length}}-col-{i_x}-row-{i_y}"
72-
crs_to_patches[bbox.crs].append((name, bbox.geometry))
7354

74-
grid = {}
75-
for crs, named_bbox_geoms in crs_to_patches.items():
76-
names, geoms = zip(*named_bbox_geoms)
77-
grid[crs] = gpd.GeoDataFrame({self.NAME_COLUMN: names}, geometry=list(geoms), crs=crs.pyproj_crs())
78-
79-
return grid
55+
return create_utm_zone_grid(
56+
area_geometry,
57+
self.NAME_COLUMN,
58+
(self.config.patch.size_x, self.config.patch.size_y),
59+
(self.config.offset_x, self.config.offset_y),
60+
(self.config.patch.buffer_x, self.config.patch.buffer_y),
61+
)
8062

8163
def get_grid_cache_filename(self) -> str:
8264
input_filename = fs.path.basename(self.config.geometry_filename)
@@ -94,3 +76,28 @@ def get_grid_cache_filename(self) -> str:
9476
params = [str(param) for param in raw_params]
9577

9678
return f"{self.__class__.__name__}_{'_'.join(params)}.gpkg"
79+
80+
81+
def create_utm_zone_grid(
82+
geometry: Geometry,
83+
name_column: str,
84+
bbox_size: tuple[int, int],
85+
bbox_offset: tuple[float, float],
86+
bbox_buffer: tuple[float, float],
87+
) -> dict[CRS, GeoDataFrame]:
88+
"""Creates a grid of bounding boxes covering the given area geometry."""
89+
splitter = UtmZoneSplitter([geometry.geometry], crs=geometry.crs, bbox_size=bbox_size, offset=bbox_offset)
90+
bbox_list, info_list = splitter.get_bbox_list(), splitter.get_info_list()
91+
if bbox_buffer != (0, 0):
92+
bbox_list = [bbox.buffer(bbox_buffer, relative=False) for bbox in bbox_list]
93+
94+
tiles_dict: dict[CRS, list[dict]] = defaultdict(list)
95+
zfill_length = len(str(len(bbox_list) - 1))
96+
for i, (bbox, info) in enumerate(zip(bbox_list, info_list)):
97+
i_x, i_y = info["index_x"], info["index_y"]
98+
name = f"eopatch-id-{i:0{zfill_length}}-col-{i_x}-row-{i_y}"
99+
tiles_dict[bbox.crs].append({"id": i, name_column: name, "geometry": bbox.geometry})
100+
101+
return {
102+
crs: gpd.GeoDataFrame(tiles, geometry="geometry", crs=crs.pyproj_crs()) for crs, tiles in tiles_dict.items()
103+
}

eogrow/pipelines/byoc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def get_tile_paths(self) -> dict[str, list[str]]:
106106
"""
107107
tiff_paths = self._get_tiff_paths()
108108

109-
folder_to_filename_map = defaultdict(list)
109+
folder_to_filename_map: dict[str, list[str]] = defaultdict(list)
110110
for folder, filename in map(fs.path.split, tiff_paths):
111111
folder_to_filename_map[folder].append(filename)
112112

0 commit comments

Comments
 (0)