Skip to content

Commit

Permalink
Merge branch 'master' into issue421_specifying-temporal-extent-with-s…
Browse files Browse the repository at this point in the history
…ingle-string
  • Loading branch information
JohanKJSchreurs committed Aug 11, 2023
2 parents 54d72ac + 5bb0da8 commit 543149e
Show file tree
Hide file tree
Showing 13 changed files with 767 additions and 379 deletions.
22 changes: 18 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,42 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).



## [Unreleased]

### Added

### Changed

### Removed

### Fixed



- Processes that take a CRS as argument now try harder to convert your input into a proper EPSG code, to avoid unexpected results when an invalid argument gets sent to the backend.
## [0.22.0] - 2023-08-09

### Added

- Processes that take a CRS as argument now try harder to normalize your input to
a CRS representation that aligns with the openEO API (using `pyproj` library when available)
([#259](https://github.com/Open-EO/openeo-python-client/issues/259))
- Initial `load_geojson` support with `Connection.load_geojson()` ([#424](https://github.com/Open-EO/openeo-python-client/issues/424))
- Initial `load_url` (for vector cubes) support with `Connection.load_url()` ([#424](https://github.com/Open-EO/openeo-python-client/issues/424))
- Add `VectorCube.apply_dimension()` ([Open-EO/openeo-python-driver#197](https://github.com/Open-EO/openeo-python-driver/issues/197))
- Support lambda based property filtering in `Connection.load_stac()` ([#425](https://github.com/Open-EO/openeo-python-client/issues/425))

- `VectorCube`: initial support for `filter_bands`, `filter_bbox`, `filter_labels` and `filter_vector` ([#459](https://github.com/Open-EO/openeo-python-client/issues/459))

### Changed

- `Connection` based requests: always use finite timeouts by default (20 minutes in general, 30 minutes for synchronous execute requests)
([#454](https://github.com/Open-EO/openeo-python-client/issues/454))

### Removed

### Fixed

- Fix: MultibackendJobManager should stop when finished, also when job finishes with error ([#452](https://github.com/Open-EO/openeo-python-client/issues/432))


## [0.21.1] - 2023-07-19

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pythonPipeline {
package_name = 'openeo'
wipeout_workspace = true
python_version = ["3.8"]
extras_require = 'dev'
extras_require = 'tests'
upload_dev_wheels = false
wheel_repo = 'python-openeo'
wheel_repo_dev = 'python-openeo'
Expand Down
2 changes: 1 addition & 1 deletion openeo/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.22.0a1"
__version__ = "0.23.0a1"
35 changes: 31 additions & 4 deletions openeo/rest/_testing.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import re
from typing import Union, Optional

from openeo import Connection
from openeo import Connection, DataCube
from openeo.rest.vectorcube import VectorCube


class DummyBackend:
Expand Down Expand Up @@ -91,8 +93,33 @@ def get_batch_pg(self) -> dict:
assert len(self.batch_jobs) == 1
return self.batch_jobs[max(self.batch_jobs.keys())]["pg"]

def get_pg(self) -> dict:
"""Get one and only batch process graph (sync or batch)"""
def get_pg(self, process_id: Optional[str] = None) -> dict:
"""
Get one and only batch process graph (sync or batch)
:param process_id: just return single process graph node with this process_id
:return: process graph (flat graph representation) or process graph node
"""
pgs = self.sync_requests + [b["pg"] for b in self.batch_jobs.values()]
assert len(pgs) == 1
return pgs[0]
pg = pgs[0]
if process_id:
# Just return single node (by process_id)
found = [node for node in pg.values() if node.get("process_id") == process_id]
if len(found) != 1:
raise RuntimeError(
f"Expected single process graph node with process_id {process_id!r}, but found {len(found)}: {found}"
)
return found[0]
return pg

def execute(self, cube: Union[DataCube, VectorCube], process_id: Optional[str] = None) -> dict:
"""
Execute given cube (synchronously) and return observed process graph (or subset thereof).
:param cube: cube to execute on dummy back-end
:param process_id: just return single process graph node with this process_id
:return: process graph (flat graph representation) or process graph node
"""
cube.execute()
return self.get_pg(process_id=process_id)
2 changes: 1 addition & 1 deletion openeo/rest/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1185,7 +1185,7 @@ def load_stac(
spatial_extent: Optional[Dict[str, float]] = None,
temporal_extent: Optional[List[Union[str, datetime.datetime, datetime.date]]] = None,
bands: Optional[List[str]] = None,
properties: Optional[dict] = None,
properties: Optional[Dict[str, Union[str, PGNode, Callable]]] = None,
) -> DataCube:
"""
Loads data from a static STAC catalog or a STAC API Collection and returns the data as a processable :py:class:`DataCube`.
Expand Down
6 changes: 3 additions & 3 deletions openeo/rest/datacube.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from openeo.rest.service import Service
from openeo.rest.udp import RESTUserDefinedProcess
from openeo.rest.vectorcube import VectorCube
from openeo.util import get_temporal_extent, dict_no_none, rfc3339, guess_format, crs_to_epsg_code
from openeo.util import get_temporal_extent, dict_no_none, rfc3339, guess_format, normalize_crs

if typing.TYPE_CHECKING:
# Imports for type checking only (circular import issue at runtime).
Expand Down Expand Up @@ -332,7 +332,7 @@ def filter_bbox(
" Use keyword arguments or tuple/list argument instead.")
west, east, north, south = args[:4]
if len(args) > 4:
crs = crs_to_epsg_code(args[4])
crs = normalize_crs(args[4])
elif len(args) == 1 and (isinstance(args[0], (list, tuple)) and len(args[0]) == 4
or isinstance(args[0], (dict, shapely.geometry.base.BaseGeometry, Parameter))):
bbox = args[0]
Expand Down Expand Up @@ -834,7 +834,7 @@ def _get_geometry_argument(
# TODO: don't warn when the crs is Lon-Lat like EPSG:4326?
warnings.warn(f"Geometry with non-Lon-Lat CRS {crs!r} is only supported by specific back-ends.")
# TODO #204 alternative for non-standard CRS in GeoJSON object?
epsg_code = crs_to_epsg_code(crs)
epsg_code = normalize_crs(crs)
if epsg_code is not None:
# proj did recognize the CRS
crs_name = f"EPSG:{epsg_code}"
Expand Down
73 changes: 71 additions & 2 deletions openeo/rest/vectorcube.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
import pathlib
import typing
from typing import List, Optional, Union
from typing import List, Optional, Union, Tuple, Callable

import shapely.geometry.base

Expand All @@ -14,7 +14,7 @@
from openeo.rest._datacube import THIS, UDF, _ProcessGraphAbstraction, build_child_callback
from openeo.rest.job import BatchJob
from openeo.rest.mlmodel import MlModel
from openeo.util import dict_no_none, guess_format
from openeo.util import dict_no_none, guess_format, to_bbox_dict, InvalidBBoxException

if typing.TYPE_CHECKING:
# Imports for type checking only (circular import issue at runtime).
Expand Down Expand Up @@ -327,6 +327,75 @@ def create_job(

send_job = legacy_alias(create_job, name="send_job", since="0.10.0")

@openeo_process
def filter_bands(self, bands: List[str]) -> "VectorCube":
"""
.. versionadded:: 0.22.0
"""
# TODO #459 docs
return self.process(
process_id="filter_bands",
arguments={"data": THIS, "bands": bands},
)

@openeo_process
def filter_bbox(
self,
*,
west: Optional[float] = None,
south: Optional[float] = None,
east: Optional[float] = None,
north: Optional[float] = None,
extent: Optional[Union[dict, List[float], Tuple[float, float, float, float], Parameter]] = None,
crs: Optional[int] = None,
) -> "VectorCube":
"""
.. versionadded:: 0.22.0
"""
# TODO #459 docs
if any(c is not None for c in [west, south, east, north]):
if extent is not None:
raise InvalidBBoxException("Don't specify both west/south/east/north and extent")
extent = dict_no_none(west=west, south=south, east=east, north=north)

if isinstance(extent, Parameter):
pass
else:
extent = to_bbox_dict(extent, crs=crs)
return self.process(
process_id="filter_bbox",
arguments={"data": THIS, "extent": extent},
)

@openeo_process
def filter_labels(
self, condition: Union[PGNode, Callable], dimension: str, context: Optional[dict] = None
) -> "VectorCube":
"""
.. versionadded:: 0.22.0
"""
# TODO #459 docs
condition = build_child_callback(condition, parent_parameters=["value"])
return self.process(
process_id="filter_labels",
arguments=dict_no_none(data=THIS, condition=condition, dimension=dimension, context=context),
)

@openeo_process
def filter_vector(
self, geometries: Union["VectorCube", shapely.geometry.base.BaseGeometry, dict], relation: str = "intersects"
) -> "VectorCube":
"""
.. versionadded:: 0.22.0
"""
# TODO #459 docs
if not isinstance(geometries, (VectorCube, Parameter)):
geometries = self.load_geojson(connection=self.connection, data=geometries)
return self.process(
process_id="filter_vector",
arguments={"data": THIS, "geometries": geometries, "relation": relation},
)

@openeo_process
def fit_class_random_forest(
self,
Expand Down
Loading

0 comments on commit 543149e

Please sign in to comment.