Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
sbrunato committed Feb 19, 2024
2 parents 934279e + 3e3c2a4 commit 733c537
Show file tree
Hide file tree
Showing 28 changed files with 317 additions and 1,697 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: [3.8, 3.12]
os: [ubuntu-latest]
python-version: [3.8, "3.12"]
os: [ubuntu-latest, windows-latest]
steps:
- name: Checkout the repo
uses: actions/checkout@v2
Expand Down
11 changes: 10 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
Release history
---------------

0.4.0 (2024-02-19)
++++++++++++++++++

- `get_data` directly available on `product.assets` (#46)
- Adds windows support (#9)
- Removes limited grpc support (#43)
- Adds python type hints (#45)
- Various minor fixes and improvements (#44)(#47)

0.3.1 (2023-11-15)
++++++++++++++++++

- Allows regex in band selection through `StacAssets` driver (#38)
- Removes support for `python3.7`` and adds support for `python3.12` (#39)
- Various minor fixes and improvements (#37)
- Various minor fixes and improvements (#37)

0.3.0 (2023-03-17)
++++++++++++++++++
Expand Down
2 changes: 0 additions & 2 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ The Apache 2.0 License
The following components are provided under the Apache 2.0 License (https://opensource.org/licenses/Apache-2.0).
See project link for details.

https://grpc.io/
https://github.com/pydata/xarray
https://github.com/corteva/rioxarray
https://github.com/CS-SI/eodag
Expand All @@ -31,4 +30,3 @@ See project link for details.

http://www.numpy.org/
https://github.com/mapbox/rasterio
https://github.com/google/protobuf
2 changes: 1 addition & 1 deletion eodag_cube/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

__title__ = "eodag-cube"
__description__ = "Data access for EODAG"
__version__ = "0.3.1"
__version__ = "0.4.0"
__author__ = "CS GROUP - France (CSSI)"
__author_email__ = "[email protected]"
__url__ = "https://github.com/CS-SI/eodag-cube"
Expand Down
105 changes: 105 additions & 0 deletions eodag_cube/api/product/_assets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
# Copyright 2023, CS GROUP - France, https://www.csgroup.eu/
#
# This file is part of EODAG project
# https://www.github.com/CS-SI/EODAG
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union

from eodag.api.product._assets import Asset as Asset_core
from eodag.api.product._assets import AssetsDict as AssetsDict_core

if TYPE_CHECKING:
from rasterio.enums import Resampling
from shapely.geometry.base import BaseGeometry
from xarray import DataArray


class AssetsDict(AssetsDict_core):
"""A UserDict object listing assets contained in a
:class:`~eodag.api.product._product.EOProduct` resulting from a search.
:param product: Product resulting from a search
:type product: :class:`~eodag.api.product._product.EOProduct`
:param args: (optional) Arguments used to init the dictionary
:type args: Any
:param kwargs: (optional) Additional named-arguments used to init the dictionary
:type kwargs: Any
"""

def __setitem__(self, key: str, value: Dict[str, Any]) -> None:
super(AssetsDict_core, self).__setitem__(key, Asset(self.product, key, value))


class Asset(Asset_core):
"""A UserDict object containg one of the assets of a
:class:`~eodag.api.product._product.EOProduct` resulting from a search.
:param product: Product resulting from a search
:type product: :class:`~eodag.api.product._product.EOProduct`
:param key: asset key
:type key: str
:param args: (optional) Arguments used to init the dictionary
:type args: Any
:param kwargs: (optional) Additional named-arguments used to init the dictionary
:type kwargs: Any
"""

def get_data(
self,
crs: Optional[str] = None,
resolution: Optional[float] = None,
extent: Optional[
Union[str, Dict[str, float], List[float], BaseGeometry]
] = None,
resampling: Optional[Resampling] = None,
**rioxr_kwargs: Any,
) -> DataArray:
"""Retrieves asset raster data abstracted by the :class:`EOProduct`
:param crs: (optional) The coordinate reference system in which the dataset should be returned
:type crs: str
:param resolution: (optional) The resolution in which the dataset should be returned
(given in the unit of the crs)
:type resolution: float
:param extent: (optional) The coordinates on which to zoom, matching the given CRS. Can be defined in
different ways (its bounds will be used):
* with a Shapely geometry object:
:class:`shapely.geometry.base.BaseGeometry`
* with a bounding box (dict with keys: "lonmin", "latmin", "lonmax", "latmax"):
``dict.fromkeys(["lonmin", "latmin", "lonmax", "latmax"])``
* with a bounding box as list of float:
``[lonmin, latmin, lonmax, latmax]``
* with a WKT str
:type extent: Union[str, dict, shapely.geometry.base.BaseGeometry]
:param resampling: (optional) Warp resampling algorithm passed to :class:`rasterio.vrt.WarpedVRT`
:type resampling: Resampling
:param rioxr_kwargs: kwargs passed to ``rioxarray.open_rasterio()``
:type rioxr_kwargs: Any
:returns: The numeric matrix corresponding to the sub dataset or an empty
array if unable to get the data
:rtype: xarray.DataArray
"""
return self.product.get_data(
band=self.key,
crs=crs,
resolution=resolution,
extent=extent,
resampling=resampling,
**rioxr_kwargs,
)
100 changes: 45 additions & 55 deletions eodag_cube/api/product/_product.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations

import logging
from contextlib import contextmanager
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union

import numpy as np
import rasterio
Expand All @@ -27,8 +30,14 @@
from eodag.api.product._product import EOProduct as EOProduct_core
from eodag.utils import get_geometry_from_various
from eodag.utils.exceptions import DownloadError, UnsupportedDatasetAddressScheme
from eodag_cube.api.product._assets import AssetsDict

if TYPE_CHECKING:
from rasterio.enums import Resampling
from shapely.geometry.base import BaseGeometry
from xarray import DataArray

logger = logging.getLogger("eodag.api.product")
logger = logging.getLogger("eodag-cube.api.product")


class EOProduct(EOProduct_core):
Expand All @@ -47,6 +56,19 @@ class EOProduct(EOProduct_core):
:type provider: str
:param properties: The metadata of the product
:type properties: dict
:ivar product_type: The product type
:vartype product_type: str
:ivar location: The path to the product, either remote or local if downloaded
:vartype location: str
:ivar remote_location: The remote path to the product
:vartype remote_location: str
:ivar search_kwargs: The search kwargs used by eodag to search for the product
:vartype search_kwargs: Any
:ivar geometry: The geometry of the product
:vartype geometry: :class:`shapely.geometry.base.BaseGeometry`
:ivar search_intersection: The intersection between the product's geometry
and the search area.
:vartype search_intersection: :class:`shapely.geometry.base.BaseGeometry` or None
.. note::
The geojson spec `enforces <https://github.com/geojson/draft-geojson/pull/6>`_
Expand All @@ -56,18 +78,27 @@ class EOProduct(EOProduct_core):
mentioned CRS.
"""

def __init__(self, *args, **kwargs):
super(EOProduct, self).__init__(*args, **kwargs)
def __init__(
self, provider: str, properties: Dict[str, Any], **kwargs: Any
) -> None:
super(EOProduct, self).__init__(
provider=provider, properties=properties, **kwargs
)
core_assets_data = self.assets.data
self.assets = AssetsDict(self)
self.assets.update(core_assets_data)

def get_data(
self,
band,
crs=None,
resolution=None,
extent=None,
resampling=None,
**rioxr_kwargs,
):
band: str,
crs: Optional[str] = None,
resolution: Optional[float] = None,
extent: Optional[
Union[str, Dict[str, float], List[float], BaseGeometry]
] = None,
resampling: Optional[Resampling] = None,
**rioxr_kwargs: Any,
) -> DataArray:
"""Retrieves all or part of the raster data abstracted by the :class:`EOProduct`
:param band: The band of the dataset to retrieve (e.g.: 'B01')
Expand All @@ -92,7 +123,7 @@ def get_data(
:param resampling: (optional) Warp resampling algorithm passed to :class:`rasterio.vrt.WarpedVRT`
:type resampling: Resampling
:param rioxr_kwargs: kwargs passed to ``rioxarray.open_rasterio()``
:type rioxr_kwargs: dict
:type rioxr_kwargs: Any
:returns: The numeric matrix corresponding to the sub dataset or an empty
array if unable to get the data
:rtype: xarray.DataArray
Expand Down Expand Up @@ -142,7 +173,7 @@ def get_data(
warped_vrt_args["resampling"] = resampling

@contextmanager
def pass_resource(resource, **kwargs):
def pass_resource(resource: Any, **kwargs: Any) -> Any:
yield resource

if warped_vrt_args:
Expand Down Expand Up @@ -181,14 +212,14 @@ def pass_resource(resource, **kwargs):
logger.error(e)
return fail_value

def _get_rio_env(self, dataset_address):
def _get_rio_env(self, dataset_address: str) -> Dict[str, Any]:
"""Get rasterio environement variables needed for data access.
:param dataset_address: address of the data to read
:type dataset_address: str
:return: The rasterio environement variables
:rtype: dict
:rtype: Dict[str, Any]
"""
product_location_scheme = dataset_address.split("://")[0]
if product_location_scheme == "s3" and hasattr(
Expand All @@ -205,44 +236,3 @@ def _get_rio_env(self, dataset_address):
}
else:
return {}

def encode(self, raster, encoding="protobuf"):
"""Encode the subset to a network-compatible format.
:param raster: The raster data to encode
:type raster: xarray.DataArray
:param encoding: The encoding of the export
:type encoding: str
:return: The data encoded in the specified encoding
:rtype: bytes
"""
# If no encoding return an empty byte
if not encoding:
logger.warning("Trying to encode a raster without specifying an encoding")
return b""
strategy = getattr(self, "_{encoding}".format(**locals()), None)
if strategy:
return strategy(raster)
logger.error("Unknown encoding: %s", encoding)
return b""

def _protobuf(self, raster):
"""Google's Protocol buffers encoding strategy.
:param raster: The raster to encode
:type raster: xarray.DataArray
:returns: The raster data represented by this subset in protocol buffers
encoding
:rtype: bytes
"""
from eodag_cube.api.product.protobuf import eo_product_pb2

subdataset = eo_product_pb2.EOProductSubdataset()
subdataset.id = self.properties["id"]
subdataset.producer = self.provider
subdataset.product_type = self.product_type
subdataset.platform = self.properties["platformSerialIdentifier"]
subdataset.sensor = self.properties["instrument"]
data = subdataset.data
data.array.extend(list(raster.values.flatten().astype(int)))
data.shape.extend(list(raster.values.shape))
data.dtype = raster.values.dtype.name
return subdataset.SerializeToString()
4 changes: 3 additions & 1 deletion eodag_cube/api/product/drivers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@

DRIVERS = [
{
"criteria": [lambda prod: True if hasattr(prod, "assets") else False],
"criteria": [
lambda prod: True if len(getattr(prod, "assets", {})) > 0 else False
],
"driver": StacAssets(),
},
{
Expand Down
20 changes: 16 additions & 4 deletions eodag_cube/api/product/drivers/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,35 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

import rasterio

from eodag.api.product.drivers.base import DatasetDriver
from eodag.utils import uri_to_path
from eodag.utils.exceptions import AddressNotFound, UnsupportedDatasetAddressScheme

if TYPE_CHECKING:
from eodag.api.product._product import EOProduct


class GenericDriver(DatasetDriver):
"""Generic Driver for products that need to be downloaded"""

def get_data_address(self, eo_product, band):
def get_data_address(self, eo_product: EOProduct, band: str) -> str:
"""Get the address of a product subdataset.
See :func:`~eodag.api.product.drivers.base.DatasetDriver.get_data_address` to get help on the formal
parameters.
:param eo_product: The product whom underlying dataset address is to be retrieved
:type eo_product: :class:`~eodag.api.product._product.EOProduct`
:param band: The band to retrieve (e.g: 'B01')
:type band: str
:returns: An address for the dataset
:rtype: str
:raises: :class:`~eodag.utils.exceptions.AddressNotFound`
:raises: :class:`~eodag.utils.exceptions.UnsupportedDatasetAddressScheme`
"""
product_location_scheme = eo_product.location.split("://")[0]
if product_location_scheme == "file":
Expand All @@ -42,7 +54,7 @@ def get_data_address(self, eo_product, band):
try:
# return the first file readable by rasterio
rasterio.drivers.driver_from_extension(filename)
return str(filename)
return str(filename.resolve())
except ValueError:
pass
raise AddressNotFound
Expand Down
Loading

0 comments on commit 733c537

Please sign in to comment.