Skip to content
This repository was archived by the owner on Jul 24, 2024. It is now read-only.

Commit d2e6ace

Browse files
authored
Merge pull request #3 from CS-SI/develop
v0.3.0
2 parents 381f648 + 763ab7b commit d2e6ace

File tree

6 files changed

+317
-82
lines changed

6 files changed

+317
-82
lines changed

CHANGES.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Release history
2+
---------------
3+
4+
0.3.0 (2021-03-26)
5+
++++++++++++++++++
6+
7+
- Search using rounded coords in WKT geometries for shorter request URL
8+
- Package building updated to fix integration with eodag
9+
- Various minor fixes
10+
11+
0.2.0 (2021-03-18)
12+
++++++++++++++++++
13+
14+
- Update and align to eodag 2.1.0
15+
- Project moved to github
16+
17+
0.1.0 (2018-07-30)
18+
++++++++++++++++++
19+
20+
- First release

README.rst

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
.. image:: https://badge.fury.io/py/eodag-sentinelsat.svg
2+
:target: https://badge.fury.io/py/eodag-sentinelsat
3+
4+
.. image:: https://img.shields.io/pypi/l/eodag-sentinelsat.svg
5+
:target: https://pypi.org/project/eodag-sentinelsat/
6+
7+
.. image:: https://img.shields.io/pypi/pyversions/eodag-sentinelsat.svg
8+
:target: https://pypi.org/project/eodag-sentinelsat/
9+
110
eodag-sentinelsat
211
=================
312

@@ -7,21 +16,19 @@ search and download EO products from catalogs implementing the
716
`SciHub / Copernicus Open Access Hub interface <https://scihub.copernicus.eu/userguide/WebHome>`_.
817
It is basically a wrapper around `sentinelsat <https://sentinelsat.readthedocs.io>`_, enabling it to be used on eodag.
918

19+
.. image:: https://eodag.readthedocs.io/en/latest/_static/eodag_bycs.png
20+
:target: https://github.com/CS-SI/eodag
21+
22+
|
23+
1024

1125
Installation
1226
============
1327

14-
* If you already have a particular version of eodag installed on your system::
28+
eodag-sentinelsat is on `PyPI <https://pypi.org/project/eodag-sentinelsat/>`_::
1529

1630
python -m pip install eodag-sentinelsat
1731

18-
* If you don't have eodag installed and want it installed and knowing about sentinelsat plugin or if you want to
19-
develop on this repository::
20-
21-
python -m pip install eodag-sentinelsat[standalone]
22-
23-
The standalone install will install eodag itself along the way
24-
2532

2633
Contribute
2734
==========
@@ -30,7 +37,7 @@ If you intend to contribute to eodag-sentinelsat source code::
3037

3138
git clone https://github.com/CS-SI/eodag-sentinelsat.git
3239
cd eodag-sentinelsat
33-
python -m pip install -e .[standalone,dev]
40+
python -m pip install -e .[dev]
3441
pre-commit install
3542
tox
3643

eodag_sentinelsat/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# -*- coding: utf-8 -*-
2+
# eodag-sentinelsat, a plugin for searching and downloading products from Copernicus Scihub
3+
# Copyright 2021, CS GROUP - France, http://www.c-s.fr
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
"""Sentinelsat plugin to EODAG."""
Lines changed: 77 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121
import zipfile
2222
from datetime import datetime
2323

24+
from eodag.api.search_result import SearchResult
2425
from eodag.plugins.apis.base import Api
2526
from eodag.plugins.search.qssearch import ODataV4Search
2627
from eodag.utils import get_progress_callback
27-
from sentinelsat import SentinelAPI
28+
from eodag.utils.exceptions import MisconfiguredError, RequestError
29+
from sentinelsat import SentinelAPI, SentinelAPIError
2830

2931
logger = logging.getLogger("eodag.plugins.apis.sentinelsat")
3032

@@ -35,6 +37,13 @@ class SentinelsatAPI(Api, ODataV4Search):
3537
3638
Api that enables to search and download EO products from catalogs implementing the SchiHub interface.
3739
It is basically a wrapper around sentinelsat, enabling it to be used on eodag.
40+
41+
We use the API to download data. Available keywords are:
42+
[area, date, raw, area_relation, order_by, limit, offset, **keywords]
43+
https://sentinelsat.readthedocs.io/en/stable/api.html#sentinelsat.SentinelAPI.query
44+
45+
The keywords are those that can be found here:
46+
https://sentinelsat.readthedocs.io/en/stable/api.html#opensearch-example
3847
"""
3948

4049
def __init__(self, provider, config):
@@ -94,6 +103,22 @@ def query(self, items_per_page=None, page=None, count=True, **kwargs):
94103
)
95104
logger.info("No results found !")
96105

106+
except SentinelAPIError as ex:
107+
# TODO: change it to ServerError when ssat 0.15 will be published !
108+
"""
109+
SentinelAPIError -- the parent, catch-all exception. Only used when no other more specific exception
110+
can be applied.
111+
SentinelAPILTAError -- raised when retrieving a product from the Long Term Archive.
112+
ServerError -- raised when the server responded in an unexpected manner, typically due to undergoing
113+
maintenance.
114+
UnauthorizedError -- raised when attempting to retrieve a product with incorrect credentials.
115+
QuerySyntaxError -- raised when the query string could not be parsed on the server side.
116+
QueryLengthError -- raised when the query string length was excessively long.
117+
InvalidKeyError -- raised when product with given key was not found on the server.
118+
InvalidChecksumError -- MD5 checksum of a local file does not match the one from the server.
119+
"""
120+
raise RequestError(ex) from ex
121+
97122
return eo_products, len(eo_products)
98123

99124
def download(self, product, auth=None, progress_callback=None, **kwargs) -> str:
@@ -106,35 +131,19 @@ def download(self, product, auth=None, progress_callback=None, **kwargs) -> str:
106131
:param kwargs: Not used, just here for compatibility reasons
107132
:return: Downloaded product path
108133
"""
109-
# Init Sentinelsat API if needed (connect...)
110-
self._init_api()
111-
112-
# Download all products
113-
prod_id = product.properties["id"]
114-
product_info = self.api.download_all(
115-
[prod_id], directory_path=self.config.outputs_prefix
134+
prods = self.download_all(
135+
SearchResult(
136+
[
137+
product,
138+
]
139+
),
140+
auth,
141+
progress_callback,
142+
**kwargs
116143
)
117144

118-
# Only select the downloaded products
119-
product_info = product_info[0][prod_id]
120-
121-
# Extract them if needed
122-
if self.config.extract and product_info["path"].endswith(".zip"):
123-
logger.info("Extraction activated")
124-
with zipfile.ZipFile(product_info["path"], "r") as zfile:
125-
fileinfos = zfile.infolist()
126-
with get_progress_callback() as bar:
127-
bar.max_size = len(fileinfos)
128-
bar.unit = "file"
129-
bar.desc = "Extracting files from {}".format(product_info["path"])
130-
bar.unit_scale = False
131-
bar.position = 2
132-
for fileinfo in fileinfos:
133-
zfile.extract(fileinfo, path=self.config.outputs_prefix)
134-
bar(1)
135-
return product_info["path"][: product_info["path"].index(".zip")]
136-
else:
137-
return product_info["path"]
145+
# Manage the case if nothing has been downloaded
146+
return prods[0] if len(prods) > 0 else ""
138147

139148
def download_all(
140149
self, search_result, auth=None, progress_callback=None, **kwargs
@@ -154,44 +163,51 @@ def download_all(
154163

155164
# Download all products
156165
prod_ids = [prod.properties["uuid"] for prod in search_result.data]
157-
product_info = self.api.download_all(
166+
success, _, _ = self.api.download_all(
158167
prod_ids, directory_path=self.config.outputs_prefix
159168
)
160169

161-
# Only select the downloaded products
162-
paths = []
163-
for prod_id in prod_ids:
164-
info = product_info[0][prod_id]
165-
166-
# Extract them if needed
167-
if self.config.extract and info["path"].endswith(".zip"):
168-
logger.info("Extraction activated")
169-
with zipfile.ZipFile(info["path"], "r") as zfile:
170-
fileinfos = zfile.infolist()
171-
with get_progress_callback() as bar:
172-
bar.max_size = len(fileinfos)
173-
bar.unit = "file"
174-
bar.desc = "Extracting files from {}".format(info["path"])
175-
bar.unit_scale = False
176-
bar.position = 2
177-
for fileinfo in fileinfos:
178-
zfile.extract(fileinfo, path=self.config.outputs_prefix)
179-
bar(1)
180-
paths.append(info["path"][: info["path"].index(".zip")])
181-
else:
182-
paths.append(info["path"])
183-
170+
# Only extract the successfully downloaded products
171+
paths = [self.extract(prods) for prods in success.values()]
184172
return paths
185173

186-
def _init_api(self):
174+
def extract(self, product_info: dict) -> str:
175+
"""
176+
Extract products if needed.
177+
178+
:param product_info: Product info
179+
:return: Path (archive or extracted according to the config)
180+
"""
181+
# Extract them if needed
182+
if self.config.extract and product_info["path"].endswith(".zip"):
183+
logger.info("Extraction activated")
184+
with zipfile.ZipFile(product_info["path"], "r") as zfile:
185+
fileinfos = zfile.infolist()
186+
with get_progress_callback() as bar:
187+
bar.max_size = len(fileinfos)
188+
bar.unit = "file"
189+
bar.desc = "Extracting files from {}".format(product_info["path"])
190+
bar.unit_scale = False
191+
bar.position = 2
192+
for fileinfo in fileinfos:
193+
zfile.extract(fileinfo, path=self.config.outputs_prefix)
194+
bar(1)
195+
return product_info["path"][: product_info["path"].index(".zip")]
196+
else:
197+
return product_info["path"]
198+
199+
def _init_api(self) -> None:
187200
"""Initialize Sentinelsat API if needed (connection and link)."""
188201
if not self.api:
189-
logger.debug("Initializing Sentinelsat API")
190-
self.api = SentinelAPI(
191-
self.config.credentials["username"],
192-
self.config.credentials["password"],
193-
self.config.endpoint,
194-
)
202+
try:
203+
logger.debug("Initializing Sentinelsat API")
204+
self.api = SentinelAPI(
205+
self.config.credentials["username"],
206+
self.config.credentials["password"],
207+
self.config.endpoint,
208+
)
209+
except KeyError as ex:
210+
raise MisconfiguredError(ex) from ex
195211
else:
196212
logger.debug("Sentinelsat API already initialized")
197213

@@ -242,7 +258,7 @@ def update_keyword(self, **kwargs):
242258
)
243259

244260
# Footprint
245-
if "area" in qp:
246-
qp["area"] = qp.pop("area").wkt
261+
if "area" in qp and isinstance(qp["area"], list):
262+
qp["area"] = qp["area"][0]
247263

248264
return qp, provider_product_type

0 commit comments

Comments
 (0)