eotdl 2023.11.2.post5__tar.gz → 2023.11.3.post2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/PKG-INFO +5 -3
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/README.md +1 -1
- eotdl-2023.11.3.post2/eotdl/__init__.py +1 -0
- eotdl-2023.11.3.post2/eotdl/access/__init__.py +7 -0
- eotdl-2023.11.3.post2/eotdl/access/airbus/__init__.py +6 -0
- eotdl-2023.11.3.post2/eotdl/access/airbus/client.py +385 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/access/airbus/parameters.py +19 -4
- eotdl-2023.11.3.post2/eotdl/access/airbus/utils.py +34 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/access/download.py +30 -14
- eotdl-2023.11.3.post2/eotdl/access/search.py +26 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/access/sentinelhub/__init__.py +5 -1
- eotdl-2023.11.3.post2/eotdl/access/sentinelhub/client.py +113 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/access/sentinelhub/evalscripts.py +38 -39
- eotdl-2023.11.3.post2/eotdl/access/sentinelhub/parameters.py +90 -0
- eotdl-2023.11.3.post2/eotdl/access/sentinelhub/utils.py +100 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/auth/errors.py +2 -1
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/commands/auth.py +3 -3
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/__init__.py +5 -1
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/__init__.py +5 -1
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/assets.py +55 -32
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/dataframe.py +20 -14
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/dataframe_bck.py +2 -2
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/dataframe_labeling.py +15 -12
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/extensions/__init__.py +6 -2
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/extensions/base.py +8 -4
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/extensions/dem.py +6 -3
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/extensions/eo.py +10 -6
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/extensions/label/__init__.py +5 -1
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/extensions/label/base.py +40 -26
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/extensions/label/image_name_labeler.py +64 -43
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/extensions/label/scaneo.py +59 -56
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/extensions/ml_dataset.py +154 -56
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/extensions/projection.py +11 -9
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/extensions/raster.py +22 -14
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/extensions/sar.py +12 -7
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/extent.py +67 -40
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/parsers.py +18 -10
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/curation/stac/stac.py +81 -62
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/datasets/__init__.py +1 -1
- eotdl-2023.11.3.post2/eotdl/datasets/download.py +106 -0
- eotdl-2023.11.3.post2/eotdl/datasets/ingest.py +112 -0
- eotdl-2023.11.3.post2/eotdl/files/__init__.py +1 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/files/ingest.py +3 -1
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/models/download.py +1 -1
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/repos/AuthAPIRepo.py +0 -1
- eotdl-2023.11.3.post2/eotdl/repos/DatasetsAPIRepo.py +125 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/repos/FilesAPIRepo.py +7 -92
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/repos/ModelsAPIRepo.py +0 -1
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/tools/__init__.py +5 -1
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/tools/geo_utils.py +78 -48
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/tools/metadata.py +13 -11
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/tools/paths.py +14 -14
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/tools/stac.py +36 -31
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/tools/time_utils.py +53 -26
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/tools/tools.py +84 -50
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/pyproject.toml +6 -2
- eotdl-2023.11.2.post5/eotdl/__init__.py +0 -1
- eotdl-2023.11.2.post5/eotdl/access/__init__.py +0 -4
- eotdl-2023.11.2.post5/eotdl/access/airbus/__init__.py +0 -2
- eotdl-2023.11.2.post5/eotdl/access/airbus/client.py +0 -367
- eotdl-2023.11.2.post5/eotdl/access/airbus/utils.py +0 -29
- eotdl-2023.11.2.post5/eotdl/access/search.py +0 -15
- eotdl-2023.11.2.post5/eotdl/access/sentinelhub/client.py +0 -110
- eotdl-2023.11.2.post5/eotdl/access/sentinelhub/parameters.py +0 -70
- eotdl-2023.11.2.post5/eotdl/access/sentinelhub/utils.py +0 -90
- eotdl-2023.11.2.post5/eotdl/datasets/download.py +0 -119
- eotdl-2023.11.2.post5/eotdl/datasets/ingest.py +0 -55
- eotdl-2023.11.2.post5/eotdl/files/__init__.py +0 -1
- eotdl-2023.11.2.post5/eotdl/repos/DatasetsAPIRepo.py +0 -249
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/auth/__init__.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/auth/auth.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/auth/is_logged.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/auth/logout.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/cli.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/commands/__init__.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/commands/datasets.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/commands/models.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/datasets/metadata.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/datasets/retrieve.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/datasets/usecases/__init__.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/datasets/usecases/datasets/DownloadFile.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/datasets/usecases/datasets/DownloadFileURL.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/datasets/usecases/datasets/IngestDataset.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/datasets/usecases/datasets/IngestLargeDataset.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/datasets/usecases/datasets/IngestLargeDatasetParallel.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/datasets/usecases/datasets/IngestSTAC.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/datasets/usecases/datasets/__init__.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/models/__init__.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/models/ingest.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/models/metadata.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/models/retrieve.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/repos/APIRepo.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/repos/AuthRepo.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/repos/__init__.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/shared/__init__.py +0 -0
- {eotdl-2023.11.2.post5 → eotdl-2023.11.3.post2}/eotdl/shared/checksum.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: eotdl
|
3
|
-
Version: 2023.11.
|
3
|
+
Version: 2023.11.3.post2
|
4
4
|
Summary: Earth Observation Training Data Lab
|
5
5
|
License: MIT
|
6
6
|
Author: EarthPulse
|
@@ -12,11 +12,13 @@ Classifier: Programming Language :: Python :: 3.8
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.9
|
13
13
|
Classifier: Programming Language :: Python :: 3.10
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
15
|
+
Requires-Dist: black (>=23.10.1,<24.0.0)
|
15
16
|
Requires-Dist: geomet (>=1.0.0,<2.0.0)
|
16
17
|
Requires-Dist: geopandas (>=0.13.2,<0.14.0)
|
18
|
+
Requires-Dist: mypy (>=1.6.1,<2.0.0)
|
17
19
|
Requires-Dist: pydantic (>=1.10.6,<2.0.0)
|
18
20
|
Requires-Dist: pyjwt (>=2.6.0,<3.0.0)
|
19
|
-
Requires-Dist: pystac (
|
21
|
+
Requires-Dist: pystac[validation] (==1.8.2)
|
20
22
|
Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
|
21
23
|
Requires-Dist: rasterio (>=1.3.9,<2.0.0)
|
22
24
|
Requires-Dist: requests (>=2.28.2,<3.0.0)
|
@@ -27,7 +29,7 @@ Description-Content-Type: text/markdown
|
|
27
29
|
|
28
30
|
<p align="center">
|
29
31
|
<a href="https://www.eotdl.com/">
|
30
|
-
<img src="eotdl.png"
|
32
|
+
<img src="eotdl.png" alt="EOTDL" />
|
31
33
|
</a>
|
32
34
|
</p>
|
33
35
|
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "2023.11.03-2"
|
@@ -0,0 +1,385 @@
|
|
1
|
+
"""
|
2
|
+
Module for managing the Airbus configuration and data access
|
3
|
+
"""
|
4
|
+
|
5
|
+
import json
|
6
|
+
import time
|
7
|
+
from typing import Optional, Iterable
|
8
|
+
from os.path import join, exists
|
9
|
+
import requests
|
10
|
+
from requests.exceptions import ConnectTimeout, ReadTimeout
|
11
|
+
|
12
|
+
from .parameters import AirbusURL, AirbusProductType, AirbusImageFormat, AirbusRadiometricProcessing
|
13
|
+
from .utils import get_airbus_access_token
|
14
|
+
from ...tools import expand_time_interval, bbox_to_coordinates
|
15
|
+
|
16
|
+
|
17
|
+
class AirbusClient:
|
18
|
+
"""
|
19
|
+
Client class to manage the Sentinel Hub Python interface.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(
|
23
|
+
self,
|
24
|
+
access_token: str,
|
25
|
+
api_key: str,
|
26
|
+
) -> None:
|
27
|
+
"""
|
28
|
+
Constructor
|
29
|
+
|
30
|
+
Params
|
31
|
+
----------
|
32
|
+
access_token: str
|
33
|
+
Access token
|
34
|
+
api_key: str
|
35
|
+
API key
|
36
|
+
"""
|
37
|
+
self.airbus_access_token = access_token
|
38
|
+
self._api_key = api_key
|
39
|
+
|
40
|
+
def get_total_products_price(
|
41
|
+
self, payload: dict, all_info: Optional[bool] = False
|
42
|
+
) -> dict:
|
43
|
+
"""
|
44
|
+
Get total products price
|
45
|
+
"""
|
46
|
+
if all_info:
|
47
|
+
response = []
|
48
|
+
else:
|
49
|
+
response = 0
|
50
|
+
|
51
|
+
for _, location_data in payload.items():
|
52
|
+
product_id = location_data["image"]
|
53
|
+
if product_id:
|
54
|
+
price_response = self.get_product_price(
|
55
|
+
product_id, location_data["bounding_box"]
|
56
|
+
)
|
57
|
+
if all_info:
|
58
|
+
response.append(price_response)
|
59
|
+
else:
|
60
|
+
response += price_response["price"]["credits"]
|
61
|
+
|
62
|
+
return response
|
63
|
+
|
64
|
+
def get_product_price(
|
65
|
+
self,
|
66
|
+
product_id: str,
|
67
|
+
coordinates: Iterable,
|
68
|
+
product_type: Optional[AirbusProductType] = AirbusProductType.MULTISPECTRAL,
|
69
|
+
image_format: Optional[AirbusImageFormat] = AirbusImageFormat.GEOTIFF,
|
70
|
+
processing: Optional[
|
71
|
+
AirbusRadiometricProcessing
|
72
|
+
] = AirbusRadiometricProcessing.REFLECTANCE,
|
73
|
+
) -> dict:
|
74
|
+
"""
|
75
|
+
Get product price
|
76
|
+
|
77
|
+
Params
|
78
|
+
----------
|
79
|
+
product_id: str
|
80
|
+
Product ID
|
81
|
+
coordinates: tuple
|
82
|
+
Polygon coordinates
|
83
|
+
product_type: AirbusProductType
|
84
|
+
Product type
|
85
|
+
image_format: AirbusImageFormat
|
86
|
+
Image format
|
87
|
+
processing: AirbusRadiometricProcessing
|
88
|
+
Radiometric processing
|
89
|
+
Types are defined at parameters.py
|
90
|
+
|
91
|
+
Returns
|
92
|
+
----------
|
93
|
+
dict
|
94
|
+
Product price
|
95
|
+
"""
|
96
|
+
if isinstance(coordinates, (list, tuple)) and len(
|
97
|
+
coordinates
|
98
|
+
) == 4:
|
99
|
+
coordinates = bbox_to_coordinates(coordinates)
|
100
|
+
|
101
|
+
headers = {
|
102
|
+
"Authorization": f"Bearer {self.airbus_access_token}",
|
103
|
+
"Content-Type": "application/json",
|
104
|
+
"Cache-Control": "no-cache",
|
105
|
+
}
|
106
|
+
|
107
|
+
# TODO make productType, imageformat and processing configurable with paramters controlled by classes like
|
108
|
+
# TODO AirbusProductType.multiSpectral, AirbusImageFormat.geotiff, AirbusProcessing
|
109
|
+
payload = {
|
110
|
+
"kind": "order.data.product",
|
111
|
+
"products": [
|
112
|
+
{
|
113
|
+
"productType": product_type,
|
114
|
+
"radiometricProcessing": processing,
|
115
|
+
"imageFormat": image_format,
|
116
|
+
"crsCode": "urn:ogc:def:crs:EPSG::4326",
|
117
|
+
"id": product_id,
|
118
|
+
"aoi": {"type": "Polygon", "coordinates": [coordinates]},
|
119
|
+
}
|
120
|
+
],
|
121
|
+
}
|
122
|
+
response = requests.request(
|
123
|
+
"POST", AirbusURL.PRICES, json=payload, headers=headers, timeout=60000
|
124
|
+
)
|
125
|
+
|
126
|
+
return response.json()
|
127
|
+
|
128
|
+
def place_product_order(
|
129
|
+
self,
|
130
|
+
product_id: str,
|
131
|
+
coordinates: Iterable,
|
132
|
+
product_type: Optional[AirbusProductType] = AirbusProductType.MULTISPECTRAL,
|
133
|
+
image_format: Optional[AirbusImageFormat] = AirbusImageFormat.GEOTIFF,
|
134
|
+
processing: Optional[
|
135
|
+
AirbusRadiometricProcessing
|
136
|
+
] = AirbusRadiometricProcessing.REFLECTANCE,
|
137
|
+
) -> dict:
|
138
|
+
"""
|
139
|
+
Place product order
|
140
|
+
|
141
|
+
Params
|
142
|
+
----------
|
143
|
+
product_id: str
|
144
|
+
Product ID
|
145
|
+
bounding_box: tuple
|
146
|
+
Bounding box
|
147
|
+
product_type: AirbusProductType
|
148
|
+
Product type
|
149
|
+
image_format: AirbusImageFormat
|
150
|
+
Image format
|
151
|
+
processing: AirbusRadiometricProcessing
|
152
|
+
Radiometric processing
|
153
|
+
Types are defined at parameters.py
|
154
|
+
|
155
|
+
Returns
|
156
|
+
----------
|
157
|
+
dict
|
158
|
+
Order data
|
159
|
+
"""
|
160
|
+
if isinstance(coordinates, (list, tuple)) and len(
|
161
|
+
coordinates
|
162
|
+
) == 4:
|
163
|
+
coordinates = bbox_to_coordinates(coordinates)
|
164
|
+
|
165
|
+
payload = {
|
166
|
+
"kind": "order.data.product",
|
167
|
+
"products": [
|
168
|
+
{
|
169
|
+
"productType": product_type,
|
170
|
+
"radiometricProcessing": processing,
|
171
|
+
"imageFormat": image_format,
|
172
|
+
"crsCode": "urn:ogc:def:crs:EPSG::4326",
|
173
|
+
"id": product_id,
|
174
|
+
"aoi": {"type": "Polygon", "coordinates": [coordinates]},
|
175
|
+
}
|
176
|
+
],
|
177
|
+
}
|
178
|
+
|
179
|
+
headers = {
|
180
|
+
"Authorization": f"Bearer {self.airbus_access_token}",
|
181
|
+
}
|
182
|
+
|
183
|
+
response = requests.request(
|
184
|
+
"POST", AirbusURL.ORDERS, json=payload, headers=headers, timeout=60000
|
185
|
+
)
|
186
|
+
|
187
|
+
return response.json()
|
188
|
+
|
189
|
+
def search_image(
|
190
|
+
self,
|
191
|
+
bounding_box: Iterable,
|
192
|
+
acquisition_date: Iterable,
|
193
|
+
timeout: Optional[int] = 10,
|
194
|
+
) -> dict:
|
195
|
+
"""
|
196
|
+
Search image
|
197
|
+
|
198
|
+
Params
|
199
|
+
----------
|
200
|
+
bounding_box: tuple or list
|
201
|
+
Bounding box
|
202
|
+
acquisition_date: tuple or list
|
203
|
+
Acquisition date
|
204
|
+
timeout: int
|
205
|
+
Timeout
|
206
|
+
|
207
|
+
Returns
|
208
|
+
----------
|
209
|
+
dict
|
210
|
+
Image data
|
211
|
+
"""
|
212
|
+
if isinstance(acquisition_date, (tuple, list)):
|
213
|
+
acquisition_date = "[" + ",".join(acquisition_date) + "]"
|
214
|
+
if isinstance(bounding_box, (tuple, list)) and len(
|
215
|
+
bounding_box
|
216
|
+
) == 4:
|
217
|
+
bounding_box = ",".join(str(num) for num in bounding_box)
|
218
|
+
|
219
|
+
querystring = {"acquisitionDate": str(acquisition_date), "bbox": bounding_box}
|
220
|
+
|
221
|
+
headers = {
|
222
|
+
"authorization": f"Bearer {self.airbus_access_token}",
|
223
|
+
"cache-control": "no-cache",
|
224
|
+
}
|
225
|
+
|
226
|
+
try:
|
227
|
+
response = requests.request(
|
228
|
+
"GET",
|
229
|
+
AirbusURL.SEARCH,
|
230
|
+
headers=headers,
|
231
|
+
params=querystring,
|
232
|
+
verify=False,
|
233
|
+
timeout=timeout,
|
234
|
+
)
|
235
|
+
return response.json()
|
236
|
+
except json.decoder.JSONDecodeError:
|
237
|
+
print("JSONDecodeError")
|
238
|
+
print(response)
|
239
|
+
except ReadTimeout:
|
240
|
+
print("ReadTimeout")
|
241
|
+
print(response)
|
242
|
+
|
243
|
+
def search_images_close_in_time(
|
244
|
+
self,
|
245
|
+
payload_dict: dict,
|
246
|
+
path: Optional[str] = None,
|
247
|
+
max_days: Optional[int] = 30,
|
248
|
+
) -> dict:
|
249
|
+
"""
|
250
|
+
Search images close in time
|
251
|
+
|
252
|
+
Params
|
253
|
+
----------
|
254
|
+
payload_dict: dict
|
255
|
+
Payload dictionary
|
256
|
+
max_days: int
|
257
|
+
Maximum days to search
|
258
|
+
|
259
|
+
Returns
|
260
|
+
----------
|
261
|
+
dict
|
262
|
+
Dictionary with the image data, as {location_id: image_data}, to
|
263
|
+
maintain track of the location
|
264
|
+
"""
|
265
|
+
responses = {}
|
266
|
+
if path:
|
267
|
+
responses_path = join(path, "airbus_images_response.json")
|
268
|
+
if exists(responses_path):
|
269
|
+
with open(responses_path, "r", encoding="utf-8") as f:
|
270
|
+
responses = json.load(f)
|
271
|
+
|
272
|
+
for location_id, location_info in list(payload_dict.items()):
|
273
|
+
bounding_box, time_interval = (
|
274
|
+
location_info["bounding_box"],
|
275
|
+
location_info["time_interval"],
|
276
|
+
)
|
277
|
+
days = 1
|
278
|
+
|
279
|
+
if location_id in responses:
|
280
|
+
continue
|
281
|
+
|
282
|
+
while days <= max_days:
|
283
|
+
try:
|
284
|
+
response = self.search_image(bounding_box, time_interval)
|
285
|
+
except ConnectTimeout or TimeoutError:
|
286
|
+
# Wait 5 seconds and try again
|
287
|
+
time.sleep(5)
|
288
|
+
# Restart the connection
|
289
|
+
self.airbus_access_token = get_airbus_access_token(self._api_key)
|
290
|
+
# Continue with the search
|
291
|
+
response = self.search_image(bounding_box, time_interval)
|
292
|
+
|
293
|
+
total_results = response["totalResults"]
|
294
|
+
|
295
|
+
if total_results > 0:
|
296
|
+
# By default, the search results are sorted per acquisition date
|
297
|
+
# (newest data is displayed first) and per cloud coverage (less cloudy images are displayed first).
|
298
|
+
# So, we can stop the search when we find the first image
|
299
|
+
if total_results > 1:
|
300
|
+
response["features"] = [response["features"][0]]
|
301
|
+
responses[location_id] = response["features"][0]
|
302
|
+
if path:
|
303
|
+
with open(responses_path, "w", encoding="utf-8") as f:
|
304
|
+
json.dump(responses, f)
|
305
|
+
break
|
306
|
+
|
307
|
+
time_interval = expand_time_interval(time_interval)
|
308
|
+
days += 1
|
309
|
+
|
310
|
+
# If no image is found, we add a None value to the dictionary
|
311
|
+
if total_results == 0:
|
312
|
+
responses[location_id] = None
|
313
|
+
if path:
|
314
|
+
with open(responses_path, "w", encoding="utf-8") as f:
|
315
|
+
json.dump(responses, f)
|
316
|
+
|
317
|
+
return responses
|
318
|
+
|
319
|
+
def format_product_payload(
|
320
|
+
self, location_payload: dict, images_response: dict
|
321
|
+
) -> dict:
|
322
|
+
"""
|
323
|
+
Format product payload
|
324
|
+
"""
|
325
|
+
for product_id, _ in location_payload.items():
|
326
|
+
# Add new key to the dictionary
|
327
|
+
location_payload[product_id]["image_response"] = (
|
328
|
+
images_response[product_id] if product_id in images_response else None
|
329
|
+
)
|
330
|
+
|
331
|
+
return location_payload
|
332
|
+
|
333
|
+
def split_product_payload(self, product_payload: dict) -> dict:
|
334
|
+
"""
|
335
|
+
Split product payload
|
336
|
+
"""
|
337
|
+
# split the product payload depending on if 'image' is None or not
|
338
|
+
product_payload_with_image = {}
|
339
|
+
product_payload_without_image = {}
|
340
|
+
|
341
|
+
for product_id, info in product_payload.items():
|
342
|
+
if info["image"]:
|
343
|
+
product_payload_with_image[product_id] = info
|
344
|
+
else:
|
345
|
+
product_payload_without_image[product_id] = info
|
346
|
+
|
347
|
+
return product_payload_with_image, product_payload_without_image
|
348
|
+
|
349
|
+
def get_all_order_status(self):
|
350
|
+
"""
|
351
|
+
Get all order status
|
352
|
+
"""
|
353
|
+
headers = {
|
354
|
+
"Authorization": f"Bearer {self.airbus_access_token}",
|
355
|
+
"Content-Type": "application/json",
|
356
|
+
"Cache-Control": "no-cache",
|
357
|
+
}
|
358
|
+
|
359
|
+
response = requests.request("GET", AirbusURL.ALL_ORDERS_STATUS, headers=headers, timeout=60000)
|
360
|
+
|
361
|
+
return response.text
|
362
|
+
|
363
|
+
def get_account_information(self):
|
364
|
+
"""
|
365
|
+
Get account information
|
366
|
+
"""
|
367
|
+
headers = {
|
368
|
+
"Authorization": f"Bearer {self.airbus_access_token}",
|
369
|
+
"Content-Type": "application/json",
|
370
|
+
"Cache-Control": "no-cache",
|
371
|
+
}
|
372
|
+
|
373
|
+
response = requests.request("GET", AirbusURL.ACCOUNT, headers=headers, timeout=60000)
|
374
|
+
|
375
|
+
return response.json()
|
376
|
+
|
377
|
+
def get_user_roles(self):
|
378
|
+
"""
|
379
|
+
Get user roles
|
380
|
+
"""
|
381
|
+
headers = {"Authorization": f"Bearer {self.airbus_access_token}"}
|
382
|
+
|
383
|
+
response = requests.request("GET", AirbusURL.ROLES, headers=headers, timeout=60000)
|
384
|
+
|
385
|
+
return response.json()
|
@@ -2,7 +2,11 @@
|
|
2
2
|
Parameters to access Airbus data
|
3
3
|
"""
|
4
4
|
|
5
|
-
|
5
|
+
|
6
|
+
class AirbusURL:
|
7
|
+
"""
|
8
|
+
AIRBUS URLs
|
9
|
+
"""
|
6
10
|
PRICES = "https://data.api.oneatlas.airbus.com/api/v1/prices"
|
7
11
|
ORDERS = "https://data.api.oneatlas.airbus.com/api/v1/orders"
|
8
12
|
SEARCH = "https://search.foundation.api.oneatlas.airbus.com/api/v2/opensearch?constellation=SPOT"
|
@@ -10,16 +14,27 @@ class AirbusURL():
|
|
10
14
|
ACCOUNT = "https://data.api.oneatlas.airbus.com/api/v1/me"
|
11
15
|
ROLES = "https://data.api.oneatlas.airbus.com/api/v1/me/services"
|
12
16
|
|
17
|
+
|
13
18
|
# Types are defined at: https://www.geoapi-airbusds.com/api-catalog-v2/oad-living-library/tutorials/#order-an-individual-product
|
14
19
|
|
15
|
-
|
20
|
+
|
21
|
+
class AirbusProductType:
|
22
|
+
"""
|
23
|
+
Airbus product types
|
24
|
+
"""
|
16
25
|
MULTISPECTRAL = "multiSpectral"
|
17
26
|
|
18
27
|
|
19
|
-
class AirbusRadiometricProcessing
|
28
|
+
class AirbusRadiometricProcessing:
|
29
|
+
"""
|
30
|
+
Airbus radiometric processing
|
31
|
+
"""
|
20
32
|
REFLECTANCE = "REFLECTANCE"
|
21
33
|
|
22
34
|
|
23
|
-
class AirbusImageFormat
|
35
|
+
class AirbusImageFormat:
|
36
|
+
"""
|
37
|
+
Airbus image format
|
38
|
+
"""
|
24
39
|
GEOTIFF = "image/geotiff"
|
25
40
|
JP2 = "image/jp2"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
"""
|
2
|
+
Utils
|
3
|
+
"""
|
4
|
+
|
5
|
+
import requests
|
6
|
+
|
7
|
+
|
8
|
+
def get_airbus_access_token(api_key: str) -> str:
|
9
|
+
"""
|
10
|
+
Get Airbus access token
|
11
|
+
|
12
|
+
Returns:
|
13
|
+
str: access token
|
14
|
+
"""
|
15
|
+
headers = {
|
16
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
17
|
+
}
|
18
|
+
|
19
|
+
data = [
|
20
|
+
("apikey", api_key),
|
21
|
+
("grant_type", "api_key"),
|
22
|
+
("client_id", "IDP"),
|
23
|
+
]
|
24
|
+
|
25
|
+
response = requests.post(
|
26
|
+
"https://authenticate.foundation.api.oneatlas.airbus.com/auth/realms/IDP/protocol/openid-connect/token",
|
27
|
+
headers=headers,
|
28
|
+
data=data,
|
29
|
+
timeout=60
|
30
|
+
)
|
31
|
+
|
32
|
+
access_token = response.json()["access_token"]
|
33
|
+
|
34
|
+
return access_token
|
@@ -1,16 +1,28 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
"""
|
2
|
+
Download imagery
|
3
|
+
"""
|
3
4
|
|
4
|
-
from shutil import rmtree
|
5
5
|
from datetime import datetime
|
6
6
|
from typing import Union, List
|
7
7
|
|
8
|
+
from .sentinelhub import (
|
9
|
+
SHClient,
|
10
|
+
SH_PARAMETERS_DICT,
|
11
|
+
evaluate_sentinel_parameters,
|
12
|
+
imagery_from_tmp_to_dir,
|
13
|
+
)
|
14
|
+
from .search import search_sentinel_imagery
|
15
|
+
|
8
16
|
|
9
|
-
def download_sentinel_imagery(
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
17
|
+
def download_sentinel_imagery(
|
18
|
+
output: str,
|
19
|
+
time_interval: Union[str, datetime, List[Union[str, datetime]]],
|
20
|
+
bounding_box: List[Union[int, float]],
|
21
|
+
sensor: str,
|
22
|
+
) -> None:
|
23
|
+
"""
|
24
|
+
Download Sentinel imagery
|
25
|
+
"""
|
14
26
|
evaluate_sentinel_parameters(sensor, time_interval, bounding_box, output)
|
15
27
|
|
16
28
|
client = SHClient()
|
@@ -21,11 +33,15 @@ def download_sentinel_imagery(output: str,
|
|
21
33
|
imagery_from_tmp_to_dir(output)
|
22
34
|
|
23
35
|
|
24
|
-
def search_and_download_sentinel_imagery(
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
36
|
+
def search_and_download_sentinel_imagery(
|
37
|
+
output: str,
|
38
|
+
time_interval: Union[str, datetime, List[Union[str, datetime]]],
|
39
|
+
bounding_box: List[Union[int, float]],
|
40
|
+
sensor: str,
|
41
|
+
) -> None:
|
42
|
+
"""
|
43
|
+
Search and download Sentinel imagery
|
44
|
+
"""
|
29
45
|
evaluate_sentinel_parameters(sensor, time_interval, bounding_box, output)
|
30
46
|
|
31
47
|
client = SHClient()
|
@@ -34,7 +50,7 @@ def search_and_download_sentinel_imagery(output: str,
|
|
34
50
|
results = search_sentinel_imagery(time_interval, bounding_box, sensor)
|
35
51
|
timestamps = [date.strftime("%Y-%m-%d") for date in results.get_timestamps()]
|
36
52
|
|
37
|
-
requests_list =
|
53
|
+
requests_list = []
|
38
54
|
for date in timestamps:
|
39
55
|
requests_list.append(client.request_data(date, bounding_box, parameters))
|
40
56
|
client.download_data(requests_list)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
"""
|
2
|
+
Search imagery
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Union, List
|
6
|
+
from datetime import datetime
|
7
|
+
|
8
|
+
from .sentinelhub import SHClient, SH_PARAMETERS_DICT, evaluate_sentinel_parameters
|
9
|
+
|
10
|
+
|
11
|
+
def search_sentinel_imagery(
|
12
|
+
time_interval: Union[str, datetime, List[Union[str, datetime]]],
|
13
|
+
bounding_box: List[Union[int, float]],
|
14
|
+
sensor: str,
|
15
|
+
) -> None:
|
16
|
+
"""
|
17
|
+
Search Sentinel imagery
|
18
|
+
"""
|
19
|
+
evaluate_sentinel_parameters(
|
20
|
+
sensor, time_interval, bounding_box, output_needed=False
|
21
|
+
)
|
22
|
+
|
23
|
+
client = SHClient()
|
24
|
+
parameters = SH_PARAMETERS_DICT[sensor]()
|
25
|
+
|
26
|
+
return client.search_data(bounding_box, time_interval, parameters)
|
@@ -1,4 +1,8 @@
|
|
1
|
+
"""
|
2
|
+
Sentinel-Hub data access module.
|
3
|
+
"""
|
4
|
+
|
1
5
|
from .client import SHClient
|
2
6
|
from .parameters import SHParameters, SH_PARAMETERS_DICT
|
3
7
|
from .evalscripts import EvalScripts
|
4
|
-
from .utils import evaluate_sentinel_parameters, imagery_from_tmp_to_dir
|
8
|
+
from .utils import evaluate_sentinel_parameters, imagery_from_tmp_to_dir
|