eotdl 2025.2.10__py3-none-any.whl → 2025.4.2__py3-none-any.whl

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.
Files changed (61) hide show
  1. eotdl/__init__.py +1 -1
  2. eotdl/access/__init__.py +13 -3
  3. eotdl/access/download.py +47 -14
  4. eotdl/access/search.py +33 -5
  5. eotdl/access/sentinelhub/__init__.py +6 -2
  6. eotdl/access/sentinelhub/client.py +7 -6
  7. eotdl/access/sentinelhub/evalscripts.py +266 -0
  8. eotdl/access/sentinelhub/parameters.py +101 -23
  9. eotdl/access/sentinelhub/utils.py +54 -15
  10. eotdl/cli.py +2 -2
  11. eotdl/commands/datasets.py +28 -31
  12. eotdl/commands/models.py +27 -30
  13. eotdl/commands/stac.py +57 -0
  14. eotdl/curation/__init__.py +0 -8
  15. eotdl/curation/stac/__init__.py +1 -8
  16. eotdl/curation/stac/api.py +58 -0
  17. eotdl/curation/stac/stac.py +31 -341
  18. eotdl/datasets/__init__.py +2 -2
  19. eotdl/datasets/ingest.py +36 -161
  20. eotdl/datasets/retrieve.py +0 -9
  21. eotdl/datasets/stage.py +64 -0
  22. eotdl/files/__init__.py +0 -2
  23. eotdl/files/ingest.bck +178 -0
  24. eotdl/files/ingest.py +237 -166
  25. eotdl/{datasets → files}/metadata.py +16 -17
  26. eotdl/models/__init__.py +1 -1
  27. eotdl/models/ingest.py +35 -158
  28. eotdl/models/stage.py +63 -0
  29. eotdl/repos/APIRepo.py +1 -1
  30. eotdl/repos/DatasetsAPIRepo.py +56 -43
  31. eotdl/repos/FilesAPIRepo.py +260 -167
  32. eotdl/repos/ModelsAPIRepo.py +50 -42
  33. eotdl/repos/STACAPIRepo.py +40 -0
  34. eotdl/repos/__init__.py +1 -0
  35. eotdl/tools/time_utils.py +3 -3
  36. {eotdl-2025.2.10.dist-info → eotdl-2025.4.2.dist-info}/METADATA +1 -1
  37. eotdl-2025.4.2.dist-info/RECORD +66 -0
  38. eotdl/curation/stac/assets.py +0 -110
  39. eotdl/curation/stac/dataframe.py +0 -172
  40. eotdl/curation/stac/dataframe_bck.py +0 -253
  41. eotdl/curation/stac/dataframe_labeling.py +0 -63
  42. eotdl/curation/stac/extensions/__init__.py +0 -23
  43. eotdl/curation/stac/extensions/base.py +0 -30
  44. eotdl/curation/stac/extensions/dem.py +0 -18
  45. eotdl/curation/stac/extensions/eo.py +0 -117
  46. eotdl/curation/stac/extensions/label/__init__.py +0 -7
  47. eotdl/curation/stac/extensions/label/base.py +0 -136
  48. eotdl/curation/stac/extensions/label/image_name_labeler.py +0 -203
  49. eotdl/curation/stac/extensions/label/scaneo.py +0 -219
  50. eotdl/curation/stac/extensions/ml_dataset.py +0 -648
  51. eotdl/curation/stac/extensions/projection.py +0 -44
  52. eotdl/curation/stac/extensions/raster.py +0 -53
  53. eotdl/curation/stac/extensions/sar.py +0 -55
  54. eotdl/curation/stac/extent.py +0 -158
  55. eotdl/curation/stac/parsers.py +0 -61
  56. eotdl/datasets/download.py +0 -104
  57. eotdl/files/list_files.py +0 -13
  58. eotdl/models/metadata.py +0 -43
  59. eotdl-2025.2.10.dist-info/RECORD +0 -81
  60. {eotdl-2025.2.10.dist-info → eotdl-2025.4.2.dist-info}/WHEEL +0 -0
  61. {eotdl-2025.2.10.dist-info → eotdl-2025.4.2.dist-info}/entry_points.txt +0 -0
@@ -11,190 +11,283 @@ class FilesAPIRepo(APIRepo):
11
11
  def __init__(self, url=None):
12
12
  super().__init__(url)
13
13
 
14
- def ingest_files_batch(
15
- self,
16
- batch, # ziped batch of files
17
- checksums,
18
- dataset_or_model_id,
19
- user,
20
- endpoint,
21
- version=None,
22
- ):
23
- url = self.url + f"{endpoint}/{dataset_or_model_id}/batch"
24
- if version is not None:
25
- url += "?version=" + str(version)
26
- reponse = requests.post(
27
- url,
28
- files={"batch": ("batch.zip", batch)},
29
- data={"checksums": checksums},
30
- headers=self.generate_headers(user),
31
- )
32
- return self.format_response(reponse)
33
-
34
- def add_files_batch_to_version(
35
- self,
36
- batch,
37
- dataset_or_model_id,
38
- version,
39
- user,
40
- endpoint,
41
- ):
42
- reponse = requests.post(
43
- self.url + f"{endpoint}/{dataset_or_model_id}/files?version={str(version)}",
44
- data={
45
- "filenames": [f["path"] for f in batch],
46
- "checksums": [f["checksum"] for f in batch],
47
- },
48
- headers=self.generate_headers(user),
49
- )
50
- return self.format_response(reponse)
51
-
52
14
  def ingest_file(
53
- self, file, dataset_or_model_id, user, checksum, endpoint, version=None
15
+ self, file_path_or_bytes, file_name, dataset_or_model_id, user, endpoint, version=None
54
16
  ):
55
- # TODO: ingest file URL
56
17
  url = self.url + f"{endpoint}/{dataset_or_model_id}"
57
18
  if version is not None:
58
19
  url += "?version=" + str(version)
20
+ # get a presigned url to upload the file directly to the bucket
59
21
  reponse = requests.post(
60
22
  url,
61
- files={"file": open(file, "rb")},
62
- data={"checksum": checksum},
23
+ json={
24
+ "file_name": file_name,
25
+ # "file_size": files_size,
26
+ # "checksum": checksum
27
+ },
63
28
  headers=self.generate_headers(user),
64
29
  )
65
- return self.format_response(reponse)
30
+ data, error = self.format_response(reponse)
31
+ if error:
32
+ raise Exception(error)
33
+ # ingest the file
34
+ error = None
35
+ try:
36
+ presigned_url = data["presigned_url"]
37
+ if isinstance(file_path_or_bytes, (str, bytes)):
38
+ if isinstance(file_path_or_bytes, str):
39
+ # Handle file path
40
+ with open(file_path_or_bytes, 'rb') as f:
41
+ file_data = f.read()
42
+ else:
43
+ # Handle bytes directly
44
+ file_data = file_path_or_bytes
45
+ # Send file data to presigned URL
46
+ response = requests.put(presigned_url, data=file_data)
47
+ response.raise_for_status()
48
+ else:
49
+ raise TypeError("file_path_or_bytes must be either a file path string or bytes")
50
+ except Exception as e:
51
+ error = str(e)
52
+ return data, error
66
53
 
67
- def retrieve_files(self, dataset_or_model_id, endpoint, version=None):
68
- url = f"{self.url}{endpoint}/{dataset_or_model_id}/files"
69
- if version is not None:
70
- url += "?version=" + str(version)
71
- response = requests.get(url)
72
- return self.format_response(response)
73
-
74
- def download_file(
54
+ def stage_file(
75
55
  self,
76
56
  dataset_or_model_id,
77
57
  file_name,
78
58
  user,
79
59
  path,
80
- file_version,
81
60
  endpoint="datasets",
82
61
  progress=False,
83
62
  ):
84
- url = self.url + f"{endpoint}/{dataset_or_model_id}/download/{file_name}"
85
- if file_version is not None:
86
- url += "?version=" + str(file_version)
87
- return self.download_file_url(url, file_name, path, user, progress=progress)
88
-
89
- def download_file_url(self, url, filename, path, user, progress=False):
90
- headers = self.generate_headers(user)
91
- path = f"{path}/{filename}"
92
- for i in range(1, len(path.split("/")) - 1):
93
- # print("/".join(path.split("/")[: i + 1]))
94
- os.makedirs("/".join(path.split("/")[: i + 1]), exist_ok=True)
95
- with requests.get(url, headers=headers, stream=True) as r:
96
- r.raise_for_status()
97
- total_size = int(r.headers.get("content-length", 0))
98
- block_size = 1024 * 1024 * 10
99
- progress = progress and total_size > 1024 * 1024 * 16
100
- if progress:
101
- progress_bar = tqdm(
102
- total=total_size,
103
- unit="iB",
104
- unit_scale=True,
105
- unit_divisor=1024,
106
- position=1,
107
- )
108
- with open(path, "wb") as f:
109
- for chunk in r.iter_content(block_size):
110
- if progress:
111
- progress_bar.update(len(chunk))
112
- if chunk:
113
- f.write(chunk)
114
- if progress:
115
- progress_bar.close()
116
- return path
117
-
118
- def prepare_large_upload(
119
- self, filename, dataset_or_model_id, checksum, user, endpoint
120
- ):
121
- response = requests.post(
122
- self.url + f"{endpoint}/{dataset_or_model_id}/uploadId",
123
- json={"filname": filename, "checksum": checksum},
124
- headers=self.generate_headers(user),
125
- )
126
- if response.status_code != 200:
127
- raise Exception(response.json()["detail"])
128
- data = response.json()
129
- upload_id, parts = (
130
- data["upload_id"],
131
- data["parts"] if "parts" in data else [],
132
- )
133
- return upload_id, parts
134
-
135
- def get_chunk_size(self, content_size):
136
- # adapt chunk size to content size to avoid S3 limits (10000 parts, 500MB per part, 5TB per object)
137
- chunk_size = 1024 * 1024 * 10 # 10 MB (up to 100 GB, 10000 parts)
138
- if content_size >= 1024 * 1024 * 1024 * 100: # 100 GB
139
- chunk_size = 1024 * 1024 * 100 # 100 MB (up to 1 TB, 10000 parts)
140
- elif content_size >= 1024 * 1024 * 1024 * 1000: # 1 TB
141
- chunk_size = 1024 * 1024 * 500 # 0.5 GB (up to 5 TB, 10000 parts)
142
- return chunk_size
143
-
144
- def read_in_chunks(self, file_object, CHUNK_SIZE):
145
- while True:
146
- data = file_object.read(CHUNK_SIZE)
147
- if not data:
148
- break
149
- yield data
150
-
151
- def ingest_large_file(
152
- self, file_path, files_size, upload_id, user, parts, endpoint
63
+ url = self.url + f"{endpoint}/{dataset_or_model_id}/stage/{file_name}"
64
+ # if file_version is not None:
65
+ # url += "?version=" + str(file_version)
66
+ return self.stage_file_url(url, path, user)
67
+
68
+
69
+ def stage_file_url(
70
+ self,
71
+ url,
72
+ path,
73
+ user,
153
74
  ):
154
- print(endpoint)
155
- # content_path = os.path.abspath(file)
156
- # content_size = os.stat(content_path).st_size
157
- chunk_size = self.get_chunk_size(files_size)
158
- total_chunks = files_size // chunk_size
159
- # upload chunks sequentially
160
- pbar = tqdm(
161
- self.read_in_chunks(open(file_path, "rb"), chunk_size),
162
- total=total_chunks,
163
- )
164
- index = 0
165
- for chunk in pbar:
166
- part = index // chunk_size + 1
167
- offset = index + len(chunk)
168
- index = offset
169
- if part not in parts:
170
- checksum = hashlib.md5(chunk).hexdigest()
171
- response = requests.post(
172
- f"{self.url}{endpoint}/chunk/{upload_id}",
173
- files={"file": chunk},
174
- data={"part_number": part, "checksum": checksum},
175
- headers=self.generate_headers(user),
176
- )
177
- if response.status_code != 200:
178
- raise Exception(response.json()["detail"])
179
- pbar.set_description(
180
- "{:.2f}/{:.2f} MB".format(
181
- offset / 1024 / 1024, files_size / 1024 / 1024
182
- )
183
- )
184
- pbar.close()
185
- return
186
-
187
- def complete_upload(self, user, upload_id, version, endpoint):
188
- r = requests.post(
189
- f"{self.url}{endpoint}/complete/{upload_id}?version={version}",
190
- headers=self.generate_headers(user),
191
- )
192
- return self.format_response(r)
75
+ if '/stage/' in url: # asset is in EOTDL (can do better...)
76
+ file_name = url.split("/stage/")[-1]
77
+ reponse = requests.get(url, headers=self.generate_headers(user))
78
+ data, error = self.format_response(reponse)
79
+ if error:
80
+ raise Exception(error)
81
+ presigned_url = data["presigned_url"]
82
+ else:
83
+ file_name = url.split("//")[-1]
84
+ presigned_url = url
85
+ file_path = f"{path}/{file_name}"
86
+ for i in range(1, len(file_path.split("/")) - 1):
87
+ os.makedirs("/".join(file_path.split("/")[: i + 1]), exist_ok=True)
88
+ try:
89
+ response = requests.get(presigned_url)
90
+ response.raise_for_status() # This will raise an HTTPError for 4XX and 5XX status codes
91
+ with open(file_path, 'wb') as f:
92
+ f.write(response.content)
93
+ except requests.exceptions.HTTPError as e:
94
+ raise Exception(f"Failed to stage file: {str(e)}")
95
+ except Exception as e:
96
+ raise Exception(f"Unexpected error while staging file: {str(e)}")
97
+ return file_path
193
98
 
194
- def get_file_stream(self, dataset_id, filename, user, version=None):
195
- url = self.url + f"datasets/{dataset_id}/download/{filename}"
196
- if version is not None:
197
- url += "?version=" + str(version)
198
- headers = self.generate_headers(user)
199
- response = requests.get(url, headers=headers, stream=True)
200
- return BytesIO(response.content)
99
+ def generate_presigned_url(self, filename, dataset_or_model_id, user, endpoint="datasets"):
100
+ url = f"{self.url}{endpoint}/{dataset_or_model_id}/stage/{filename}"
101
+ reponse = requests.get(url, headers=self.generate_headers(user))
102
+ data, error = self.format_response(reponse)
103
+ if error:
104
+ # print("ERROR generate_presigned_url", error)
105
+ return None
106
+ return data["presigned_url"]
107
+
108
+ # can we download large files?
109
+
110
+ # with requests.get(presigned_url, headers=headers, stream=True) as r:
111
+ # r.raise_for_status()
112
+ # total_size = int(r.headers.get("content-length", 0))
113
+ # block_size = 1024 * 1024 * 10
114
+ # progress = progress and total_size > 1024 * 1024 * 16
115
+ # if progress:
116
+ # progress_bar = tqdm(
117
+ # total=total_size,
118
+ # unit="iB",
119
+ # unit_scale=True,
120
+ # unit_divisor=1024,
121
+ # position=1,
122
+ # )
123
+ # with open(path, "wb") as f:
124
+ # for chunk in r.iter_content(block_size):
125
+ # if progress:
126
+ # progress_bar.update(len(chunk))
127
+ # if chunk:
128
+ # f.write(chunk)
129
+ # if progress:
130
+ # progress_bar.close()
131
+ # return path
132
+
133
+
134
+
135
+ # def ingest_files_batch(
136
+ # self,
137
+ # batch, # ziped batch of files
138
+ # checksums,
139
+ # dataset_or_model_id,
140
+ # user,
141
+ # endpoint,
142
+ # version=None,
143
+ # ):
144
+ # url = self.url + f"{endpoint}/{dataset_or_model_id}/batch"
145
+ # if version is not None:
146
+ # url += "?version=" + str(version)
147
+ # reponse = requests.post(
148
+ # url,
149
+ # files={"batch": ("batch.zip", batch)},
150
+ # data={"checksums": checksums},
151
+ # headers=self.generate_headers(user),
152
+ # )
153
+ # return self.format_response(reponse)
154
+
155
+ # def add_files_batch_to_version(
156
+ # self,
157
+ # batch,
158
+ # dataset_or_model_id,
159
+ # version,
160
+ # user,
161
+ # endpoint,
162
+ # ):
163
+ # reponse = requests.post(
164
+ # self.url + f"{endpoint}/{dataset_or_model_id}/files?version={str(version)}",
165
+ # data={
166
+ # "filenames": [f["path"] for f in batch],
167
+ # "checksums": [f["checksum"] for f in batch],
168
+ # },
169
+ # headers=self.generate_headers(user),
170
+ # )
171
+ # return self.format_response(reponse)
172
+
173
+ # def retrieve_files(self, dataset_or_model_id, endpoint, version=None):
174
+ # url = f"{self.url}{endpoint}/{dataset_or_model_id}/files"
175
+ # if version is not None:
176
+ # url += "?version=" + str(version)
177
+ # response = requests.get(url)
178
+ # return self.format_response(response)
179
+
180
+
181
+
182
+ # def download_file_url(self, url, filename, path, user, progress=False):
183
+ # headers = self.generate_headers(user)
184
+ # path = f"{path}/{filename}"
185
+ # for i in range(1, len(path.split("/")) - 1):
186
+ # # print("/".join(path.split("/")[: i + 1]))
187
+ # os.makedirs("/".join(path.split("/")[: i + 1]), exist_ok=True)
188
+ # with requests.get(url, headers=headers, stream=True) as r:
189
+ # r.raise_for_status()
190
+ # total_size = int(r.headers.get("content-length", 0))
191
+ # block_size = 1024 * 1024 * 10
192
+ # progress = progress and total_size > 1024 * 1024 * 16
193
+ # if progress:
194
+ # progress_bar = tqdm(
195
+ # total=total_size,
196
+ # unit="iB",
197
+ # unit_scale=True,
198
+ # unit_divisor=1024,
199
+ # position=1,
200
+ # )
201
+ # with open(path, "wb") as f:
202
+ # for chunk in r.iter_content(block_size):
203
+ # if progress:
204
+ # progress_bar.update(len(chunk))
205
+ # if chunk:
206
+ # f.write(chunk)
207
+ # if progress:
208
+ # progress_bar.close()
209
+ # return path
210
+
211
+ # def prepare_large_upload(
212
+ # self, filename, dataset_or_model_id, checksum, user, endpoint
213
+ # ):
214
+ # response = requests.post(
215
+ # self.url + f"{endpoint}/{dataset_or_model_id}/uploadId",
216
+ # json={"filname": filename, "checksum": checksum},
217
+ # headers=self.generate_headers(user),
218
+ # )
219
+ # if response.status_code != 200:
220
+ # raise Exception(response.json()["detail"])
221
+ # data = response.json()
222
+ # upload_id, parts = (
223
+ # data["upload_id"],
224
+ # data["parts"] if "parts" in data else [],
225
+ # )
226
+ # return upload_id, parts
227
+
228
+ # def get_chunk_size(self, content_size):
229
+ # # adapt chunk size to content size to avoid S3 limits (10000 parts, 500MB per part, 5TB per object)
230
+ # chunk_size = 1024 * 1024 * 10 # 10 MB (up to 100 GB, 10000 parts)
231
+ # if content_size >= 1024 * 1024 * 1024 * 100: # 100 GB
232
+ # chunk_size = 1024 * 1024 * 100 # 100 MB (up to 1 TB, 10000 parts)
233
+ # elif content_size >= 1024 * 1024 * 1024 * 1000: # 1 TB
234
+ # chunk_size = 1024 * 1024 * 500 # 0.5 GB (up to 5 TB, 10000 parts)
235
+ # return chunk_size
236
+
237
+ # def read_in_chunks(self, file_object, CHUNK_SIZE):
238
+ # while True:
239
+ # data = file_object.read(CHUNK_SIZE)
240
+ # if not data:
241
+ # break
242
+ # yield data
243
+
244
+ # def ingest_large_file(
245
+ # self, file_path, files_size, upload_id, user, parts, endpoint
246
+ # ):
247
+ # print(endpoint)
248
+ # # content_path = os.path.abspath(file)
249
+ # # content_size = os.stat(content_path).st_size
250
+ # chunk_size = self.get_chunk_size(files_size)
251
+ # total_chunks = files_size // chunk_size
252
+ # # upload chunks sequentially
253
+ # pbar = tqdm(
254
+ # self.read_in_chunks(open(file_path, "rb"), chunk_size),
255
+ # total=total_chunks,
256
+ # )
257
+ # index = 0
258
+ # for chunk in pbar:
259
+ # part = index // chunk_size + 1
260
+ # offset = index + len(chunk)
261
+ # index = offset
262
+ # if part not in parts:
263
+ # checksum = hashlib.md5(chunk).hexdigest()
264
+ # response = requests.post(
265
+ # f"{self.url}{endpoint}/chunk/{upload_id}",
266
+ # files={"file": chunk},
267
+ # data={"part_number": part, "checksum": checksum},
268
+ # headers=self.generate_headers(user),
269
+ # )
270
+ # if response.status_code != 200:
271
+ # raise Exception(response.json()["detail"])
272
+ # pbar.set_description(
273
+ # "{:.2f}/{:.2f} MB".format(
274
+ # offset / 1024 / 1024, files_size / 1024 / 1024
275
+ # )
276
+ # )
277
+ # pbar.close()
278
+ # return
279
+
280
+ # def complete_upload(self, user, upload_id, version, endpoint):
281
+ # r = requests.post(
282
+ # f"{self.url}{endpoint}/complete/{upload_id}?version={version}",
283
+ # headers=self.generate_headers(user),
284
+ # )
285
+ # return self.format_response(r)
286
+
287
+ # def get_file_stream(self, dataset_id, filename, user, version=None):
288
+ # url = self.url + f"datasets/{dataset_id}/download/{filename}"
289
+ # if version is not None:
290
+ # url += "?version=" + str(version)
291
+ # headers = self.generate_headers(user)
292
+ # response = requests.get(url, headers=headers, stream=True)
293
+ # return BytesIO(response.content)
@@ -20,6 +20,10 @@ class ModelsAPIRepo(APIRepo):
20
20
  response = requests.get(url)
21
21
  return self.format_response(response)
22
22
 
23
+ def retrieve_model(self, name):
24
+ response = requests.get(self.url + "models?name=" + name)
25
+ return self.format_response(response)
26
+
23
27
  def create_model(self, metadata, user):
24
28
  response = requests.post(
25
29
  self.url + "models",
@@ -28,53 +32,57 @@ class ModelsAPIRepo(APIRepo):
28
32
  )
29
33
  return self.format_response(response)
30
34
 
31
- def retrieve_model(self, name):
32
- response = requests.get(self.url + "models?name=" + name)
33
- return self.format_response(response)
34
-
35
- def create_version(self, model_id, user):
35
+ def complete_ingestion(self, model_id, version, size, user):
36
36
  response = requests.post(
37
- self.url + "models/version/" + model_id,
37
+ self.url + "models/complete/" + model_id,
38
+ json={"version": version, "size": size},
38
39
  headers=self.generate_headers(user),
39
40
  )
40
41
  return self.format_response(response)
41
42
 
42
- def update_model(
43
- self, model_id, authors, source, license, thumbnail, content, user
44
- ):
45
- response = requests.put(
46
- self.url + f"models/{model_id}",
47
- json={
48
- "authors": authors,
49
- "source": source,
50
- "license": license,
51
- "thumbnail": thumbnail,
52
- "description": content,
53
- },
54
- headers=self.generate_headers(user),
55
- )
56
- return self.format_response(response)
43
+ # def create_version(self, model_id, user):
44
+ # response = requests.post(
45
+ # self.url + "models/version/" + model_id,
46
+ # headers=self.generate_headers(user),
47
+ # )
48
+ # return self.format_response(response)
57
49
 
58
- def create_stac_model(self, name, user):
59
- response = requests.post(
60
- self.url + "models/stac",
61
- json={"name": name},
62
- headers=self.generate_headers(user),
63
- )
64
- return self.format_response(response)
50
+ # def update_model(
51
+ # self, model_id, authors, source, license, thumbnail, content, user
52
+ # ):
53
+ # response = requests.put(
54
+ # self.url + f"models/{model_id}",
55
+ # json={
56
+ # "authors": authors,
57
+ # "source": source,
58
+ # "license": license,
59
+ # "thumbnail": thumbnail,
60
+ # "description": content,
61
+ # },
62
+ # headers=self.generate_headers(user),
63
+ # )
64
+ # return self.format_response(response)
65
65
 
66
- def ingest_stac(self, stac_json, model_id, user):
67
- response = requests.put(
68
- self.url + f"models/stac/{model_id}",
69
- json={"stac": stac_json},
70
- headers=self.generate_headers(user),
71
- )
72
- return self.format_response(response)
66
+ # def create_stac_model(self, name, user):
67
+ # response = requests.post(
68
+ # self.url + "models/stac",
69
+ # json={"name": name},
70
+ # headers=self.generate_headers(user),
71
+ # )
72
+ # return self.format_response(response)
73
+
74
+ # def ingest_stac(self, stac_json, model_id, user):
75
+ # response = requests.put(
76
+ # self.url + f"models/stac/{model_id}",
77
+ # json={"stac": stac_json},
78
+ # headers=self.generate_headers(user),
79
+ # )
80
+ # return self.format_response(response)
73
81
 
74
- def download_stac(self, model_id, user):
75
- url = self.url + "models/" + model_id + "/download"
76
- headers = self.generate_headers(user)
77
- response = requests.get(url, headers=headers)
78
- if response.status_code != 200:
79
- return None, response.json()["detail"]
80
- return gpd.GeoDataFrame.from_features(response.json()["features"]), None
82
+ # def download_stac(self, model_id, user):
83
+ # url = self.url + "models/" + model_id + "/download"
84
+ # headers = self.generate_headers(user)
85
+ # response = requests.get(url, headers=headers)
86
+ # if response.status_code != 200:
87
+ # return None, response.json()["detail"]
88
+ # return gpd.GeoDataFrame.from_features(response.json()["features"]), None
@@ -0,0 +1,40 @@
1
+ import requests
2
+ import geopandas as gpd
3
+
4
+ from ..repos import APIRepo
5
+
6
+
7
+ class STACAPIRepo(APIRepo):
8
+ def __init__(self, url=None):
9
+ super().__init__(url)
10
+
11
+ def status(self):
12
+ response = requests.get(self.url + "stac")
13
+ return self.format_response(response)
14
+
15
+ def collections(self):
16
+ response = requests.get(self.url + "stac/collections")
17
+ return self.format_response(response)
18
+
19
+ def collection(self, collection_id):
20
+ response = requests.get(self.url + f"stac/collections/{collection_id}")
21
+ return self.format_response(response)
22
+
23
+ def items(self, collection_id):
24
+ response = requests.get(self.url + f"stac/collections/{collection_id}/items")
25
+ return self.format_response(response)
26
+
27
+ def item(self, collection_id, item_id):
28
+ response = requests.get(self.url + f"stac/collections/{collection_id}/items/{item_id}")
29
+ return self.format_response(response)
30
+
31
+ def search(self, collection_id, query):
32
+ body = {"collection_id": collection_id}
33
+ if query is not None:
34
+ body["query"] = query
35
+ response = requests.post(self.url + f"stac/search", json=body)
36
+ return self.format_response(response)
37
+
38
+ def search_columns(self, collection_id):
39
+ response = requests.get(self.url + f"stac/search?collection={collection_id}")
40
+ return self.format_response(response)
eotdl/repos/__init__.py CHANGED
@@ -4,3 +4,4 @@ from .AuthAPIRepo import AuthAPIRepo
4
4
  from .DatasetsAPIRepo import DatasetsAPIRepo
5
5
  from .FilesAPIRepo import FilesAPIRepo
6
6
  from .ModelsAPIRepo import ModelsAPIRepo
7
+ from .STACAPIRepo import STACAPIRepo
eotdl/tools/time_utils.py CHANGED
@@ -131,11 +131,11 @@ def get_day_between(
131
131
  Get the day between two dates
132
132
  """
133
133
  if isinstance(from_date, str):
134
- from_date = datetime.strptime(from_date, "%Y-%m-%dT%H:%M:%SZ")
134
+ from_date = format_time_acquired(from_date)
135
135
  if isinstance(to_date, str):
136
- to_date = datetime.strptime(to_date, "%Y-%m-%dT%H:%M:%SZ")
136
+ to_date = format_time_acquired(to_date)
137
137
 
138
- date_between = from_date + timedelta(days=1)
138
+ date_between = from_date + (to_date - from_date) / 2
139
139
  date_between = date_between.strftime("%Y-%m-%d")
140
140
 
141
141
  return date_between
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: eotdl
3
- Version: 2025.2.10
3
+ Version: 2025.4.2
4
4
  Summary: Earth Observation Training Data Lab
5
5
  Author-email: earthpulse <it@earthpulse.es>
6
6
  License-Expression: MIT