# landstac/read.py"""Read helpers for opening STAC assets as xarray DataArrays.Access modes------------1) Public HREFs: open directly with rioxarray.2) Auth + cache: download to a cache dir, then open.3) Auth + RAM: stream the whole asset into memory, no files on disk.Notes------ Native resolution by default: set `overview_level=None`.- Internal overviews: pass an int overview level to read a lower-res pyramid.- When `download_dir` is provided, assets are cached to: download_dir/<scene_id>/<filename>"""from__future__importannotationsimportosimportiofromtypingimportDict,Iterable,Optionalimportrequestsimportrioxarrayasrxrfromrasterio.ioimportMemoryFile
[docs]defread_stac_bands(item,bands:Iterable[str]=("blue","green","red","nir08"),overview_level:Optional[int]=None,to_linear:bool=False,session:Optional[requests.Session]=None,download_dir:Optional[str]=None,in_memory:bool=False,)->Dict[str,"rxr.DataArray"]:""" Open one or more STAC asset bands as rioxarray DataArrays. Parameters ---------- item : pystac.Item STAC item. bands : Iterable[str] Asset keys to open (e.g., ["blue","green","red","nir08"]). overview_level : int or None None for native resolution; integer for an internal overview. to_linear : bool Apply 10**(x/10) conversion (e.g., Sentinel-1 dB -> linear). session : requests.Session, optional Authenticated ERS session. Required for protected assets if using `download_dir` or `in_memory`. download_dir : str, optional Cache directory; files are stored under `download_dir/<scene_id>/<filename>`. in_memory : bool If True, stream the full asset into RAM and open from memory file. Returns ------- dict Mapping {band_name: DataArray}. """arrays:Dict[str,rxr.DataArray]={}# ---------- Helpers ----------def_open_from_href(href:str):returnrxr.open_rasterio(href,masked=True,overview_level=overview_level)def_open_from_disk(href:str,out_path:str):ifnotos.path.exists(out_path):ifsessionisNone:raiseValueError("session is required to download protected assets")withsession.get(href,stream=True,timeout=120)asr:r.raise_for_status()os.makedirs(os.path.dirname(out_path)or".",exist_ok=True)withopen(out_path,"wb")asf:forchunkinr.iter_content(1<<20):ifchunk:f.write(chunk)returnrxr.open_rasterio(out_path,masked=True,overview_level=overview_level)def_open_in_memory(href:str):ifsessionisNone:raiseValueError("session is required for in_memory=True")r=session.get(href,stream=True,timeout=120)r.raise_for_status()bio=io.BytesIO()forchunkinr.iter_content(1<<20):ifchunk:bio.write(chunk)bio.seek(0)withMemoryFile(bio)asmemfile:withmemfile.open()assrc:returnrxr.open_rasterio(src,masked=True,overview_level=overview_level)# -----------------------------forbinbands:ifbnotinitem.assets:continuehref=item.assets[b].hrefifsessionisNoneanddownload_dirisNoneandnotin_memory:# Public asset HREFda=_open_from_href(href)elifdownload_dirandnotin_memory:# Cache under download_dir/<scene_id>/<filename>scene=item.properties.get("landsat:scene_id",item.id)scene_dir=os.path.join(download_dir,scene)os.makedirs(scene_dir,exist_ok=True)fname=os.path.basename(href)out_path=os.path.join(scene_dir,fname)da=_open_from_disk(href,out_path)else:# Auth + RAMda=_open_in_memory(href)ifto_linear:da=10**(da/10.0)arrays[b]=dareturnarrays