""" Load Sentinel data without ODC Database Sแปญ dแปฅng Microsoft Planetary Computer STAC API Usage: from load_data_no_odc import load_sentinel2_stac, load_sentinel1_stac """ import xarray as xr import numpy as np import pandas as pd from pystac_client import Client import planetary_computer import odc.stac from datetime import datetime def load_sentinel2_stac( bbox, # (lon_min, lat_min, lon_max, lat_max) date_range, # ("2022-09-01", "2023-10-01") bands=None, resolution=10, chunks={'time': 1, 'x': 2048, 'y': 2048} ): """ Load Sentinel-2 L2A data from Microsoft Planetary Computer Args: bbox: Bounding box (lon_min, lat_min, lon_max, lat_max) date_range: Tuple of start and end dates bands: List of bands to load (default: ['red', 'nir', 'scl']) resolution: Spatial resolution in meters chunks: Dask chunk sizes Returns: xarray.Dataset with Sentinel-2 data """ if bands is None: bands = ['red', 'nir', 'blue', 'green', 'nir08', 'swir16', 'swir22', 'SCL'] print(f"๐Ÿ” Searching Sentinel-2 data...") print(f" Bbox: {bbox}") print(f" Date: {date_range[0]} to {date_range[1]}") print(f" Bands: {bands}") # Connect to Planetary Computer STAC catalog = Client.open( "https://planetarycomputer.microsoft.com/api/stac/v1", modifier=planetary_computer.sign_inplace, ) # Search for Sentinel-2 items search = catalog.search( collections=["sentinel-2-l2a"], bbox=bbox, datetime=f"{date_range[0]}/{date_range[1]}", ) items = list(search.items()) print(f"โœ… Found {len(items)} Sentinel-2 scenes") if len(items) == 0: print("โš  No data found for the given parameters") return None # Load data using odc-stac print("๐Ÿ“ฅ Loading data...") try: data = odc.stac.load( items, bands=bands, bbox=bbox, resolution=resolution, chunks=chunks, groupby="solar_day", ) print(f"โœ… Loaded Sentinel-2 data: {data.dims}") print(f" Shape: {dict(data.dims)}") print(f" Bands: {list(data.data_vars)}") return data except Exception as e: print(f"โŒ Error loading data: {e}") import traceback traceback.print_exc() return None def load_sentinel1_stac( bbox, # (lon_min, lat_min, lon_max, lat_max) date_range, # ("2022-09-01", "2023-10-01") bands=None, resolution=10, chunks={'time': 1, 'x': 2048, 'y': 2048} ): """ Load Sentinel-1 RTC data from Microsoft Planetary Computer Args: bbox: Bounding box (lon_min, lat_min, lon_max, lat_max) date_range: Tuple of start and end dates bands: List of bands to load (default: ['vv', 'vh']) resolution: Spatial resolution in meters chunks: Dask chunk sizes Returns: xarray.Dataset with Sentinel-1 data """ if bands is None: bands = ['vv', 'vh'] print(f"๐Ÿ” Searching Sentinel-1 data...") print(f" Bbox: {bbox}") print(f" Date: {date_range[0]} to {date_range[1]}") print(f" Bands: {bands}") # Connect to Planetary Computer STAC catalog = Client.open( "https://planetarycomputer.microsoft.com/api/stac/v1", modifier=planetary_computer.sign_inplace, ) # Search for Sentinel-1 RTC items search = catalog.search( collections=["sentinel-1-rtc"], bbox=bbox, datetime=f"{date_range[0]}/{date_range[1]}", ) items = list(search.items()) print(f"โœ… Found {len(items)} Sentinel-1 scenes") if len(items) == 0: print("โš  No data found for the given parameters") return None # Load data using odc-stac print("๐Ÿ“ฅ Loading data...") try: data = odc.stac.load( items, bands=bands, bbox=bbox, resolution=resolution, chunks=chunks, groupby="solar_day", ) # Rename bands to uppercase for consistency if 'vv' in data.data_vars: data = data.rename({'vv': 'VV', 'vh': 'VH'}) print(f"โœ… Loaded Sentinel-1 data: {data.dims}") print(f" Shape: {dict(data.dims)}") print(f" Bands: {list(data.data_vars)}") return data except Exception as e: print(f"โŒ Error loading data: {e}") import traceback traceback.print_exc() return None def mask_clean_s2(data, scl_band='SCL'): """ Apply cloud mask to Sentinel-2 data using SCL band Good pixels: 2=dark, 4=vegetation, 5=not-vegetated, 6=water """ print("๐Ÿงน Applying cloud mask...") if scl_band not in data.data_vars: print(f"โš  Warning: {scl_band} band not found, skipping cloud mask") return data # Good pixel values good_pixels = [2, 4, 5, 6] # Create mask mask = xr.zeros_like(data[scl_band], dtype=bool) for pixel_val in good_pixels: mask = mask | (data[scl_band] == pixel_val) # Apply mask to all bands except SCL data_vars = [v for v in data.data_vars if v != scl_band] result = data[data_vars].where(mask) print(f"โœ… Cloud mask applied") return result def calculate_ndvi(data, nir_band='nir08', red_band='red'): """ Calculate NDVI from Sentinel-2 data """ print(f"๐Ÿ“Š Calculating NDVI using {nir_band} and {red_band}...") if nir_band not in data.data_vars or red_band not in data.data_vars: print(f"โš  Warning: Required bands not found") print(f" Available bands: {list(data.data_vars)}") return data nir = data[nir_band] red = data[red_band] ndvi = (nir - red) / (nir + red) ndvi = ndvi.rename('NDVI') # Add NDVI to dataset result = xr.merge([data, ndvi]) print(f"โœ… NDVI calculated") return result def fill_nan_temporal(data, dim='time'): """ Fill NaN values using forward/backward fill along time dimension """ print(f"๐Ÿ”ง Filling NaN values using temporal interpolation...") filled = data.bfill(dim=dim).ffill(dim=dim) print(f"โœ… NaN values filled") return filled def resample_monthly(data, method='mean'): """ Resample data to monthly frequency """ print(f"๐Ÿ“… Resampling to monthly using {method}...") if method == 'mean': result = data.resample(time='1MS').mean() elif method == 'median': result = data.resample(time='1MS').median() else: raise ValueError(f"Unknown method: {method}") print(f"โœ… Resampled to monthly: {result.dims}") return result # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # CONVENIENCE WRAPPER # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• def load_and_process_s2(bbox, date_range, apply_cloud_mask=True, calculate_indices=True): """ Load and process Sentinel-2 data in one step """ # Load data data = load_sentinel2_stac(bbox, date_range) if data is None: return None # Apply cloud mask if apply_cloud_mask: data = mask_clean_s2(data) # Calculate NDVI if calculate_indices: data = calculate_ndvi(data) # Fill NaN data = fill_nan_temporal(data) # Resample to monthly data_monthly = resample_monthly(data) return data_monthly def load_and_process_s1(bbox, date_range): """ Load and process Sentinel-1 data in one step """ # Load data data = load_sentinel1_stac(bbox, date_range) if data is None: return None # Resample to monthly data_monthly = resample_monthly(data) return data_monthly # Print module info print("=" * 70) print("๐Ÿ“ฆ Data Loading Module (No ODC Database Required)") print("=" * 70) print("\n๐Ÿ’ก Usage:") print(" from load_data_no_odc import load_and_process_s2, load_and_process_s1") print("\n bbox = (lon_min, lat_min, lon_max, lat_max)") print(" date_range = ('2022-09-01', '2023-10-01')") print("\n data_s2 = load_and_process_s2(bbox, date_range)") print(" data_s1 = load_and_process_s1(bbox, date_range)") print("=" * 70) print()