tilebox-storage 0.47.0__tar.gz → 0.49.0b1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilebox-storage
3
- Version: 0.47.0
3
+ Version: 0.49.0b1
4
4
  Summary: Storage client for Tilebox
5
5
  Project-URL: Homepage, https://tilebox.com
6
6
  Project-URL: Documentation, https://docs.tilebox.com/
@@ -2,6 +2,7 @@ from pathlib import Path
2
2
 
3
3
  from tilebox.storage.aio import ASFStorageClient as _ASFStorageClient
4
4
  from tilebox.storage.aio import CopernicusStorageClient as _CopernicusStorageClient
5
+ from tilebox.storage.aio import LocalFileSystemStorageClient as _LocalFileSystemStorageClient
5
6
  from tilebox.storage.aio import UmbraStorageClient as _UmbraStorageClient
6
7
  from tilebox.storage.aio import USGSLandsatStorageClient as _USGSLandsatStorageClient
7
8
 
@@ -66,3 +67,14 @@ class USGSLandsatStorageClient(_USGSLandsatStorageClient):
66
67
  """
67
68
  super().__init__(cache_directory)
68
69
  self._syncify()
70
+
71
+
72
+ class LocalFileSystemStorageClient(_LocalFileSystemStorageClient):
73
+ def __init__(self, root: Path) -> None:
74
+ """A tilebox storage client for accessing data on a local file system, or a mounted network file system.
75
+
76
+ Args:
77
+ root: The root directory of the file system to access.
78
+ """
79
+ super().__init__(root)
80
+ self._syncify()
@@ -24,6 +24,7 @@ from _tilebox.grpc.aio.syncify import Syncifiable
24
24
  from tilebox.storage.granule import (
25
25
  ASFStorageGranule,
26
26
  CopernicusStorageGranule,
27
+ LocationStorageGranule,
27
28
  UmbraStorageGranule,
28
29
  USGSLandsatStorageGranule,
29
30
  )
@@ -241,6 +242,10 @@ def _display_quicklook(image_data: bytes | Path, width: int, height: int, image_
241
242
 
242
243
 
243
244
  class StorageClient(Syncifiable):
245
+ """Base class for all storage clients."""
246
+
247
+
248
+ class CachingStorageClient(StorageClient):
244
249
  def __init__(self, cache_directory: Path | None) -> None:
245
250
  self._cache = cache_directory
246
251
 
@@ -323,7 +328,7 @@ async def _download_object(
323
328
  return output_path
324
329
 
325
330
 
326
- class ASFStorageClient(StorageClient):
331
+ class ASFStorageClient(CachingStorageClient):
327
332
  def __init__(self, user: str, password: str, cache_directory: Path = Path.home() / ".cache" / "tilebox") -> None:
328
333
  """A tilebox storage client that downloads data from the Alaska Satellite Facility.
329
334
 
@@ -415,7 +420,7 @@ class ASFStorageClient(StorageClient):
415
420
  """
416
421
  granule = ASFStorageGranule.from_data(datapoint)
417
422
  if Image is None:
418
- raise ImportError("IPython is not available, please use download_preview instead.")
423
+ raise ImportError("IPython is not available, please use download_quicklook instead.")
419
424
  quicklook = await self._download_quicklook(datapoint)
420
425
  _display_quicklook(quicklook, width, height, f"<code>Image {quicklook.name} © ASF {granule.time.year}</code>")
421
426
 
@@ -439,7 +444,7 @@ def _umbra_s3_prefix(datapoint: xr.Dataset | UmbraStorageGranule) -> str:
439
444
  return f"sar-data/tasks/{granule.location}/"
440
445
 
441
446
 
442
- class UmbraStorageClient(StorageClient):
447
+ class UmbraStorageClient(CachingStorageClient):
443
448
  _STORAGE_PROVIDER = "Umbra"
444
449
  _BUCKET = "umbra-open-data-catalog"
445
450
  _REGION = "us-west-2"
@@ -539,7 +544,7 @@ def _copernicus_s3_prefix(datapoint: xr.Dataset | CopernicusStorageGranule) -> s
539
544
  return granule.location.removeprefix("/eodata/")
540
545
 
541
546
 
542
- class CopernicusStorageClient(StorageClient):
547
+ class CopernicusStorageClient(CachingStorageClient):
543
548
  _STORAGE_PROVIDER = "CopernicusDataspace"
544
549
  _BUCKET = "eodata"
545
550
  _ENDPOINT_URL = "https://eodata.dataspace.copernicus.eu"
@@ -724,7 +729,7 @@ class CopernicusStorageClient(StorageClient):
724
729
  ValueError: If no quicklook is available for the given datapoint.
725
730
  """
726
731
  if Image is None:
727
- raise ImportError("IPython is not available, please use download_preview instead.")
732
+ raise ImportError("IPython is not available, please use download_quicklook instead.")
728
733
  granule = CopernicusStorageGranule.from_data(datapoint)
729
734
  quicklook = await self._download_quicklook(granule)
730
735
  _display_quicklook(quicklook, width, height, f"<code>{granule.granule_name} © ESA {granule.time.year}</code>")
@@ -750,7 +755,7 @@ def _landsat_s3_prefix(datapoint: xr.Dataset | USGSLandsatStorageGranule) -> str
750
755
  return granule.location.removeprefix("s3://usgs-landsat/")
751
756
 
752
757
 
753
- class USGSLandsatStorageClient(StorageClient):
758
+ class USGSLandsatStorageClient(CachingStorageClient):
754
759
  """
755
760
  A client for downloading USGS Landsat data from the usgs-landsat and usgs-landsat-ard S3 bucket.
756
761
 
@@ -883,7 +888,7 @@ class USGSLandsatStorageClient(StorageClient):
883
888
  ValueError: If no quicklook is available for the given datapoint.
884
889
  """
885
890
  if Image is None:
886
- raise ImportError("IPython is not available, please use download_preview instead.")
891
+ raise ImportError("IPython is not available, please use download_quicklook instead.")
887
892
  quicklook = await self._download_quicklook(datapoint)
888
893
  _display_quicklook(quicklook, width, height, f"<code>Image {quicklook.name} © USGS</code>")
889
894
 
@@ -901,3 +906,77 @@ class USGSLandsatStorageClient(StorageClient):
901
906
 
902
907
  await download_objects(self._store, prefix, [granule.thumbnail], output_folder, show_progress=False)
903
908
  return output_folder / granule.thumbnail
909
+
910
+
911
+ class LocalFileSystemStorageClient(StorageClient):
912
+ def __init__(self, root: Path) -> None:
913
+ """A tilebox storage client for accessing data on a local file system, or a mounted network file system.
914
+
915
+ Args:
916
+ root: The root directory of the file system to access.
917
+ """
918
+ super().__init__()
919
+ self._root = Path(root)
920
+
921
+ async def list_objects(self, datapoint: xr.Dataset | LocationStorageGranule) -> list[str]:
922
+ """List all available objects for a given datapoint."""
923
+ granule = LocationStorageGranule.from_data(datapoint)
924
+ granule_path = self._root / granule.location
925
+ return [p.relative_to(granule_path).as_posix() for p in granule_path.rglob("**/*") if p.is_file()]
926
+
927
+ async def download(
928
+ self,
929
+ datapoint: xr.Dataset | LocationStorageGranule,
930
+ ) -> Path:
931
+ """No-op download method, as the data is already on the local file system.
932
+
933
+ Args:
934
+ datapoint: The datapoint to locate the data for in the local file system.
935
+
936
+ Returns:
937
+ The path to the data on the local file system.
938
+ """
939
+ granule = LocationStorageGranule.from_data(datapoint)
940
+ granule_path = self._root / granule.location
941
+ if not granule_path.exists():
942
+ raise ValueError(f"Data not found on the local file system: {granule_path}")
943
+ return granule_path
944
+
945
+ async def _download_quicklook(self, datapoint: xr.Dataset | LocationStorageGranule) -> Path:
946
+ granule = LocationStorageGranule.from_data(datapoint)
947
+ if granule.thumbnail is None:
948
+ raise ValueError(f"No quicklook available for {granule.location}")
949
+ quicklook_path = self._root / granule.thumbnail
950
+ if not quicklook_path.exists():
951
+ raise ValueError(f"Quicklook not found on the local file system: {quicklook_path}")
952
+ return quicklook_path
953
+
954
+ async def download_quicklook(self, datapoint: xr.Dataset | LocationStorageGranule) -> Path:
955
+ """No-op download_quicklook method, as the quicklook image is already on the local file system.
956
+
957
+ Args:
958
+ datapoint: The datapoint to locate the quicklook image for in the local file system.
959
+
960
+ Returns:
961
+ The path to the data on the local file system.
962
+
963
+ Raises:
964
+ ValueError: If no quicklook image is available for the given datapoint, or if the quicklook image is not
965
+ found on the local file system.
966
+ """
967
+ return await self._download_quicklook(datapoint)
968
+
969
+ async def quicklook(
970
+ self, datapoint: xr.Dataset | LocationStorageGranule, width: int = 600, height: int = 600
971
+ ) -> None:
972
+ """Display the quicklook image for a given datapoint.
973
+
974
+ Args:
975
+ datapoint: The datapoint to display the quicklook for.
976
+ width: Display width of the image in pixels. Defaults to 600.
977
+ height: Display height of the image in pixels. Defaults to 600.
978
+ """
979
+ quicklook_path = await self._download_quicklook(datapoint)
980
+ if Image is None:
981
+ raise ImportError("IPython is not available, please use download_quicklook instead.")
982
+ _display_quicklook(quicklook_path, width, height, None)
@@ -183,3 +183,28 @@ class USGSLandsatStorageGranule:
183
183
  dataset.location.item().replace("s3://usgs-landsat-ard/", "s3://usgs-landsat/"),
184
184
  thumbnail,
185
185
  )
186
+
187
+
188
+ @dataclass
189
+ class LocationStorageGranule:
190
+ location: str
191
+ thumbnail: str | None = None
192
+
193
+ @classmethod
194
+ def from_data(cls, dataset: "xr.Dataset | LocationStorageGranule") -> "LocationStorageGranule":
195
+ """Extract the granule information from a datapoint given as xarray dataset."""
196
+ if isinstance(dataset, LocationStorageGranule):
197
+ return dataset
198
+
199
+ if "location" not in dataset:
200
+ raise ValueError("The given dataset has no location information.")
201
+
202
+ thumbnail = None
203
+ if "thumbnail" in dataset:
204
+ thumbnail = dataset.thumbnail.item()
205
+ elif "overview" in dataset:
206
+ thumbnail = dataset.overview.item()
207
+ elif "quicklook" in dataset:
208
+ thumbnail = dataset.quicklook.item()
209
+
210
+ return cls(dataset.location.item(), thumbnail)