2424from tilebox .storage .granule import (
2525 ASFStorageGranule ,
2626 CopernicusStorageGranule ,
27+ LocationStorageGranule ,
2728 UmbraStorageGranule ,
2829 USGSLandsatStorageGranule ,
2930)
@@ -241,6 +242,10 @@ def _display_quicklook(image_data: bytes | Path, width: int, height: int, image_
241242
242243
243244class StorageClient (Syncifiable ):
245+ """Base class for all storage clients."""
246+
247+
248+ class CachingStorageClient (StorageClient ):
244249 def __init__ (self , cache_directory : Path | None ) -> None :
245250 self ._cache = cache_directory
246251
@@ -323,7 +328,7 @@ async def _download_object(
323328 return output_path
324329
325330
326- class ASFStorageClient (StorageClient ):
331+ class ASFStorageClient (CachingStorageClient ):
327332 def __init__ (self , user : str , password : str , cache_directory : Path = Path .home () / ".cache" / "tilebox" ) -> None :
328333 """A tilebox storage client that downloads data from the Alaska Satellite Facility.
329334
@@ -415,7 +420,7 @@ async def quicklook(self, datapoint: xr.Dataset | ASFStorageGranule, width: int
415420 """
416421 granule = ASFStorageGranule .from_data (datapoint )
417422 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." )
419424 quicklook = await self ._download_quicklook (datapoint )
420425 _display_quicklook (quicklook , width , height , f"<code>Image { quicklook .name } © ASF { granule .time .year } </code>" )
421426
@@ -439,7 +444,7 @@ def _umbra_s3_prefix(datapoint: xr.Dataset | UmbraStorageGranule) -> str:
439444 return f"sar-data/tasks/{ granule .location } /"
440445
441446
442- class UmbraStorageClient (StorageClient ):
447+ class UmbraStorageClient (CachingStorageClient ):
443448 _STORAGE_PROVIDER = "Umbra"
444449 _BUCKET = "umbra-open-data-catalog"
445450 _REGION = "us-west-2"
@@ -539,7 +544,7 @@ def _copernicus_s3_prefix(datapoint: xr.Dataset | CopernicusStorageGranule) -> s
539544 return granule .location .removeprefix ("/eodata/" )
540545
541546
542- class CopernicusStorageClient (StorageClient ):
547+ class CopernicusStorageClient (CachingStorageClient ):
543548 _STORAGE_PROVIDER = "CopernicusDataspace"
544549 _BUCKET = "eodata"
545550 _ENDPOINT_URL = "https://eodata.dataspace.copernicus.eu"
@@ -724,7 +729,7 @@ async def quicklook(
724729 ValueError: If no quicklook is available for the given datapoint.
725730 """
726731 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." )
728733 granule = CopernicusStorageGranule .from_data (datapoint )
729734 quicklook = await self ._download_quicklook (granule )
730735 _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
750755 return granule .location .removeprefix ("s3://usgs-landsat/" )
751756
752757
753- class USGSLandsatStorageClient (StorageClient ):
758+ class USGSLandsatStorageClient (CachingStorageClient ):
754759 """
755760 A client for downloading USGS Landsat data from the usgs-landsat and usgs-landsat-ard S3 bucket.
756761
@@ -883,7 +888,7 @@ async def quicklook(
883888 ValueError: If no quicklook is available for the given datapoint.
884889 """
885890 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." )
887892 quicklook = await self ._download_quicklook (datapoint )
888893 _display_quicklook (quicklook , width , height , f"<code>Image { quicklook .name } © USGS</code>" )
889894
@@ -901,3 +906,77 @@ async def _download_quicklook(self, datapoint: xr.Dataset | USGSLandsatStorageGr
901906
902907 await download_objects (self ._store , prefix , [granule .thumbnail ], output_folder , show_progress = False )
903908 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 )
0 commit comments