Skip to content

Commit

Permalink
Add reader for EarthCARE MSI L1c data.
Browse files Browse the repository at this point in the history
  • Loading branch information
simonrp84 committed May 21, 2024
1 parent 5fed712 commit b5b785e
Show file tree
Hide file tree
Showing 3 changed files with 395 additions and 0 deletions.
39 changes: 39 additions & 0 deletions satpy/etc/composites/ec_msi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
sensor_name: visir/ec_msi


modifiers:
sunz_corrected:
modifier: !!python/name:satpy.modifiers.SunZenithCorrector

rayleigh_corrected:
modifier: !!python/name:satpy.modifiers.PSPRayleighReflectance
atmosphere: us-standard
aerosol_type: rayleigh_only
prerequisites:
- name: VIS
modifiers: [sunz_corrected]
optional_prerequisites:
- satellite_azimuth_angle
- satellite_zenith_angle
- solar_azimuth_angle
- solar_zenith_angle

composites:
natural_color_nocorr:
compositor: !!python/name:satpy.composites.GenericCompositor
prerequisites:
- SWIR1
- NIR
- VIS
standard_name: natural_color

natural_color:
compositor: !!python/name:satpy.composites.GenericCompositor
prerequisites:
- name: SWIR1
modifiers: [sunz_corrected]
- name: NIR
modifiers: [sunz_corrected]
- name: VIS
modifiers: [sunz_corrected]
standard_name: natural_color
262 changes: 262 additions & 0 deletions satpy/etc/readers/msi_l1c_earthcare.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
reader:
name: msi_l1c_earthcare
short_name: MSI EarthCARE
long_name: Multispectral Imager for EarthCARE
description: Multispectral Imager for EarthCARE Level 1C (regridded) Reader
status: Nominal
supports_fsspec: true
sensors: [ec_msi]
reader: !!python/name:satpy.readers.yaml_reader.FileYAMLReader


file_types:
msi_l1c_earthcare_rgr:
file_reader: !!python/name:satpy.readers.msi_ec_l1c_h5.MSIECL1CFileHandler
file_patterns:
- '{mission_id:s}_{processing_institute:s}_{sensor_id:s}_{file_id:s}_{proc_level:s}_{start_time:%Y%m%dT%H%M%S}Z_{end_time:%Y%m%dT%H%M%S}Z_{orbit_number:s}{frame_id:s}.h5'


datasets:
# Science measurement datasets
VIS:
name: VIS
sensor: ec_msi
wavelength: [0.66, 0.67, 0.68]
resolution: 500
calibration:
reflectance:
standard_name: toa_bidirectional_reflectance
units: "%"
radiance:
standard_name: toa_outgoing_radiance
units: W m-2 sr-1
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/pixel_values
band_index: 0
coordinates: [longitude, latitude]
NIR:
name: NIR
sensor: ec_msi
wavelength: [0.855, 0.865, 0.875]
resolution: 500
calibration:
reflectance:
standard_name: toa_bidirectional_reflectance
units: "%"
radiance:
standard_name: toa_outgoing_radiance
units: W m-2 sr-1
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/pixel_values
band_index: 1
coordinates: [longitude, latitude]
SWIR1:
name: SWIR1
sensor: ec_msi
wavelength: [1.64, 1.67, 1.70]
resolution: 500
calibration:
reflectance:
standard_name: toa_bidirectional_reflectance
units: "%"
radiance:
standard_name: toa_outgoing_radiance
units: W m-2 sr-1
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/pixel_values
band_index: 2
coordinates: [longitude, latitude]
SWIR2:
name: SWIR2
sensor: ec_msi
wavelength: [2.16, 2.21, 2.26]
resolution: 500
calibration:
reflectance:
standard_name: toa_bidirectional_reflectance
units: "%"
radiance:
standard_name: toa_outgoing_radiance
units: W m-2 sr-1
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/pixel_values
band_index: 3
coordinates: [longitude, latitude]
TIR1:
name: TIR1
sensor: ec_msi
wavelength: [8.35, 8.80, 9.25]
resolution: 500
calibration:
brightness_temperature:
standard_name: toa_brightness_temperature
units: "K"
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/pixel_values
band_index: 4
coordinates: [longitude, latitude]
TIR2:
name: TIR2
sensor: ec_msi
wavelength: [10.35, 10.80, 11.25]
resolution: 500
calibration:
brightness_temperature:
standard_name: toa_brightness_temperature
units: "K"
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/pixel_values
band_index: 5
coordinates: [longitude, latitude]
TIR3:
name: TIR3
sensor: ec_msi
wavelength: [11.55,12.00,12.45]
resolution: 500
calibration:
brightness_temperature:
standard_name: toa_brightness_temperature
units: "K"
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/pixel_values
band_index: 6
coordinates: [longitude, latitude]

# Relative error datasets
VIS_rel_error:
name: VIS_rel_error
sensor: ec_msi
resolution: 500
standard_name: relative_error_in_toa_radiance
units: "%"
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/pixel_values_relative_error
band_index: 0
NIR_rel_error:
name: NIR_rel_error
sensor: ec_msi
resolution: 500
standard_name: relative_error_in_toa_radiance
units: "%"
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/pixel_values_relative_error
band_index: 1
SWIR1_rel_error:
name: SWIR1_rel_error
sensor: ec_msi
resolution: 500
standard_name: relative_error_in_toa_radiance
units: "%"
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/pixel_values_relative_error
band_index: 2
SWIR2_rel_error:
name: SWIR2_rel_error
sensor: ec_msi
resolution: 500
standard_name: relative_error_in_toa_radiance
units: "%"
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/pixel_values_relative_error
band_index: 3
TIR1_rel_error:
name: TIR1_rel_error
sensor: ec_msi
resolution: 500
standard_name: relative_error_in_toa_brightness_temperature
units: "%"
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/pixel_values_relative_error
band_index: 4
TIR2_rel_error:
name: TIR2_rel_error
sensor: ec_msi
resolution: 500
standard_name: relative_error_in_toa_brightness_temperature
units: "%"
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/pixel_values_relative_error
band_index: 5
TIR3_rel_error:
name: TIR3_rel_error
sensor: ec_msi
resolution: 500
standard_name: relative_error_in_toa_brightness_temperature
units: "%"
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/pixel_values_relative_error
band_index: 6

# Geolocation data
longitude:
name: longitude
units: degrees_east
standard_name: longitude
resolution: 500
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/longitude
latitude:
name: latitude
units: degrees_north
standard_name: latitude
resolution: 500
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/latitude
solar_azimuth_angle:
name: solar_azimuth_angle
units: degree
standard_name: solar_azimuth_angle
resolution: 500
coordinates: [longitude, latitude]
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/solar_azimuth_angle
sensor_azimuth_angle:
name: sensor_azimuth_angle
units: degree
standard_name: sensor_azimuth_angle
resolution: 500
coordinates: [longitude, latitude]
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/sensor_azimuth_angle
sensor_view_angle:
name: sensor_zenith_angle
units: degree
standard_name: sensor_zenith_angle
resolution: 500
coordinates: [longitude, latitude]
file_type: msi_l1c_earthcare_rgr
file_key: NonStandard/sensor_view_angle
solar_zenith_angle:
name: solar_zenith_angle
units: degree
standard_name: solar_zenith_angle
resolution: 500
coordinates: [longitude, latitude]
file_type: msi_l1c_earthcare_rgr
file_key: NonStandard/solar_zenith_angle

# Ancillary data
land_flag:
name: land_water_mask
units: 1
standard_name: land_water_mask
resolution: 500
coordinates: [longitude, latitude]
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/land_flag
surface_elevation:
name: surface_elevation
units: m
standard_name: surface_elevation
resolution: 500
coordinates: [longitude, latitude]
file_type: msi_l1c_earthcare_rgr
file_key: ScienceData/surface_elevation
surface_index:
name: surface_index
units: 1
standard_name: surface_index
resolution: 500
coordinates: [longitude, latitude]
file_type: msi_l1c_earthcare_rgr
file_key: NonStandard/surface_index
94 changes: 94 additions & 0 deletions satpy/readers/msi_ec_l1c_h5.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2024.
#
# This file is part of satpy.
#
# satpy is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# satpy is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# satpy. If not, see <http://www.gnu.org/licenses/>.
"""A reader for Level 1C data produced by the MSI instrument aboard EarthCARE."""
import logging

from satpy.readers.hdf5_utils import HDF5FileHandler
from satpy.utils import get_legacy_chunk_size

LOG = logging.getLogger(__name__)
CHUNK_SIZE = get_legacy_chunk_size()


class MSIECL1CFileHandler(HDF5FileHandler):
"""File handler for MSI L1c H5 files."""

def __init__(self, filename, filename_info, filetype_info):
"""Init the file handler."""
super(MSIECL1CFileHandler, self).__init__(filename,
filename_info,
filetype_info)

@property
def end_time(self):
"""Get end time."""
return self.filename_info["end_time"]

@property
def start_time(self):
"""Get start time."""
return self.filename_info["start_time"]

def get_dataset(self, dataset_id, ds_info):
"""Load data variable and metadata and calibrate if needed."""
file_key = ds_info.get("file_key", dataset_id["name"])
data = self[file_key]

# Band data is stored in a 3d array (Band x Along_Track x Across_Track).
# This means we have to select a single 2d array for a given band,
# and the correct index is given in the reader YAML.
band_index = ds_info.get("band_index")
if band_index is not None:
data = data[band_index]

# The dataset has incorrect units attribute (due to storing multiple types). Fix it here.
data.attrs.update(ds_info)
data.attrs.update({"units": ds_info.get("units")})

# VIS/SWIR data can have radiance or reflectance calibration.
if "calibration" in ds_info:
if ds_info["calibration"].name == "reflectance":
data = self._calibrate(data, band_index)
elif ds_info["calibration"].name not in ["radiance", "brightness_temperature"]:
raise ValueError(f"Unknown calibration type:{ds_info['calibration'].name}")

# Rename dimensions, as some have incorrect names (notably the pixel value data).
if "dim_1" in data.dims:
data = data.rename({"dim_1": "y", "dim_2": "x"})

# The dimension list is usually a reference to an H5 variable, which is problematic
# when making a copy of the data. This sorts out the dimensions and sets them correctly
# following the process done in the OMPS reader.
if "DIMENSION_LIST" in data.attrs:
data.attrs.pop("DIMENSION_LIST")
dimensions = self.get_reference(file_key, "DIMENSION_LIST")
dim_dict = {}
# We have to loop over dimensions to match dim sizes as the pixel data is 3d rather than 2d.
for i in range(0, len(data.dims)):
c_dim = data.dims[i]
for r_dim in dimensions:
if data.shape[i] == r_dim[0].shape[0]:
dim_dict[c_dim] = r_dim[0]
data.assign_coords(dim_dict)
return data

def _calibrate(self, data, band_index):
"""Calibrate the data."""
sol_irrad = self["NonStandard/solar_irradiance"]

return 100 * data / sol_irrad[band_index]

0 comments on commit b5b785e

Please sign in to comment.