Skip to content

modis_l2

MODIS L2 Swath Reader

MODISL2Reader

Bases: GriddedReader

Reader for MODIS L2 swath data.

Source code in monetio/readers/modis_l2.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
@register_reader("modis_l2")
class MODISL2Reader(GriddedReader):
    """
    Reader for MODIS L2 swath data.
    """

    def open_dataset(
        self,
        files: str | list[str],
        variable_dict: dict = None,
        **kwargs,
    ) -> xr.Dataset:
        """
        Reads MODIS L2 swath data.

        Parameters
        ----------
        files : Union[str, List[str]]
            File path(s) or URL(s).
        variable_dict : dict, optional
            Dictionary of variables to read with metadata (scale, minimum, maximum, quality_flag).
            Example: `{'AOD_550': {'scale': 0.001, 'quality_flag': 3}}`
        **kwargs : dict
            Additional arguments passed to the reader.

        Returns
        -------
        xr.Dataset
            The processed MODIS dataset.

        Examples
        --------
        >>> reader = MODISL2Reader()
        >>> vdict = {'Deep_Blue_Aerosol_Optical_Depth_550_Land': {'scale': 0.001}}
        >>> ds = reader.open_dataset('MYD04_L2.A2023126.1830.061.2023127154555.hdf', variable_dict=vdict)
        """
        if "preprocess" not in kwargs:
            kwargs["preprocess"] = lambda ds: modis_l2_preprocess(ds, variable_dict=variable_dict)

        # If variable_dict is provided, we can try to only load those variables
        # plus the required coordinates (Latitude, Longitude, Scan_Start_Time)
        if variable_dict is not None and "drop_variables" not in kwargs:
            # We don't know all variables in the file without opening it,
            # but we can specify which ones we WANT if the engine supports it.
            # Most xarray engines don't have an 'only_variables' but have 'drop_variables'.
            # For now, we'll load everything and filter in preprocess,
            # or the user can pass drop_variables.
            pass

        ds = super().open_dataset(files, **kwargs)

        # Filter to requested variables if variable_dict is provided
        if variable_dict is not None:
            # Keep variables in variable_dict
            vars_to_keep = list(variable_dict.keys())

            # Only filter data_vars, preserve ALL coordinates
            available_vars = [v for v in vars_to_keep if v in ds.data_vars]
            ds = ds[available_vars]

        # Update history
        ds = update_history(ds, "Read MODIS L2 data.")

        return ds

open_dataset(files, variable_dict=None, **kwargs)

Reads MODIS L2 swath data.

Parameters:

Name Type Description Default
files Union[str, List[str]]

File path(s) or URL(s).

required
variable_dict dict

Dictionary of variables to read with metadata (scale, minimum, maximum, quality_flag). Example: {'AOD_550': {'scale': 0.001, 'quality_flag': 3}}

None
**kwargs dict

Additional arguments passed to the reader.

{}

Returns:

Type Description
Dataset

The processed MODIS dataset.

Examples:

>>> reader = MODISL2Reader()
>>> vdict = {'Deep_Blue_Aerosol_Optical_Depth_550_Land': {'scale': 0.001}}
>>> ds = reader.open_dataset('MYD04_L2.A2023126.1830.061.2023127154555.hdf', variable_dict=vdict)
Source code in monetio/readers/modis_l2.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def open_dataset(
    self,
    files: str | list[str],
    variable_dict: dict = None,
    **kwargs,
) -> xr.Dataset:
    """
    Reads MODIS L2 swath data.

    Parameters
    ----------
    files : Union[str, List[str]]
        File path(s) or URL(s).
    variable_dict : dict, optional
        Dictionary of variables to read with metadata (scale, minimum, maximum, quality_flag).
        Example: `{'AOD_550': {'scale': 0.001, 'quality_flag': 3}}`
    **kwargs : dict
        Additional arguments passed to the reader.

    Returns
    -------
    xr.Dataset
        The processed MODIS dataset.

    Examples
    --------
    >>> reader = MODISL2Reader()
    >>> vdict = {'Deep_Blue_Aerosol_Optical_Depth_550_Land': {'scale': 0.001}}
    >>> ds = reader.open_dataset('MYD04_L2.A2023126.1830.061.2023127154555.hdf', variable_dict=vdict)
    """
    if "preprocess" not in kwargs:
        kwargs["preprocess"] = lambda ds: modis_l2_preprocess(ds, variable_dict=variable_dict)

    # If variable_dict is provided, we can try to only load those variables
    # plus the required coordinates (Latitude, Longitude, Scan_Start_Time)
    if variable_dict is not None and "drop_variables" not in kwargs:
        # We don't know all variables in the file without opening it,
        # but we can specify which ones we WANT if the engine supports it.
        # Most xarray engines don't have an 'only_variables' but have 'drop_variables'.
        # For now, we'll load everything and filter in preprocess,
        # or the user can pass drop_variables.
        pass

    ds = super().open_dataset(files, **kwargs)

    # Filter to requested variables if variable_dict is provided
    if variable_dict is not None:
        # Keep variables in variable_dict
        vars_to_keep = list(variable_dict.keys())

        # Only filter data_vars, preserve ALL coordinates
        available_vars = [v for v in vars_to_keep if v in ds.data_vars]
        ds = ds[available_vars]

    # Update history
    ds = update_history(ds, "Read MODIS L2 data.")

    return ds

modis_l2_preprocess(ds, variable_dict=None)

Preprocess MODIS L2 dataset: standardize coords, apply scaling/clipping, and quality flags.

Parameters:

Name Type Description Default
ds Dataset

Input dataset.

required
variable_dict dict

Metadata for variables.

None

Returns:

Type Description
Dataset

Processed dataset.

Examples:

>>> vdict = {'AOD': {'scale': 0.001, 'quality_flag': 2}, 'Quality_Assurance': {}}
>>> ds = modis_l2_preprocess(ds, variable_dict=vdict)
Source code in monetio/readers/modis_l2.py
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
def modis_l2_preprocess(ds: xr.Dataset, variable_dict: dict = None) -> xr.Dataset:
    """
    Preprocess MODIS L2 dataset: standardize coords, apply scaling/clipping, and quality flags.

    Parameters
    ----------
    ds : xr.Dataset
        Input dataset.
    variable_dict : dict, optional
        Metadata for variables.

    Returns
    -------
    xr.Dataset
        Processed dataset.

    Examples
    --------
    >>> vdict = {'AOD': {'scale': 0.001, 'quality_flag': 2}, 'Quality_Assurance': {}}
    >>> ds = modis_l2_preprocess(ds, variable_dict=vdict)
    """
    # 1. Standardize dimensions and coordinates
    # MODIS L2 uses 'Cell_Along_Swath' and 'Cell_Across_Swath'
    ds = standardize_satellite_coords(
        ds,
        y_dim=["Cell_Along_Swath", "Rows", "scanline"],
        x_dim=["Cell_Across_Swath", "Columns", "ground_pixel"],
    )

    # 2. Add time coordinate if Scan_Start_Time is present
    if "Scan_Start_Time" in ds.variables and "time" not in ds.coords:
        ds["time"] = tai93_to_datetime(ds["Scan_Start_Time"])
        ds = ds.set_coords("time")

    # 3. Apply variable_dict transformations (scale, minimum, maximum)
    quality_masks = []
    if variable_dict:
        for varname, meta in variable_dict.items():
            if varname in ds.variables:
                if "scale" in meta:
                    ds[varname] = ds[varname] * meta["scale"]
                if "minimum" in meta:
                    ds[varname] = ds[varname].where(ds[varname] >= meta["minimum"])
                if "maximum" in meta:
                    ds[varname] = ds[varname].where(ds[varname] <= meta["maximum"])

                if "quality_flag" in meta:
                    # Collect quality flags for batch masking
                    quality_masks.append((varname, meta["quality_flag"]))

    # 4. Apply Quality Flag masking (Lazy & Vectorized)
    if quality_masks:
        combined_mask = None
        for q_var, q_thresh in quality_masks:
            mask = ds[q_var] >= q_thresh
            if combined_mask is None:
                combined_mask = mask
            else:
                combined_mask = combined_mask & mask

        if combined_mask is not None:
            for varname in ds.data_vars:
                # Mask all data variables by the combined quality flag
                ds[varname] = ds[varname].where(combined_mask)

    ds = update_history(ds, "Preprocessed MODIS L2 data using standardized preprocessing.")

    return ds