Skip to content

raqms

RAQMS Reader

RAQMSReader

Bases: GriddedReader

Reader for RAQMS (Real-time Air Quality Modeling System) model output files.

Source code in monetio/readers/raqms.py
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
73
74
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
@register_reader("raqms")
class RAQMSReader(GriddedReader):
    """
    Reader for RAQMS (Real-time Air Quality Modeling System) model output files.
    """

    def open_dataset(
        self,
        files: str | list[str],
        convert_to_ppb: bool = True,
        var_list: list[str] | None = None,
        surf_only: bool = False,
        **kwargs: Any,
    ) -> xr.Dataset:
        """
        Reads RAQMS netCDF files.

        Parameters
        ----------
        files : Union[str, List[str]]
            File path, list of paths, or glob pattern.
        convert_to_ppb : bool, optional
            Convert gas species from ppv to ppbv, by default True.
        var_list : list of str, optional
            List of variables to keep, by default None.
        surf_only : bool, optional
            Whether to only return the surface layer, by default False.
        **kwargs : Any
            Additional arguments passed to xarray.open_mfdataset or the driver.

        Returns
        -------
        xarray.Dataset
            The processed RAQMS dataset.

        Examples
        --------
        >>> from monetio.readers.raqms import RAQMSReader
        >>> reader = RAQMSReader()
        >>> ds = reader.open_dataset("uwhyb_*.nc")
        """
        # RAQMS check file format
        if isinstance(files, str):
            fpaths = sorted(glob(files))
        else:
            fpaths = sorted(files)

        if not fpaths or not all(
            fp.endswith(".nc") and "uwhyb" in os.path.basename(fp) for fp in fpaths
        ):
            raise ValueError(
                "File format not supported. Note that files should be preprocessed to netCDF."
            )

        # 1. Setup preprocessing
        if "preprocess" not in kwargs:
            kwargs["preprocess"] = partial(
                raqms_preprocess,
                convert_to_ppb=convert_to_ppb,
                var_list=var_list,
                surf_only=surf_only,
            )

        # Prepare kwargs
        if "concat_dim" not in kwargs:
            kwargs["concat_dim"] = "time"
        if "combine" not in kwargs:
            kwargs["combine"] = "nested"

        # RAQMS specific drop
        if "drop_variables" not in kwargs:
            kwargs["drop_variables"] = ["theta"]
        elif "theta" not in kwargs["drop_variables"]:
            if isinstance(kwargs["drop_variables"], list):
                kwargs["drop_variables"].append("theta")

        # 2. Open the dataset using standard xarray (via XarrayDriver)
        # Use fpaths instead of files to ensure consistent set of files
        ds = self.driver.open(fpaths, **kwargs)

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

        return ds

open_dataset(files, convert_to_ppb=True, var_list=None, surf_only=False, **kwargs)

Reads RAQMS netCDF files.

Parameters:

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

File path, list of paths, or glob pattern.

required
convert_to_ppb bool

Convert gas species from ppv to ppbv, by default True.

True
var_list list of str

List of variables to keep, by default None.

None
surf_only bool

Whether to only return the surface layer, by default False.

False
**kwargs Any

Additional arguments passed to xarray.open_mfdataset or the driver.

{}

Returns:

Type Description
Dataset

The processed RAQMS dataset.

Examples:

>>> from monetio.readers.raqms import RAQMSReader
>>> reader = RAQMSReader()
>>> ds = reader.open_dataset("uwhyb_*.nc")
Source code in monetio/readers/raqms.py
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
73
74
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
def open_dataset(
    self,
    files: str | list[str],
    convert_to_ppb: bool = True,
    var_list: list[str] | None = None,
    surf_only: bool = False,
    **kwargs: Any,
) -> xr.Dataset:
    """
    Reads RAQMS netCDF files.

    Parameters
    ----------
    files : Union[str, List[str]]
        File path, list of paths, or glob pattern.
    convert_to_ppb : bool, optional
        Convert gas species from ppv to ppbv, by default True.
    var_list : list of str, optional
        List of variables to keep, by default None.
    surf_only : bool, optional
        Whether to only return the surface layer, by default False.
    **kwargs : Any
        Additional arguments passed to xarray.open_mfdataset or the driver.

    Returns
    -------
    xarray.Dataset
        The processed RAQMS dataset.

    Examples
    --------
    >>> from monetio.readers.raqms import RAQMSReader
    >>> reader = RAQMSReader()
    >>> ds = reader.open_dataset("uwhyb_*.nc")
    """
    # RAQMS check file format
    if isinstance(files, str):
        fpaths = sorted(glob(files))
    else:
        fpaths = sorted(files)

    if not fpaths or not all(
        fp.endswith(".nc") and "uwhyb" in os.path.basename(fp) for fp in fpaths
    ):
        raise ValueError(
            "File format not supported. Note that files should be preprocessed to netCDF."
        )

    # 1. Setup preprocessing
    if "preprocess" not in kwargs:
        kwargs["preprocess"] = partial(
            raqms_preprocess,
            convert_to_ppb=convert_to_ppb,
            var_list=var_list,
            surf_only=surf_only,
        )

    # Prepare kwargs
    if "concat_dim" not in kwargs:
        kwargs["concat_dim"] = "time"
    if "combine" not in kwargs:
        kwargs["combine"] = "nested"

    # RAQMS specific drop
    if "drop_variables" not in kwargs:
        kwargs["drop_variables"] = ["theta"]
    elif "theta" not in kwargs["drop_variables"]:
        if isinstance(kwargs["drop_variables"], list):
            kwargs["drop_variables"].append("theta")

    # 2. Open the dataset using standard xarray (via XarrayDriver)
    # Use fpaths instead of files to ensure consistent set of files
    ds = self.driver.open(fpaths, **kwargs)

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

    return ds

raqms_preprocess(ds, *, convert_to_ppb=True, var_list=None, surf_only=False)

Preprocess function for a single RAQMS file.

Parameters:

Name Type Description Default
ds Dataset

Input RAQMS dataset.

required
convert_to_ppb bool

Convert gas species to ppbv, by default True.

True
var_list list of str

List of variables to keep, by default None.

None
surf_only bool

Whether to keep only the surface layer, by default False.

False

Returns:

Type Description
Dataset

Processed dataset.

Source code in monetio/readers/raqms.py
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
def raqms_preprocess(
    ds: xr.Dataset,
    *,
    convert_to_ppb: bool = True,
    var_list: list[str] | None = None,
    surf_only: bool = False,
) -> xr.Dataset:
    """
    Preprocess function for a single RAQMS file.

    Parameters
    ----------
    ds : xarray.Dataset
        Input RAQMS dataset.
    convert_to_ppb : bool, optional
        Convert gas species to ppbv, by default True.
    var_list : list of str, optional
        List of variables to keep, by default None.
    surf_only : bool, optional
        Whether to keep only the surface layer, by default False.

    Returns
    -------
    xarray.Dataset
        Processed dataset.
    """
    # 1. Variable selection
    if var_list is not None:
        required = [
            "lat",
            "lon",
            "IDATE",
            "Times",
            "psfc",
            "delp",
            "pdash",
            "ttheta",
        ]
        vars_to_keep = list(set(var_list + required))
        vars_to_keep = [v for v in vars_to_keep if v in ds.variables]
        ds = ds[vars_to_keep]

    # 2. Grid and Coordinates
    ds = _fix_grid(ds)

    # 3. Time
    ds = _fix_time(ds)

    # 4. Pressure
    ds = _fix_pres(ds)

    # 5. Surface only
    if surf_only:
        # Check if 'z' dimension exists before slicing
        if "z" in ds.dims:
            ds = ds.isel(z=0).expand_dims("z")

    # 6. Unit conversion
    if convert_to_ppb:
        to_convert = [v for v in ds.data_vars if ds[v].attrs.get("units") == "ppv"]
        if to_convert:
            for v in to_convert:
                with xr.set_options(keep_attrs=True):
                    ds[v] = ds[v] * 1e9
                ds[v].attrs["units"] = "ppbv"
            ds = update_history(ds, f"Converted {', '.join(to_convert)} from ppv to ppbv.")

    # 7. Temperature
    if "ttheta" in ds.data_vars and "pres_pa_mid" in ds.data_vars:
        k = 0.28571428571428564  # R/cp = kappa
        with xr.set_options(keep_attrs=True):
            ds["temperature_k"] = ds["ttheta"] * (ds["pres_pa_mid"] / 100000) ** k
        ds["temperature_k"].attrs.update({"units": "K", "long_name": "Temperature"})
        ds = update_history(ds, "Calculated temperature_k from ttheta and pres_pa_mid.")

    # 8. Transpose
    dims = [d for d in ["time", "z", "y", "x"] if d in ds.dims]
    ds = ds.transpose(*dims)

    # Update history
    ds = update_history(ds, "Preprocessed RAQMS data.")

    return ds