eotdl 2025.3.25__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.
eotdl/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "2025.03.25"
1
+ __version__ = "2025.04.02"
eotdl/access/__init__.py CHANGED
@@ -2,6 +2,16 @@
2
2
  Data access module for eotdl package.
3
3
  """
4
4
 
5
- from .download import download_sentinel_imagery, search_and_download_sentinel_imagery
6
- from .search import search_sentinel_imagery
7
- from .sentinelhub.parameters import SUPPORTED_SENSORS
5
+ from .download import (
6
+ download_sentinel_imagery,
7
+ search_and_download_sentinel_imagery,
8
+ advanced_imagery_download,
9
+ )
10
+ from .search import search_sentinel_imagery, advanced_imagery_search
11
+ from .sentinelhub.parameters import (
12
+ SUPPORTED_COLLECTION_IDS,
13
+ DATA_COLLECTION_ID,
14
+ get_default_parameters,
15
+ OUTPUT_FORMAT,
16
+ )
17
+ from .sentinelhub.evalscripts import EvalScripts
eotdl/access/download.py CHANGED
@@ -2,48 +2,82 @@
2
2
  Download imagery
3
3
  """
4
4
 
5
- from datetime import datetime
5
+ from datetime import datetime, timedelta
6
6
  from typing import Union, List, Optional
7
7
 
8
8
  from .sentinelhub import (
9
9
  SHClient,
10
- SH_PARAMETERS_DICT,
11
10
  evaluate_sentinel_parameters,
12
11
  imagery_from_tmp_to_dir,
12
+ get_default_parameters,
13
+ SHParameters,
14
+ filter_times,
13
15
  )
14
- from .search import search_sentinel_imagery
16
+ from .search import advanced_imagery_search
15
17
 
16
18
 
17
19
  def download_sentinel_imagery(
18
20
  output: str,
19
21
  time_interval: Union[str, datetime, List[Union[str, datetime]]],
20
22
  bounding_box: List[Union[int, float]],
21
- sensor: str,
23
+ collection_id: str,
22
24
  name: Optional[str] = None,
23
25
  ) -> None:
24
26
  """
25
27
  Download Sentinel imagery
26
28
  """
27
- evaluate_sentinel_parameters(sensor, time_interval, bounding_box, output)
29
+ evaluate_sentinel_parameters(time_interval, bounding_box, collection_id, output)
30
+ parameters = get_default_parameters(collection_id)
31
+ return advanced_imagery_download(
32
+ output, time_interval, bounding_box, parameters, name
33
+ )
28
34
 
29
- client = SHClient()
30
- parameters = SH_PARAMETERS_DICT[sensor]()
31
35
 
32
- results = search_sentinel_imagery(time_interval, bounding_box, sensor)
33
- timestamps = [date.strftime("%Y-%m-%d") for date in results.get_timestamps()]
36
+ def advanced_imagery_download(
37
+ output: str,
38
+ time_interval: Union[str, datetime, List[Union[str, datetime]]],
39
+ bounding_box: List[Union[int, float]],
40
+ parameters: SHParameters,
41
+ name: Optional[str] = None,
42
+ ) -> None:
43
+ """
44
+ Advanced imagery download
45
+ """
46
+ evaluate_sentinel_parameters(
47
+ time_interval, bounding_box, output=output, parameters=parameters
48
+ )
49
+ client = SHClient(parameters.BASE_URL)
50
+
51
+ results = advanced_imagery_search(time_interval, bounding_box, parameters)
52
+ timestamps = results.get_timestamps()
53
+ time_difference = timedelta(hours=1)
54
+ filtered_timestamps = filter_times(timestamps, time_difference)
34
55
 
35
56
  requests_list = []
36
- for date in timestamps:
37
- requests_list.append(client.request_data(date, bounding_box, parameters))
57
+ for date in filtered_timestamps:
58
+ timestamp_interval = (date - time_difference, date + time_difference)
59
+ requests_list.append(
60
+ client.request_data(timestamp_interval, bounding_box, parameters)
61
+ )
38
62
  if len(requests_list) == 0:
39
- print(f"No images found for {sensor} in the specified time: {time_interval}")
63
+ print(
64
+ f"No images found for {parameters.DATA_COLLECTION.api_id} in the specified time: {time_interval}"
65
+ )
40
66
  return
41
67
  elif len(requests_list) <= 2:
42
68
  bulk = False
43
69
  else:
44
70
  bulk = True
71
+
45
72
  client.download_data(requests_list)
46
- imagery_from_tmp_to_dir(output, name=name, bulk=bulk)
73
+ imagery_from_tmp_to_dir(
74
+ output,
75
+ bounding_box,
76
+ client.tmp_dir,
77
+ name=name,
78
+ bulk=bulk,
79
+ output_format=parameters.OUTPUT_FORMAT.value,
80
+ )
47
81
 
48
82
 
49
83
  def search_and_download_sentinel_imagery(
eotdl/access/search.py CHANGED
@@ -5,20 +5,48 @@ Search imagery
5
5
  from typing import Union, List
6
6
  from datetime import datetime
7
7
 
8
- from .sentinelhub import SHClient, SH_PARAMETERS_DICT, evaluate_sentinel_parameters
8
+ from .sentinelhub import (
9
+ SHClient,
10
+ get_default_parameters,
11
+ evaluate_sentinel_parameters,
12
+ SHParameters,
13
+ supports_cloud_coverage,
14
+ )
9
15
 
10
16
 
11
17
  def search_sentinel_imagery(
12
18
  time_interval: Union[str, datetime, List[Union[str, datetime]]],
13
19
  bounding_box: List[Union[int, float]],
14
- sensor: str,
20
+ collection_id: str,
15
21
  ) -> None:
16
22
  """
17
23
  Search Sentinel imagery
18
24
  """
19
25
  evaluate_sentinel_parameters(
20
- sensor, time_interval, bounding_box, output_needed=False
26
+ time_interval, bounding_box, collection_id, output_needed=False
21
27
  )
22
- client = SHClient()
23
- parameters = SH_PARAMETERS_DICT[sensor]()
28
+ parameters = get_default_parameters(collection_id)
29
+ client = SHClient(parameters.BASE_URL)
30
+ return client.search_data(bounding_box, time_interval, parameters)
31
+
32
+
33
+ def advanced_imagery_search(
34
+ time_interval: Union[str, datetime, List[Union[str, datetime]]],
35
+ bounding_box: List[Union[int, float]],
36
+ parameters: SHParameters,
37
+ ) -> None:
38
+ """
39
+ Advanced imagery search
40
+ """
41
+ evaluate_sentinel_parameters(
42
+ time_interval, bounding_box, output_needed=False, parameters=parameters
43
+ )
44
+
45
+ if (
46
+ supports_cloud_coverage(parameters.DATA_COLLECTION.api_id)
47
+ and parameters.MAX_CLOUD_COVERAGE
48
+ ):
49
+ parameters.FILTER = "eo:cloud_cover < " + str(parameters.MAX_CLOUD_COVERAGE)
50
+
51
+ client = SHClient(parameters.BASE_URL)
24
52
  return client.search_data(bounding_box, time_interval, parameters)
@@ -3,6 +3,10 @@ Sentinel-Hub data access module.
3
3
  """
4
4
 
5
5
  from .client import SHClient
6
- from .parameters import SHParameters, SH_PARAMETERS_DICT
6
+ from .parameters import (
7
+ SHParameters,
8
+ supports_cloud_coverage,
9
+ get_default_parameters,
10
+ )
7
11
  from .evalscripts import EvalScripts
8
- from .utils import evaluate_sentinel_parameters, imagery_from_tmp_to_dir
12
+ from .utils import evaluate_sentinel_parameters, imagery_from_tmp_to_dir, filter_times
@@ -3,6 +3,7 @@ Module for managing the Sentinel Hub configuration and data access
3
3
  """
4
4
 
5
5
  import json
6
+ from datetime import datetime
6
7
  from os import getenv
7
8
  from os.path import exists
8
9
  from sentinelhub import (
@@ -13,12 +14,11 @@ from sentinelhub import (
13
14
  CRS,
14
15
  SentinelHubRequest,
15
16
  SentinelHubDownloadClient,
16
- MimeType,
17
17
  )
18
+ import uuid
18
19
 
19
20
  from ...repos.AuthRepo import AuthRepo
20
21
  from .parameters import SHParameters
21
- from ...tools.time_utils import prepare_time_interval
22
22
  from ...tools.geo_utils import compute_image_size
23
23
 
24
24
 
@@ -27,7 +27,7 @@ class SHClient:
27
27
  Client class to manage the Sentinel Hub Python interface.
28
28
  """
29
29
 
30
- def __init__(self) -> None:
30
+ def __init__(self, base_url) -> None:
31
31
  """ """
32
32
  self.config = SHConfig()
33
33
  if getenv("SH_CLIENT_ID") and getenv("SH_CLIENT_SECRET"):
@@ -56,8 +56,9 @@ class SHClient:
56
56
  else:
57
57
  self.config.sh_client_id = creds["SH_CLIENT_ID"]
58
58
  self.config.sh_client_secret = creds["SH_CLIENT_SECRET"]
59
+ self.config.sh_base_url = base_url
59
60
  self.catalog = SentinelHubCatalog(config=self.config)
60
- self.tmp_dir = "/tmp/sentinelhub"
61
+ self.tmp_dir = "/tmp/sentinelhub/" + str(uuid.uuid4())
61
62
 
62
63
  def search_data(
63
64
  self, bounding_box: list, time_interval: list, parameters: SHParameters
@@ -76,12 +77,11 @@ class SHClient:
76
77
  return search_iterator
77
78
 
78
79
  def request_data(
79
- self, time_interval, bounding_box: list, parameters: SHParameters
80
+ self, time_interval: datetime, bounding_box: list, parameters: SHParameters
80
81
  ) -> list:
81
82
  """
82
83
  Request data from Sentinel Hub
83
84
  """
84
- time_interval = prepare_time_interval(time_interval)
85
85
  bounding_box, _ = compute_image_size(bounding_box, parameters)
86
86
 
87
87
  return SentinelHubRequest(
@@ -94,7 +94,9 @@ class SHClient:
94
94
  mosaicking_order=parameters.MOSAICKING_ORDER,
95
95
  )
96
96
  ],
97
- responses=[SentinelHubRequest.output_response("default", MimeType.TIFF)],
97
+ responses=[
98
+ SentinelHubRequest.output_response("default", parameters.OUTPUT_FORMAT)
99
+ ],
98
100
  bbox=bounding_box,
99
101
  size=bbox_to_dimensions(bounding_box, parameters.RESOLUTION),
100
102
  config=self.config,
@@ -109,5 +111,4 @@ class SHClient:
109
111
  requests = [requests]
110
112
  download_requests = [request.download_list[0] for request in requests]
111
113
  data = download_client.download(download_requests)
112
-
113
114
  return data
@@ -27,6 +27,28 @@ class EvalScripts:
27
27
  }
28
28
  """
29
29
 
30
+ SENTINEL_1_ENHANCED_VISUALIZATION = """
31
+ //VERSION=3
32
+ function setup() {
33
+ return {
34
+ input: ["VV", "VH", "dataMask"],
35
+ output: { bands: 4 }
36
+ }
37
+ }
38
+
39
+ function evaluatePixel(sample) {
40
+ var water_threshold = 25; //lower means more water
41
+
42
+ if (sample.VV / sample.VH > water_threshold) {
43
+ // watervis
44
+ return [sample.VV, 8 * sample.VV, 0.5 + 3 * sample.VV + 2000 * sample.VH, sample.dataMask];
45
+ } else {
46
+ // landvis
47
+ return [3 * sample.VV, 1.1 * sample.VV + 8.75 * sample.VH, 1.75 * sample.VH, sample.dataMask];
48
+ }
49
+ }
50
+ """
51
+
30
52
  SENTINEL_2_L1C = """
31
53
  //VERSION=3
32
54
  function setup() {
@@ -113,6 +135,26 @@ class EvalScripts:
113
135
  }
114
136
  """
115
137
 
138
+ SENTINEL_2_L2A_TRUE_COLOR = """
139
+ //VERSION=3
140
+ let minVal = 0.0;
141
+ let maxVal = 0.4;
142
+
143
+ let viz = new HighlightCompressVisualizer(minVal, maxVal);
144
+
145
+ function setup() {
146
+ return {
147
+ input: ["B04", "B03", "B02","dataMask"],
148
+ output: { bands: 4 }
149
+ };
150
+ }
151
+
152
+ function evaluatePixel(samples) {
153
+ let val = [samples.B04, samples.B03, samples.B02,samples.dataMask];
154
+ return viz.processList(val);
155
+ }
156
+ """
157
+
116
158
  DEM = """
117
159
  //VERSION=3
118
160
 
@@ -130,3 +172,227 @@ class EvalScripts:
130
172
  return [sample.DEM]
131
173
  }
132
174
  """
175
+
176
+ DEM_TOPOGRAPHIC = """
177
+ //VERSION=3
178
+ // To set custom max and min values, set
179
+ // defaultVis to false and choose your max and
180
+ // min values. The color map will then be scaled
181
+ // to those max and min values
182
+ const defaultVis = true
183
+ const max = 9000
184
+ const min = -9000
185
+
186
+ function setup() {
187
+ return {
188
+ input: ["DEM", "dataMask"],
189
+ output: [
190
+ { id: "default", bands: 4, sampleTYPE: 'AUTO' },
191
+ { id: "index", bands: 1, sampleType: 'FLOAT32' },
192
+ { id: "dataMask", bands: 1 }
193
+ ]
194
+ };
195
+ }
196
+
197
+ function updateMap(max, min) {
198
+ const numIntervals = map.length
199
+ const intervalLength = (max - min) / (numIntervals - 1);
200
+ for (let i = 0; i < numIntervals; i++) {
201
+ map[i][0] = max - intervalLength * i
202
+ }
203
+ }
204
+
205
+ const map = [
206
+ [8000, 0xffffff],
207
+ [7000, 0xf2f2f2],
208
+ [6000, 0xe5e5e5],
209
+ [5500, 0x493811],
210
+ [5000, 0x5e4c26],
211
+ [4500, 0x726038],
212
+ [4000, 0x87724c],
213
+ [3500, 0x998760],
214
+ [3000, 0xad9b75],
215
+ [2500, 0xc1af89],
216
+ [2000, 0xd6c49e],
217
+ [1500, 0xead8af],
218
+ [1000, 0xfcedbf],
219
+ [900, 0xaadda0],
220
+ [800, 0xa5d69b],
221
+ [700, 0x96ce8e],
222
+ [600, 0x84c17a],
223
+ [500, 0x7aba70],
224
+ [400, 0x72b266],
225
+ [300, 0x5ea354],
226
+ [200, 0x4c933f],
227
+ [100, 0x3d873d],
228
+ [75, 0x357c3a],
229
+ [50, 0x2d722d],
230
+ [25, 0x266821],
231
+ [10, 0x1e5e14],
232
+ [0.01, 0x165407]
233
+ ];
234
+
235
+ if (!defaultVis) updateMap(max, min);
236
+ // add ocean color
237
+ map.push([-10000, 0x0f0f8c])
238
+ const visualizer = new ColorMapVisualizer(map);
239
+
240
+ function evaluatePixel(sample) {
241
+ let val = sample.DEM;
242
+ let imgVals = visualizer.process(val)
243
+
244
+ // Return the 4 inputs and define content for each one
245
+ return {
246
+ default: [...imgVals, sample.dataMask],
247
+ index: [val],
248
+ dataMask: [sample.dataMask]
249
+ };
250
+ }
251
+ """
252
+
253
+ HLS_FALSE_COLOR = """
254
+ //VERSION=3
255
+
256
+ function setup() {
257
+ return {
258
+ input: ["NIR_Narrow", "Red", "Green", "dataMask"],
259
+ output: { bands: 3 }
260
+ };
261
+ }
262
+
263
+ function evaluatePixel(sample) {
264
+ return [2.5 * sample.NIR_Narrow, 2.5 * sample.Red, 2.5 * sample.Green, sample.dataMask];
265
+ }
266
+ """
267
+
268
+ HLS_TRUE_COLOR = """
269
+ //VERSION=3
270
+
271
+ function setup() {
272
+ return {
273
+ input: ["Blue","Green","Red", "dataMask"],
274
+ output: { bands: 4 }
275
+ };
276
+ }
277
+
278
+ function evaluatePixel(sample) {
279
+ return [2.5 * sample.Red, 2.5 * sample.Green, 2.5 * sample.Blue, sample.dataMask];
280
+ }
281
+ """
282
+
283
+ HLS_NDVI = """
284
+ //VERSION=3
285
+
286
+ function setup() {
287
+ return {
288
+ input: ["NIR_Narrow", "Red", "dataMask"],
289
+ output: { bands: 4 }
290
+ };
291
+ }
292
+
293
+ function evaluatePixel(sample) {
294
+ var ndvi = (sample.NIR_Narrow - sample.Red) / (sample.NIR_Narrow + sample.Red)
295
+
296
+ if (ndvi<-1.1) return [0,0,0, sample.dataMask];
297
+ else if (ndvi<-0.2) return [0.75,0.75,0.75, sample.dataMask];
298
+ else if (ndvi<-0.1) return [0.86,0.86,0.86, sample.dataMask];
299
+ else if (ndvi<0) return [1,1,0.88, sample.dataMask];
300
+ else if (ndvi<0.025) return [1,0.98,0.8, sample.dataMask];
301
+ else if (ndvi<0.05) return [0.93,0.91,0.71, sample.dataMask];
302
+ else if (ndvi<0.075) return [0.87,0.85,0.61, sample.dataMask];
303
+ else if (ndvi<0.1) return [0.8,0.78,0.51, sample.dataMask];
304
+ else if (ndvi<0.125) return [0.74,0.72,0.42, sample.dataMask];
305
+ else if (ndvi<0.15) return [0.69,0.76,0.38, sample.dataMask];
306
+ else if (ndvi<0.175) return [0.64,0.8,0.35, sample.dataMask];
307
+ else if (ndvi<0.2) return [0.57,0.75,0.32, sample.dataMask];
308
+ else if (ndvi<0.25) return [0.5,0.7,0.28, sample.dataMask];
309
+ else if (ndvi<0.3) return [0.44,0.64,0.25, sample.dataMask];
310
+ else if (ndvi<0.35) return [0.38,0.59,0.21, sample.dataMask];
311
+ else if (ndvi<0.4) return [0.31,0.54,0.18, sample.dataMask];
312
+ else if (ndvi<0.45) return [0.25,0.49,0.14, sample.dataMask];
313
+ else if (ndvi<0.5) return [0.19,0.43,0.11, sample.dataMask];
314
+ else if (ndvi<0.55) return [0.13,0.38,0.07, sample.dataMask];
315
+ else if (ndvi<0.6) return [0.06,0.33,0.04, sample.dataMask];
316
+ else return [0,0.27,0, sample.dataMask];
317
+ }
318
+ """
319
+
320
+ LANDSAT_OT_L2_TRUE_COLOR = """
321
+ //VERSION=3
322
+
323
+ let minVal = 0.0;
324
+ let maxVal = 0.4;
325
+
326
+ let viz = new DefaultVisualizer(minVal, maxVal);
327
+
328
+ function evaluatePixel(samples) {
329
+ let val = [samples.B04, samples.B03, samples.B02, samples.dataMask];
330
+ return viz.processList(val);
331
+ }
332
+
333
+ function setup() {
334
+ return {
335
+ input: [{
336
+ bands: [ "B02", "B03", "B04", "dataMask" ]
337
+ }],
338
+ output: { bands: 4 } }
339
+ }
340
+ """
341
+ LANDSAT_OT_L2_NDVI = """
342
+ //VERSION=3
343
+
344
+ function setup() {
345
+ return {
346
+ input: ["B03", "B04", "B05", "dataMask"],
347
+ output: [
348
+ { id: "default", bands: 4 },
349
+ { id: "index", bands: 1, sampleType: "FLOAT32" },
350
+ { id: "eobrowserStats", bands: 2 },
351
+ { id: "dataMask", bands: 1 },
352
+ ],
353
+ };
354
+ }
355
+
356
+ function evaluatePixel(samples) {
357
+ let val = index(samples.B05, samples.B04);
358
+ let imgVals = null;
359
+ // The library for tiffs works well only if there is only one channel returned.
360
+ // So we encode the "no data" as NaN here and ignore NaNs on frontend.
361
+ const indexVal = samples.dataMask === 1 && val >= -1 && val <= 1 ? val : NaN;
362
+
363
+ if (val < -1.1) imgVals = [0, 0, 0, samples.dataMask];
364
+ else if (val < -0.2) imgVals = [0.75, 0.75, 0.75, samples.dataMask];
365
+ else if (val < -0.1) imgVals = [0.86, 0.86, 0.86, samples.dataMask];
366
+ else if (val < 0) imgVals = [1, 1, 0.88, samples.dataMask];
367
+ else if (val < 0.025) imgVals = [1, 0.98, 0.8, samples.dataMask];
368
+ else if (val < 0.05) imgVals = [0.93, 0.91, 0.71, samples.dataMask];
369
+ else if (val < 0.075) imgVals = [0.87, 0.85, 0.61, samples.dataMask];
370
+ else if (val < 0.1) imgVals = [0.8, 0.78, 0.51, samples.dataMask];
371
+ else if (val < 0.125) imgVals = [0.74, 0.72, 0.42, samples.dataMask];
372
+ else if (val < 0.15) imgVals = [0.69, 0.76, 0.38, samples.dataMask];
373
+ else if (val < 0.175) imgVals = [0.64, 0.8, 0.35, samples.dataMask];
374
+ else if (val < 0.2) imgVals = [0.57, 0.75, 0.32, samples.dataMask];
375
+ else if (val < 0.25) imgVals = [0.5, 0.7, 0.28, samples.dataMask];
376
+ else if (val < 0.3) imgVals = [0.44, 0.64, 0.25, samples.dataMask];
377
+ else if (val < 0.35) imgVals = [0.38, 0.59, 0.21, samples.dataMask];
378
+ else if (val < 0.4) imgVals = [0.31, 0.54, 0.18, samples.dataMask];
379
+ else if (val < 0.45) imgVals = [0.25, 0.49, 0.14, samples.dataMask];
380
+ else if (val < 0.5) imgVals = [0.19, 0.43, 0.11, samples.dataMask];
381
+ else if (val < 0.55) imgVals = [0.13, 0.38, 0.07, samples.dataMask];
382
+ else if (val < 0.6) imgVals = [0.06, 0.33, 0.04, samples.dataMask];
383
+ else imgVals = [0, 0.27, 0, samples.dataMask];
384
+
385
+ return {
386
+ default: imgVals,
387
+ index: [indexVal],
388
+ eobrowserStats: [val, isCloud(samples) ? 1 : 0],
389
+ dataMask: [samples.dataMask],
390
+ };
391
+ }
392
+
393
+ function isCloud(samples) {
394
+ const NGDR = index(samples.B03, samples.B04);
395
+ const bRatio = (samples.B03 - 0.175) / (0.39 - 0.175);
396
+ return bRatio > 1 || (bRatio > 0 && NGDR > 0);
397
+ }
398
+ """