Sentinel-3 Multi-Product Demo with GDAL
Comprehensive demonstration of accessing multiple Sentinel-3 product types using GDAL EOPFZARR driver

Run this notebook interactively with all dependencies pre-installed
IntroductionΒΆ
This notebook provides a comprehensive demonstration of accessing and analyzing multiple Sentinel-3 product types using the GDAL EOPFZARR driver. Sentinel-3 is a European Space Agency (ESA) Earth observation satellite mission that provides a wide range of data products for monitoring land, ocean, and atmospheric parameters.
The notebook showcases how to work with five different Sentinel-3 product types:
OLCI (Ocean and Land Colour Instrument): Optical imagery for land and ocean applications
Level-1 EFR: Full resolution top-of-atmosphere radiances (300m)
Level-1 ERR: Reduced resolution radiances (1.2km)
Level-2 LFR: Land products including vegetation indices
SLSTR (Sea and Land Surface Temperature Radiometer): Thermal and visible/near-infrared measurements
Level-1 RBT: Radiances and brightness temperatures
Level-2 LST: Derived land surface temperature
Using GDALβs EOPFZARR driver, we can efficiently access these cloud-optimized Zarr datasets directly from remote object storage without downloading entire files. This approach enables scalable analysis of large satellite datasets.
By the end of this notebook, you will understand how to:
Discover and explore subdatasets within Sentinel-3 products
Access specific measurements and quality flags
Visualize multi-spectral and thermal imagery
Work with geographic coordinates for geolocation
Compare different product types and their characteristics
Product Overview and Subdataset DiscoveryΒΆ
Letβs explore each productβs structure and available subdatasets.
# Import required libraries
from osgeo import gdal
import matplotlib.pyplot as plt
import numpy as np
# Note: xarray import skipped due to version compatibility issues
# We use pure numpy/GDAL approach instead
import warnings
warnings.filterwarnings("ignore")
# Enable GDAL exceptions
gdal.UseExceptions()
# Verify EOPFZARR driver is available
driver = gdal.GetDriverByName("EOPFZARR")
if driver:
print(f"β
EOPFZARR driver loaded: {driver.LongName}")
else:
print("β EOPFZARR driver not found!")
print(f"GDAL version: {gdal.__version__}")β
EOPFZARR driver loaded: EOPF Zarr Wrapper Driver
GDAL version: 3.12.0dev-209c099c56
/home/yadagale/.local/lib/python3.10/site-packages/matplotlib/projections/__init__.py:63: UserWarning: Unable to import Axes3D. This may be due to multiple versions of Matplotlib being installed (e.g. as a system package and as a pip package). As a result, the 3D projection is not available.
warnings.warn("Unable to import Axes3D. This may be due to multiple versions of "
# Define all Sentinel-3 product URLs
PRODUCTS = {
"OLCI_L1_EFR": {
"name": "Sentinel-3 OLCI Level-1 EFR (Full Resolution)",
"url": "https://objects.eodc.eu/e05ab01a9d56408d82ac32d69a5aae2a:notebook-data/tutorial_data/"
"cpm_v264/S3B_OL_1_EFR____20250819T074058_20250819T074358_20250820T082937_0179_110_106_3420_ESA_O_NT_004.zarr",
"description": "TOA Radiances - 21 spectral bands (400-1020nm)",
"sample_band": "measurements/oa01_radiance",
},
"OLCI_L1_ERR": {
"name": "Sentinel-3 OLCI Level-1 ERR (Reduced Resolution)",
"url": "https://objects.eodc.eu/e05ab01a9d56408d82ac32d69a5aae2a:notebook-data/tutorial_data/"
"cpm_v264/S3A_OL_1_ERR____20251019T145533_20251019T153950_20251020T155912_2657_131_353______PS1_O_NT_004.zarr",
"description": "TOA Radiances - Reduced resolution (1.2km)",
"sample_band": "measurements/oa01_radiance",
},
"OLCI_L2_LFR": {
"name": "Sentinel-3 OLCI Level-2 LFR (Land Full Resolution)",
"url": "https://objects.eodc.eu/e05ab01a9d56408d82ac32d69a5aae2a:notebook-data/tutorial_data/"
"cpm_v264/S3A_OL_2_LFR____20250817T085058_20250817T085358_20250818T125249_0179_129_221_2160_PS1_O_NT_003.zarr",
"description": "Land products - OGVI, OTCI, RC681, etc.",
"sample_band": None, # Will discover
},
"SLSTR_L1_RBT": {
"name": "Sentinel-3 SLSTR Level-1 RBT",
"url": "https://objects.eodc.eu/e05ab01a9d56408d82ac32d69a5aae2a:notebook-data/tutorial_data/"
"cpm_v264/S3A_SL_1_RBT____20251019T064521_20251019T064821_20251020T175322_0179_131_348_2700_PS1_O_NT_004.zarr",
"description": "Radiances and Brightness Temperatures",
"sample_band": None, # Will discover
},
"SLSTR_L2_LST": {
"name": "Sentinel-3 SLSTR Level-2 LST",
"url": "https://objects.eodc.eu/e05ab01a9d56408d82ac32d69a5aae2a:notebook-data/tutorial_data/"
"cpm_v264/S3A_SL_2_LST____20251204T100846_20251204T101146_20251205T211618_0179_133_236_2340_PS1_O_NT_004.zarr",
"description": "Land Surface Temperature",
"sample_band": None, # Will discover
},
}# Helper function for eopfzarr paths
def eopfzarr_path(url, subdataset=None):
"""Construct EOPFZARR path for remote access
Note: The subdataset path MUST start with '/' for GEOLOCATION metadata to work.
This is required by the EOPFZARR driver for proper geolocation array handling.
"""
base = f'EOPFZARR:"/vsicurl/{url}"'
if subdataset:
# Ensure subdataset starts with '/' for geolocation arrays
if not subdataset.startswith("/"):
subdataset = "/" + subdataset
return f"{base}:{subdataset}"
return base
print(f"Configured {len(PRODUCTS)} Sentinel-3 products for analysis")Configured 5 Sentinel-3 products for analysis
def explore_product(product_key, max_subdatasets=15):
"""Explore a Sentinel-3 product and list its subdatasets"""
product = PRODUCTS[product_key]
print(f"\n{'='*80}")
print(f"π¦ {product['name']}")
print(f"{'='*80}")
print(f"Description: {product['description']}")
print(f"URL: ...{product['url'][-60:]}")
try:
ds = gdal.Open(eopfzarr_path(product["url"]))
if ds is None:
print("β Failed to open dataset")
return None
print(f"\nDriver: {ds.GetDriver().ShortName}")
print(f"Size: {ds.RasterXSize} x {ds.RasterYSize}")
# Get projection info
proj = ds.GetProjection()
if proj:
# Extract EPSG if available
if "EPSG" in proj:
import re
epsg_match = re.search(r'EPSG","?(\d+)', proj)
if epsg_match:
print(f"CRS: EPSG:{epsg_match.group(1)}")
# Get subdatasets
subdatasets = ds.GetMetadata("SUBDATASETS")
subdataset_list = []
for key, value in subdatasets.items():
if key.startswith("SUBDATASET_") and "_NAME" in key:
path = value.split(":")[-1]
subdataset_list.append(path)
print(f"\nSubdatasets: {len(subdataset_list)} total")
print("\nSample subdatasets:")
for i, sd in enumerate(subdataset_list[:max_subdatasets]):
print(f" {i+1:2d}. {sd}")
if len(subdataset_list) > max_subdatasets:
print(f" ... and {len(subdataset_list) - max_subdatasets} more")
ds = None
return subdataset_list
except Exception as e:
print(f"β Error: {e}")
return None
# Explore all products
product_subdatasets = {}
for key in PRODUCTS.keys():
product_subdatasets[key] = explore_product(key)
================================================================================
π¦ Sentinel-3 OLCI Level-1 EFR (Full Resolution)
================================================================================
Description: TOA Radiances - 21 spectral bands (400-1020nm)
URL: ...9T074358_20250819T092155_0179_110_106_3420_ESA_O_NR_004.zarr
Driver: EOPFZARR
Size: 512 x 512
CRS: EPSG:7030
Subdatasets: 123 total
Sample subdatasets:
1. /conditions/geometry/latitude
2. /conditions/geometry/longitude
3. /conditions/geometry/oaa
4. /conditions/geometry/oza
5. /conditions/geometry/saa
6. /conditions/geometry/sza
7. /conditions/image/altitude
8. /conditions/image/detector_index
9. /conditions/image/frame_offset
10. /conditions/image/latitude
11. /conditions/image/longitude
12. /conditions/instrument/fwhm
13. /conditions/instrument/lambda0
14. /conditions/instrument/relative_spectral_covariance
15. /conditions/instrument/solar_flux
... and 108 more
================================================================================
π¦ Sentinel-3 OLCI Level-1 ERR (Reduced Resolution)
================================================================================
Description: TOA Radiances - Reduced resolution (1.2km)
URL: ...9T153950_20251019T165332_2657_131_353______PS1_O_NR_004.zarr
Driver: EOPFZARR
Size: 512 x 512
CRS: EPSG:7030
Subdatasets: 68 total
Sample subdatasets:
1. /conditions/geometry/latitude
2. /conditions/geometry/longitude
3. /conditions/geometry/oaa
4. /conditions/geometry/oza
5. /conditions/geometry/saa
6. /conditions/geometry/sza
7. /conditions/image/altitude
8. /conditions/image/detector_index
9. /conditions/image/frame_offset
10. /conditions/image/latitude
11. /conditions/image/longitude
12. /conditions/instrument/fwhm
13. /conditions/instrument/lambda0
14. /conditions/instrument/relative_spectral_covariance
15. /conditions/instrument/solar_flux
... and 53 more
================================================================================
π¦ Sentinel-3 OLCI Level-2 LFR (Land Full Resolution)
================================================================================
Description: Land products - OGVI, OTCI, RC681, etc.
URL: ...7T085358_20250818T125249_0179_129_221_2160_PS1_O_NT_003.zarr
Driver: EOPFZARR
Size: 512 x 512
CRS: EPSG:7030
Subdatasets: 123 total
Sample subdatasets:
1. /conditions/geometry/latitude
2. /conditions/geometry/longitude
3. /conditions/geometry/oaa
4. /conditions/geometry/oza
5. /conditions/geometry/saa
6. /conditions/geometry/sza
7. /conditions/image/altitude
8. /conditions/image/detector_index
9. /conditions/image/frame_offset
10. /conditions/image/latitude
11. /conditions/image/longitude
12. /conditions/instrument/fwhm
13. /conditions/instrument/lambda0
14. /conditions/instrument/relative_spectral_covariance
15. /conditions/instrument/solar_flux
... and 108 more
================================================================================
π¦ Sentinel-3 OLCI Level-1 ERR (Reduced Resolution)
================================================================================
Description: TOA Radiances - Reduced resolution (1.2km)
URL: ...9T153950_20251019T165332_2657_131_353______PS1_O_NR_004.zarr
Driver: EOPFZARR
Size: 512 x 512
CRS: EPSG:7030
Subdatasets: 68 total
Sample subdatasets:
1. /conditions/geometry/latitude
2. /conditions/geometry/longitude
3. /conditions/geometry/oaa
4. /conditions/geometry/oza
5. /conditions/geometry/saa
6. /conditions/geometry/sza
7. /conditions/image/altitude
8. /conditions/image/detector_index
9. /conditions/image/frame_offset
10. /conditions/image/latitude
11. /conditions/image/longitude
12. /conditions/instrument/fwhm
13. /conditions/instrument/lambda0
14. /conditions/instrument/relative_spectral_covariance
15. /conditions/instrument/solar_flux
... and 53 more
================================================================================
π¦ Sentinel-3 OLCI Level-2 LFR (Land Full Resolution)
================================================================================
Description: Land products - OGVI, OTCI, RC681, etc.
URL: ...7T085358_20250818T125249_0179_129_221_2160_PS1_O_NT_003.zarr
Driver: EOPFZARR
Size: 512 x 512
CRS: EPSG:7030
Subdatasets: 37 total
Sample subdatasets:
1. /conditions/geometry/latitude
2. /conditions/geometry/longitude
3. /conditions/geometry/oaa
4. /conditions/geometry/oza
5. /conditions/geometry/saa
6. /conditions/geometry/sza
7. /conditions/image/altitude
8. /conditions/image/detector_index
9. /conditions/image/frame_offset
10. /conditions/image/latitude
11. /conditions/image/longitude
12. /conditions/instrument/fwhm
13. /conditions/instrument/lambda0
14. /conditions/instrument/relative_spectral_covariance
15. /conditions/instrument/solar_flux
... and 22 more
================================================================================
π¦ Sentinel-3 SLSTR Level-1 RBT
================================================================================
Description: Radiances and Brightness Temperatures
URL: ...9T064821_20251019T085627_0179_131_348_2700_PS1_O_NR_004.zarr
Driver: EOPFZARR
Size: 512 x 512
CRS: EPSG:7030
Subdatasets: 37 total
Sample subdatasets:
1. /conditions/geometry/latitude
2. /conditions/geometry/longitude
3. /conditions/geometry/oaa
4. /conditions/geometry/oza
5. /conditions/geometry/saa
6. /conditions/geometry/sza
7. /conditions/image/altitude
8. /conditions/image/detector_index
9. /conditions/image/frame_offset
10. /conditions/image/latitude
11. /conditions/image/longitude
12. /conditions/instrument/fwhm
13. /conditions/instrument/lambda0
14. /conditions/instrument/relative_spectral_covariance
15. /conditions/instrument/solar_flux
... and 22 more
================================================================================
π¦ Sentinel-3 SLSTR Level-1 RBT
================================================================================
Description: Radiances and Brightness Temperatures
URL: ...9T064821_20251019T085627_0179_131_348_2700_PS1_O_NR_004.zarr
Driver: EOPFZARR
Size: 512 x 512
CRS: EPSG:7030
Subdatasets: 530 total
Sample subdatasets:
1. /conditions/geometry_tn/sat_azimuth_tn
2. /conditions/geometry_tn/sat_path_tn
3. /conditions/geometry_tn/sat_zenith_tn
4. /conditions/geometry_tn/solar_azimuth_tn
5. /conditions/geometry_tn/solar_path_tn
6. /conditions/geometry_tn/solar_zenith_tn
7. /conditions/geometry_tn/x
8. /conditions/geometry_tn/y
9. /conditions/geometry_to/sat_azimuth_to
10. /conditions/geometry_to/sat_path_to
11. /conditions/geometry_to/sat_zenith_to
12. /conditions/geometry_to/solar_azimuth_to
13. /conditions/geometry_to/solar_path_to
14. /conditions/geometry_to/solar_zenith_to
15. /conditions/instrument/anadir/s1/s1_cal_gain_an
... and 515 more
================================================================================
π¦ Sentinel-3 SLSTR Level-2 LST
================================================================================
Description: Land Surface Temperature
URL: ...4T101146_20251204T122129_0179_133_236_2340_PS1_O_NR_004.zarr
Driver: EOPFZARR
Size: 512 x 512
CRS: EPSG:7030
Subdatasets: 84 total
Sample subdatasets:
1. /conditions/auxiliary/biome
2. /conditions/auxiliary/elevation
3. /conditions/auxiliary/fraction
4. /conditions/auxiliary/ndvi
5. /conditions/auxiliary/tcwv
6. /conditions/auxiliary/orphan/biome
7. /conditions/auxiliary/orphan/elevation
8. /conditions/auxiliary/orphan/fraction
9. /conditions/auxiliary/orphan/ndvi
10. /conditions/auxiliary/orphan/tcwv
11. /conditions/geometry/latitude
12. /conditions/geometry/longitude
13. /conditions/geometry/sat_azimuth_tn
14. /conditions/geometry/sat_path_tn
15. /conditions/geometry/sat_zenith_tn
... and 69 more
Driver: EOPFZARR
Size: 512 x 512
CRS: EPSG:7030
Subdatasets: 530 total
Sample subdatasets:
1. /conditions/geometry_tn/sat_azimuth_tn
2. /conditions/geometry_tn/sat_path_tn
3. /conditions/geometry_tn/sat_zenith_tn
4. /conditions/geometry_tn/solar_azimuth_tn
5. /conditions/geometry_tn/solar_path_tn
6. /conditions/geometry_tn/solar_zenith_tn
7. /conditions/geometry_tn/x
8. /conditions/geometry_tn/y
9. /conditions/geometry_to/sat_azimuth_to
10. /conditions/geometry_to/sat_path_to
11. /conditions/geometry_to/sat_zenith_to
12. /conditions/geometry_to/solar_azimuth_to
13. /conditions/geometry_to/solar_path_to
14. /conditions/geometry_to/solar_zenith_to
15. /conditions/instrument/anadir/s1/s1_cal_gain_an
... and 515 more
================================================================================
π¦ Sentinel-3 SLSTR Level-2 LST
================================================================================
Description: Land Surface Temperature
URL: ...4T101146_20251204T122129_0179_133_236_2340_PS1_O_NR_004.zarr
Driver: EOPFZARR
Size: 512 x 512
CRS: EPSG:7030
Subdatasets: 84 total
Sample subdatasets:
1. /conditions/auxiliary/biome
2. /conditions/auxiliary/elevation
3. /conditions/auxiliary/fraction
4. /conditions/auxiliary/ndvi
5. /conditions/auxiliary/tcwv
6. /conditions/auxiliary/orphan/biome
7. /conditions/auxiliary/orphan/elevation
8. /conditions/auxiliary/orphan/fraction
9. /conditions/auxiliary/orphan/ndvi
10. /conditions/auxiliary/orphan/tcwv
11. /conditions/geometry/latitude
12. /conditions/geometry/longitude
13. /conditions/geometry/sat_azimuth_tn
14. /conditions/geometry/sat_path_tn
15. /conditions/geometry/sat_zenith_tn
... and 69 more
OLCI Level-1 EFR - Full Resolution RadiancesΒΆ
The OLCI L1 EFR product contains Top-of-Atmosphere (TOA) radiances at 300m resolution with 21 spectral bands.
def read_band_data(url, subdataset, size=None):
"""Read band data from a subdataset"""
path = eopfzarr_path(url, subdataset)
ds = gdal.Open(path)
if ds is None:
raise ValueError(f"Cannot open {subdataset}")
band = ds.GetRasterBand(1)
if size is None:
data = band.ReadAsArray().astype(np.float32)
else:
# Read a subset
width = min(size, ds.RasterXSize)
height = min(size, ds.RasterYSize)
data = band.ReadAsArray(0, 0, width, height).astype(np.float32)
nodata = band.GetNoDataValue()
if nodata is not None:
data[data == nodata] = np.nan
metadata = {
"width": ds.RasterXSize,
"height": ds.RasterYSize,
"nodata": nodata,
"dtype": band.DataType,
}
ds = None
return data, metadata
# Read OLCI L1 EFR bands
print("Reading OLCI L1 EFR spectral bands...")
olci_bands = {
"Oa01 (400nm)": "measurements/oa01_radiance",
"Oa06 (560nm)": "measurements/oa06_radiance",
"Oa08 (665nm)": "measurements/oa08_radiance",
"Oa17 (865nm)": "measurements/oa17_radiance",
}
olci_data = {}
for name, subdataset in olci_bands.items():
try:
data, meta = read_band_data(
PRODUCTS["OLCI_L1_EFR"]["url"], subdataset, size=1000
)
olci_data[name] = data
print(
f" β
{name}: {data.shape}, range [{np.nanmin(data):.2f}, {np.nanmax(data):.2f}]"
)
except Exception as e:
print(f" β {name}: {e}")Reading OLCI L1 EFR spectral bands...
β
Oa01 (400nm): (1000, 1000), range [68.84, 103.83]
β
Oa06 (560nm): (1000, 1000), range [42.62, 129.86]
β
Oa08 (665nm): (1000, 1000), range [30.09, 130.64]
β
Oa17 (865nm): (1000, 1000), range [27.74, 103.16]
# Visualize OLCI L1 EFR bands
if olci_data:
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
axes = axes.flatten()
for idx, (name, data) in enumerate(olci_data.items()):
# Use robust percentile scaling
vmin, vmax = np.nanpercentile(data, [2, 98])
im = axes[idx].imshow(data, cmap="viridis", vmin=vmin, vmax=vmax)
axes[idx].set_title(f"OLCI L1 EFR - {name}", fontsize=11, fontweight="bold")
axes[idx].set_xlabel("Column")
axes[idx].set_ylabel("Row")
plt.colorbar(im, ax=axes[idx], label="Radiance", fraction=0.046)
plt.suptitle(
"Sentinel-3 OLCI L1 EFR - Multi-Spectral Bands", fontsize=14, fontweight="bold"
)
plt.tight_layout()
plt.show()
print("β
OLCI L1 EFR visualization complete!")
β
OLCI L1 EFR visualization complete!
OLCI Level-1 ERR - Reduced ResolutionΒΆ
The OLCI L1 ERR product provides TOA radiances at reduced resolution (1.2km), covering the same 21 spectral bands as EFR.
# Read OLCI L1 ERR bands
print("Reading OLCI L1 ERR (Reduced Resolution) spectral bands...")
err_data = {}
for name, subdataset in olci_bands.items():
try:
data, meta = read_band_data(
PRODUCTS["OLCI_L1_ERR"]["url"], subdataset, size=800
)
err_data[name] = data
print(
f" β
{name}: {data.shape}, range [{np.nanmin(data):.2f}, {np.nanmax(data):.2f}]"
)
except Exception as e:
print(f" β {name}: {e}")
# Visualize
if err_data:
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
axes = axes.flatten()
for idx, (name, data) in enumerate(err_data.items()):
vmin, vmax = np.nanpercentile(data, [2, 98])
im = axes[idx].imshow(data, cmap="viridis", vmin=vmin, vmax=vmax)
axes[idx].set_title(f"OLCI L1 ERR - {name}", fontsize=11, fontweight="bold")
axes[idx].set_xlabel("Column")
axes[idx].set_ylabel("Row")
plt.colorbar(im, ax=axes[idx], label="Radiance", fraction=0.046)
plt.suptitle(
"Sentinel-3 OLCI L1 ERR - Reduced Resolution", fontsize=14, fontweight="bold"
)
plt.tight_layout()
plt.show()
print("β
OLCI L1 ERR visualization complete!")Reading OLCI L1 ERR (Reduced Resolution) spectral bands...
β
Oa01 (400nm): (800, 800), range [18.07, 153.01]
β
Oa01 (400nm): (800, 800), range [18.07, 153.01]
β
Oa06 (560nm): (800, 800), range [7.30, 208.61]
β
Oa06 (560nm): (800, 800), range [7.30, 208.61]
β
Oa08 (665nm): (800, 800), range [3.94, 197.30]
β
Oa08 (665nm): (800, 800), range [3.94, 197.30]
β
Oa17 (865nm): (800, 800), range [0.88, 129.69]
β
Oa17 (865nm): (800, 800), range [0.88, 129.69]

β
OLCI L1 ERR visualization complete!
OLCI Level-2 LFR - Land ProductsΒΆ
The L2 LFR product contains derived land parameters like vegetation indices.
# Discover L2 LFR subdatasets
print("Discovering OLCI L2 LFR subdatasets...")
lfr_subdatasets = product_subdatasets.get("OLCI_L2_LFR", [])
# Find measurement subdatasets
lfr_measurements = [
sd
for sd in lfr_subdatasets
if "measurements/" in sd and "latitude" not in sd and "longitude" not in sd
]
print(f"\nAvailable L2 LFR measurements ({len(lfr_measurements)}):")
for sd in lfr_measurements[:15]:
print(f" {sd}")Discovering OLCI L2 LFR subdatasets...
Available L2 LFR measurements (5):
/measurements/gifapar
/measurements/iwv
/measurements/otci
/measurements/rc681
/measurements/rc865
# Read L2 LFR products - try common land products
print("Reading OLCI L2 LFR land products...")
# Common L2 land product names to try
l2_products_to_try = [
("GIFAPAR", "measurements/gifapar"), # OLCI Global Vegetation Index
("OTCI", "measurements/otci"), # OLCI Terrestrial Chlorophyll Index
("IWV", "measurements/iwv"), # Integrated Water Vapour
("RC681", "measurements/rc681"), # Rectified Reflectance 681nm
]
lfr_data = {}
for name, subdataset in l2_products_to_try:
try:
data, meta = read_band_data(
PRODUCTS["OLCI_L2_LFR"]["url"], subdataset, size=800
)
lfr_data[name] = data
print(
f" β
{name}: {data.shape}, range [{np.nanmin(data):.4f}, {np.nanmax(data):.4f}]"
)
except Exception as e:
print(f" β οΈ {name}: Not available or error - {str(e)[:50]}")
# If we found products, visualize them
if lfr_data:
n_products = len(lfr_data)
cols = min(2, n_products)
rows = (n_products + 1) // 2
fig, axes = plt.subplots(rows, cols, figsize=(7 * cols, 5 * rows))
if n_products == 1:
axes = [axes]
else:
axes = axes.flatten()
for idx, (name, data) in enumerate(lfr_data.items()):
vmin, vmax = np.nanpercentile(data, [2, 98])
# Choose colormap based on product
if "OGVI" in name or "OTCI" in name:
cmap = "RdYlGn"
else:
cmap = "viridis"
im = axes[idx].imshow(data, cmap=cmap, vmin=vmin, vmax=vmax)
axes[idx].set_title(f"OLCI L2 LFR - {name}", fontsize=11, fontweight="bold")
axes[idx].set_xlabel("Column")
axes[idx].set_ylabel("Row")
plt.colorbar(im, ax=axes[idx], fraction=0.046)
# Hide unused subplots
for idx in range(len(lfr_data), len(axes)):
axes[idx].axis("off")
plt.suptitle(
"Sentinel-3 OLCI L2 LFR - Land Products", fontsize=14, fontweight="bold"
)
plt.tight_layout()
plt.show()
else:
print("β οΈ No L2 LFR products loaded. Checking available subdatasets...")
print(f"Available: {lfr_measurements[:5]}")Reading OLCI L2 LFR land products...
β
GIFAPAR: (800, 800), range [0.0000, 1.0000]
β
GIFAPAR: (800, 800), range [0.0000, 1.0000]
β
OTCI: (800, 800), range [0.2303, 6.4744]
β
OTCI: (800, 800), range [0.2303, 6.4744]
β
IWV: (800, 800), range [0.0000, 67.2000]
β
IWV: (800, 800), range [0.0000, 67.2000]
β
RC681: (800, 800), range [0.0000, 0.2907]
β
RC681: (800, 800), range [0.0000, 0.2907]

SLSTR Level-1 RBT - Radiances and Brightness TemperaturesΒΆ
The SLSTR instrument provides thermal and visible/NIR radiances for sea and land surface temperature retrieval.
# Discover SLSTR L1 RBT subdatasets
print("Discovering SLSTR L1 RBT subdatasets...")
rbt_subdatasets = product_subdatasets.get("SLSTR_L1_RBT", [])
# Find measurement subdatasets
rbt_measurements = [sd for sd in rbt_subdatasets if "measurements/" in sd]
print(f"\nAvailable SLSTR L1 RBT measurements ({len(rbt_measurements)}):")
for sd in rbt_measurements[:20]:
print(f" {sd}")Discovering SLSTR L1 RBT subdatasets...
Available SLSTR L1 RBT measurements (136):
/measurements/anadir/elevation
/measurements/anadir/latitude
/measurements/anadir/longitude
/measurements/anadir/s1_radiance_an
/measurements/anadir/s2_radiance_an
/measurements/anadir/s3_radiance_an
/measurements/anadir/s4_radiance_an
/measurements/anadir/s5_radiance_an
/measurements/anadir/s6_radiance_an
/measurements/anadir/x
/measurements/anadir/y
/measurements/anadir/orphan/elevation_an
/measurements/anadir/orphan/latitude_an
/measurements/anadir/orphan/longitude_an
/measurements/anadir/orphan/s1_radiance
/measurements/anadir/orphan/s2_radiance
/measurements/anadir/orphan/s3_radiance
/measurements/anadir/orphan/s4_radiance
/measurements/anadir/orphan/s5_radiance
/measurements/anadir/orphan/s6_radiance
# Read SLSTR L1 RBT bands
print("Reading SLSTR L1 RBT bands...")
# SLSTR bands to try (common naming patterns)
slstr_bands_to_try = [
("S1 (555nm)", "measurements/anadir/s1_radiance_an"),
("S2 (659nm)", "measurements/anadir/s2_radiance_an"),
("S3 (865nm)", "measurements/anadir/s3_radiance_an"),
("S7 (3.7ΞΌm BT)", "measurements/inadir/s7_bt_in"),
("S8 (10.8ΞΌm BT)", "measurements/inadir/s8_bt_in"),
("S9 (12ΞΌm BT)", "measurements/inadir/s9_bt_in"),
]
rbt_data = {}
for name, subdataset in slstr_bands_to_try:
try:
data, meta = read_band_data(
PRODUCTS["SLSTR_L1_RBT"]["url"], subdataset, size=600
)
rbt_data[name] = data
print(
f" β
{name}: {data.shape}, range [{np.nanmin(data):.2f}, {np.nanmax(data):.2f}]"
)
except Exception as e:
print(f" β οΈ {name}: {str(e)[:60]}")
# Visualize
if rbt_data:
n_bands = len(rbt_data)
cols = min(3, n_bands)
rows = (n_bands + 2) // 3
fig, axes = plt.subplots(rows, cols, figsize=(5 * cols, 4 * rows))
if n_bands == 1:
axes = [axes]
else:
axes = axes.flatten()
for idx, (name, data) in enumerate(rbt_data.items()):
vmin, vmax = np.nanpercentile(data, [2, 98])
# Use thermal colormap for brightness temperature bands
if "BT" in name or "ΞΌm" in name:
cmap = "inferno"
label = "Brightness Temp (K)"
else:
cmap = "viridis"
label = "Radiance"
im = axes[idx].imshow(data, cmap=cmap, vmin=vmin, vmax=vmax)
axes[idx].set_title(f"{name}", fontsize=10, fontweight="bold")
plt.colorbar(im, ax=axes[idx], label=label, fraction=0.046)
# Hide unused
for idx in range(len(rbt_data), len(axes)):
axes[idx].axis("off")
plt.suptitle(
"Sentinel-3 SLSTR L1 RBT - Radiances & Brightness Temperatures",
fontsize=13,
fontweight="bold",
)
plt.tight_layout()
plt.show()
else:
print("β οΈ No SLSTR L1 RBT bands loaded")Reading SLSTR L1 RBT bands...
β
S1 (555nm): (600, 600), range [80.45, 177.95]
β
S2 (659nm): (600, 600), range [62.18, 180.45]
β
S3 (865nm): (600, 600), range [49.04, 143.32]
β
S7 (3.7ΞΌm BT): (600, 600), range [295.65, 312.34]
β
S8 (10.8ΞΌm BT): (600, 600), range [289.93, 313.81]
β
S9 (12ΞΌm BT): (600, 600), range [288.28, 312.14]

SLSTR Level-2 LST - Land Surface TemperatureΒΆ
The LST product provides derived land surface temperature.
# Discover SLSTR L2 LST subdatasets
print("Discovering SLSTR L2 LST subdatasets...")
lst_subdatasets = product_subdatasets.get("SLSTR_L2_LST", [])
# Find measurement subdatasets
lst_measurements = [sd for sd in lst_subdatasets if "measurements/" in sd]
print(f"\nAvailable SLSTR L2 LST measurements ({len(lst_measurements)}):")
for sd in lst_measurements[:15]:
print(f" {sd}")
lst_conditions = [sd for sd in lst_subdatasets if "conditions/" in sd]
print(f"\nAvailable SLSTR L2 LST conditions ({len(lst_conditions)}):")
for sd in lst_conditions[:15]:
print(f" {sd}")Discovering SLSTR L2 LST subdatasets...
Available SLSTR L2 LST measurements (10):
/measurements/latitude
/measurements/longitude
/measurements/lst
/measurements/x
/measurements/y
/measurements/orphan/latitude
/measurements/orphan/longitude
/measurements/orphan/lst
/measurements/orphan/x
/measurements/orphan/y
Available SLSTR L2 LST conditions (52):
/conditions/auxiliary/biome
/conditions/auxiliary/elevation
/conditions/auxiliary/fraction
/conditions/auxiliary/ndvi
/conditions/auxiliary/tcwv
/conditions/auxiliary/orphan/biome
/conditions/auxiliary/orphan/elevation
/conditions/auxiliary/orphan/fraction
/conditions/auxiliary/orphan/ndvi
/conditions/auxiliary/orphan/tcwv
/conditions/geometry/latitude
/conditions/geometry/longitude
/conditions/geometry/sat_azimuth_tn
/conditions/geometry/sat_path_tn
/conditions/geometry/sat_zenith_tn
# Read SLSTR L2 LST data
print("Reading SLSTR L2 LST products...")
lst_products_to_try = [
("LST", "measurements/lst"),
("LST Uncertainty", "quality/lst_uncertainty"),
("NDVI", "conditions/auxiliary/ndvi"),
("Biome", "conditions/auxiliary/biome"),
]
lst_data = {}
for name, subdataset in lst_products_to_try:
try:
data, meta = read_band_data(
PRODUCTS["SLSTR_L2_LST"]["url"], subdataset, size=600
)
lst_data[name] = data
print(
f" β
{name}: {data.shape}, range [{np.nanmin(data):.2f}, {np.nanmax(data):.2f}]"
)
except Exception as e:
print(f" β οΈ {name}: {str(e)[:60]}")
# Visualize LST
if lst_data:
n_products = len(lst_data)
cols = min(2, n_products)
rows = (n_products + 1) // 2
fig, axes = plt.subplots(rows, cols, figsize=(7 * cols, 5 * rows))
if n_products == 1:
axes = [axes]
else:
axes = axes.flatten()
for idx, (name, data) in enumerate(lst_data.items()):
vmin, vmax = np.nanpercentile(data, [2, 98])
# Choose appropriate colormap
if "LST" in name and "Uncertainty" not in name:
cmap = "RdYlBu_r" # Temperature colormap
label = "Temperature (K)"
elif "NDVI" in name:
cmap = "RdYlGn"
label = "NDVI"
else:
cmap = "viridis"
label = "Value"
im = axes[idx].imshow(data, cmap=cmap, vmin=vmin, vmax=vmax)
axes[idx].set_title(f"SLSTR L2 LST - {name}", fontsize=11, fontweight="bold")
axes[idx].set_xlabel("Column")
axes[idx].set_ylabel("Row")
plt.colorbar(im, ax=axes[idx], label=label, fraction=0.046)
# Hide unused
for idx in range(len(lst_data), len(axes)):
axes[idx].axis("off")
plt.suptitle(
"Sentinel-3 SLSTR L2 LST - Land Surface Temperature",
fontsize=14,
fontweight="bold",
)
plt.tight_layout()
plt.show()
else:
print("β οΈ No SLSTR L2 LST products loaded")Reading SLSTR L2 LST products...
β
LST: (600, 600), range [-32767.00, 1604.00]
β
LST Uncertainty: (600, 600), range [105.00, 2258.00]
β
NDVI: (600, 600), range [-2147.00, 8692.00]
β
Biome: (600, 600), range [0.00, 220.00]

Geographic CoordinatesΒΆ
Letβs demonstrate accessing geographic coordinates for proper geolocation.
# Demonstrate EOPFZARR Geolocation Arrays Support for ALL Products
print("=" * 80)
print("EOPFZARR GEOLOCATION ARRAYS DEMO - All Sentinel-3 Products")
print("=" * 80)
# Define sample subdatasets for each product type
product_samples = {
"OLCI_L1_EFR": "measurements/oa01_radiance",
"OLCI_L1_ERR": "measurements/oa01_radiance",
"OLCI_L2_LFR": "measurements/gifapar",
"SLSTR_L1_RBT": "measurements/inadir/s8_bt_in",
"SLSTR_L2_LST": "measurements/lst",
}
geoloc_results = {}
for product_key, subdataset in product_samples.items():
url = PRODUCTS[product_key]["url"]
path = eopfzarr_path(url, subdataset)
print(f"\n{'β'*70}")
print(f"π¦ {product_key}")
print(f" Subdataset: {subdataset}")
try:
ds = gdal.Open(path)
if ds:
geoloc = ds.GetMetadata("GEOLOCATION")
if geoloc:
x_path = geoloc.get("X_DATASET", "N/A")
y_path = geoloc.get("Y_DATASET", "N/A")
srs = geoloc.get("SRS", "N/A")
# Extract just the measurement path
x_meas = x_path.split(":")[-1] if x_path else "N/A"
y_meas = y_path.split(":")[-1] if y_path else "N/A"
print(" β
GEOLOCATION metadata found!")
print(f" X_DATASET (Lon): {x_meas}")
print(f" Y_DATASET (Lat): {y_meas}")
print(f" PIXEL_STEP: {geoloc.get('PIXEL_STEP', 'N/A')}")
print(f" LINE_STEP: {geoloc.get('LINE_STEP', 'N/A')}")
# Check if coordinates are WGS84
if "WGS" in srs or "EPSG" in srs:
print(" SRS: WGS 84 (EPSG:4326)")
geoloc_results[product_key] = {
"status": "success",
"x_path": x_path,
"y_path": y_path,
"size": f"{ds.RasterXSize}x{ds.RasterYSize}",
"subdataset": subdataset,
}
else:
print(" β οΈ No GEOLOCATION metadata")
geoloc_results[product_key] = {"status": "no_geoloc"}
ds = None
else:
print(" β Failed to open dataset")
geoloc_results[product_key] = {"status": "failed"}
except Exception as e:
print(f" β Error: {str(e)[:50]}")
geoloc_results[product_key] = {"status": "error", "msg": str(e)}
# Summary
print(f"\n{'='*80}")
print("GEOLOCATION ARRAYS SUMMARY")
print("=" * 80)
success_count = sum(1 for r in geoloc_results.values() if r["status"] == "success")
print(f"\nβ
{success_count}/{len(geoloc_results)} products have GEOLOCATION arrays\n")
print(f"{'Product':<20} {'Status':<12} {'Lon/Lat Arrays'}")
print("-" * 70)
for product, result in geoloc_results.items():
if result["status"] == "success":
lon_arr = result["x_path"].split("/")[-1]
lat_arr = result["y_path"].split("/")[-1]
print(f"{product:<20} {'β
Found':<12} {lon_arr} / {lat_arr}")
else:
print(f"{product:<20} {'β Missing':<12} -")
# Visualize geolocation for ALL 5 products
print("\n" + "=" * 80)
print("GEOGRAPHIC VISUALIZATION - All 5 Sentinel-3 Products")
print("=" * 80)================================================================================
EOPFZARR GEOLOCATION ARRAYS DEMO - All Sentinel-3 Products
================================================================================
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π¦ OLCI_L1_EFR
Subdataset: measurements/oa01_radiance
β
GEOLOCATION metadata found!
X_DATASET (Lon): //measurements/longitude
Y_DATASET (Lat): //measurements/latitude
PIXEL_STEP: 1
LINE_STEP: 1
SRS: WGS 84 (EPSG:4326)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π¦ OLCI_L1_ERR
Subdataset: measurements/oa01_radiance
β
GEOLOCATION metadata found!
X_DATASET (Lon): //measurements/longitude
Y_DATASET (Lat): //measurements/latitude
PIXEL_STEP: 1
LINE_STEP: 1
SRS: WGS 84 (EPSG:4326)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π¦ OLCI_L2_LFR
Subdataset: measurements/gifapar
β
GEOLOCATION metadata found!
X_DATASET (Lon): //measurements/longitude
Y_DATASET (Lat): //measurements/latitude
PIXEL_STEP: 1
LINE_STEP: 1
SRS: WGS 84 (EPSG:4326)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π¦ SLSTR_L1_RBT
Subdataset: measurements/inadir/s8_bt_in
β
GEOLOCATION metadata found!
X_DATASET (Lon): //measurements/inadir/longitude
Y_DATASET (Lat): //measurements/inadir/latitude
PIXEL_STEP: 1
LINE_STEP: 1
SRS: WGS 84 (EPSG:4326)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π¦ SLSTR_L2_LST
Subdataset: measurements/lst
β
GEOLOCATION metadata found!
X_DATASET (Lon): //measurements/longitude
Y_DATASET (Lat): //measurements/latitude
PIXEL_STEP: 1
LINE_STEP: 1
SRS: WGS 84 (EPSG:4326)
================================================================================
GEOLOCATION ARRAYS SUMMARY
================================================================================
β
5/5 products have GEOLOCATION arrays
Product Status Lon/Lat Arrays
----------------------------------------------------------------------
OLCI_L1_EFR β
Found longitude / latitude
OLCI_L1_ERR β
Found longitude / latitude
OLCI_L2_LFR β
Found longitude / latitude
SLSTR_L1_RBT β
Found longitude / latitude
SLSTR_L2_LST β
Found longitude / latitude
================================================================================
GEOGRAPHIC VISUALIZATION - All 5 Sentinel-3 Products
================================================================================
# Create a 5x2 grid for all products (Array View | Geographic View)
fig, axes = plt.subplots(5, 2, figsize=(14, 25))
# All products to visualize
viz_products = [
(
"OLCI_L1_EFR",
"measurements/oa01_radiance",
"OLCI L1 EFR (Oa01 Radiance)",
"viridis",
),
(
"OLCI_L1_ERR",
"measurements/oa01_radiance",
"OLCI L1 ERR (Oa01 Radiance)",
"viridis",
),
("OLCI_L2_LFR", "measurements/gifapar", "OLCI L2 LFR (GIFAPAR)", "RdYlGn"),
("SLSTR_L1_RBT", "measurements/inadir/s8_bt_in", "SLSTR L1 RBT (S8 BT)", "inferno"),
(
"SLSTR_L2_LST",
"measurements/lst",
"SLSTR L2 LST (Land Surface Temp)",
"RdYlBu_r",
),
]
for row, (product_key, subdataset, title, cmap) in enumerate(viz_products):
url = PRODUCTS[product_key]["url"]
path = eopfzarr_path(url, subdataset)
try:
ds = gdal.Open(path)
geoloc = ds.GetMetadata("GEOLOCATION")
if geoloc and ds:
# Read coordinate arrays
lon_ds = gdal.Open(geoloc["X_DATASET"])
lat_ds = gdal.Open(geoloc["Y_DATASET"])
# Read subset
size = 300
lon = lon_ds.GetRasterBand(1).ReadAsArray(
0, 0, min(size, lon_ds.RasterXSize), min(size, lon_ds.RasterYSize)
)
lat = lat_ds.GetRasterBand(1).ReadAsArray(
0, 0, min(size, lat_ds.RasterXSize), min(size, lat_ds.RasterYSize)
)
data = (
ds.GetRasterBand(1)
.ReadAsArray(0, 0, min(size, ds.RasterXSize), min(size, ds.RasterYSize))
.astype(np.float32)
)
# Handle nodata
nodata = ds.GetRasterBand(1).GetNoDataValue()
if nodata is not None:
data[data == nodata] = np.nan
# Match dimensions
min_r = min(data.shape[0], lat.shape[0], lon.shape[0])
min_c = min(data.shape[1], lat.shape[1], lon.shape[1])
data, lat, lon = (
data[:min_r, :min_c],
lat[:min_r, :min_c],
lon[:min_r, :min_c],
)
vmin, vmax = np.nanpercentile(data, [2, 98])
# Array view
im1 = axes[row, 0].imshow(data, cmap=cmap, vmin=vmin, vmax=vmax)
axes[row, 0].set_title(
f"{title}\n(Array View)", fontweight="bold", fontsize=10
)
axes[row, 0].set_xlabel("Column")
axes[row, 0].set_ylabel("Row")
plt.colorbar(im1, ax=axes[row, 0], fraction=0.046)
# Geographic view
step = 2
im2 = axes[row, 1].pcolormesh(
lon[::step, ::step],
lat[::step, ::step],
data[::step, ::step],
cmap=cmap,
vmin=vmin,
vmax=vmax,
shading="auto",
)
axes[row, 1].set_title(
f"{title}\n(Geographic View)", fontweight="bold", fontsize=10
)
axes[row, 1].set_xlabel("Longitude (Β°E)")
axes[row, 1].set_ylabel("Latitude (Β°N)")
axes[row, 1].grid(True, alpha=0.3)
plt.colorbar(im2, ax=axes[row, 1], fraction=0.046)
# Print coordinate range
print(
f"{product_key}: Lat [{np.nanmin(lat):.2f}Β° to {np.nanmax(lat):.2f}Β°], Lon [{np.nanmin(lon):.2f}Β° to {np.nanmax(lon):.2f}Β°]"
)
lon_ds = lat_ds = ds = None
except Exception as e:
print(f"{product_key}: Error - {str(e)[:50]}")
axes[row, 0].text(
0.5,
0.5,
f"Error: {str(e)[:30]}",
ha="center",
va="center",
transform=axes[row, 0].transAxes,
)
axes[row, 1].text(
0.5,
0.5,
f"Error: {str(e)[:30]}",
ha="center",
va="center",
transform=axes[row, 1].transAxes,
)
plt.suptitle(
"EOPFZARR Geolocation Arrays - All 5 Sentinel-3 Products\nSwath (Array) β Geographic Transformation",
fontsize=14,
fontweight="bold",
y=1.01,
)
plt.tight_layout()
plt.show()OLCI_L1_EFR: Lat [-22.03Β° to -21.06Β°], Lon [21.88Β° to 22.88Β°]
OLCI_L1_ERR: Lat [69.04Β° to 72.41Β°], Lon [-76.05Β° to -66.24Β°]
OLCI_L2_LFR: Lat [51.61Β° to 52.45Β°], Lon [14.01Β° to 15.30Β°]
SLSTR_L1_RBT: Lat [17.86Β° to 20.97Β°], Lon [41.93Β° to 45.23Β°]
SLSTR_L2_LST: Lat [38996381.00Β° to 41989955.00Β°], Lon [-6862632.00Β° to -2885982.00Β°]

Product Comparison SummaryΒΆ
# Summary table
print("\n" + "=" * 100)
print("SENTINEL-3 PRODUCT SUMMARY")
print("=" * 100)
print(f"{'Product':<20} {'Subdatasets':<15} {'Description'}")
print("-" * 100)
for key, subdatasets in product_subdatasets.items():
count = len(subdatasets) if subdatasets else 0
desc = PRODUCTS[key]["description"][:50]
print(f"{key:<20} {count:<15} {desc}")
====================================================================================================
SENTINEL-3 PRODUCT SUMMARY
====================================================================================================
Product Subdatasets Description
----------------------------------------------------------------------------------------------------
OLCI_L1_EFR 123 TOA Radiances - 21 spectral bands (400-1020nm)
OLCI_L1_ERR 68 TOA Radiances - Reduced resolution (1.2km)
OLCI_L2_LFR 37 Land products - OGVI, OTCI, RC681, etc.
SLSTR_L1_RBT 530 Radiances and Brightness Temperatures
SLSTR_L2_LST 84 Land Surface Temperature