Skip to content

nesdis_frp

NESDIS FRP Reader

NESDISFRPReader

Bases: GriddedReader

Reader for NESDIS Fire Radiative Power (FRP) data on FV3 C384 grid.

Source code in monetio/readers/nesdis_frp.py
 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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
@register_reader("nesdis_frp")
class NESDISFRPReader(GriddedReader):
    """
    Reader for NESDIS Fire Radiative Power (FRP) data on FV3 C384 grid.
    """

    def open_dataset(
        self,
        files: str | list[str] = None,
        date: datetime.datetime | str | pd.Timestamp = None,
        ftype: str = "meanFRP",
        **kwargs,
    ) -> xr.Dataset:
        """
        Reads NESDIS FRP data.

        Parameters
        ----------
        files : str or list[str], optional
            File path(s) or URL(s).
        date : datetime.datetime, str, or pd.Timestamp, optional
            Date to retrieve. If files is None, this is used to build URLs.
        ftype : str, optional
            Type of FRP data (e.g., 'meanFRP'). Default is 'meanFRP'.
        **kwargs : dict
            Additional arguments passed to XarrayDriver.open.

        Returns
        -------
        xr.Dataset
            The NESDIS FRP dataset.

        Examples
        --------
        >>> reader = NESDISFRPReader()
        >>> ds = reader.open_dataset(date="2023-01-01", ftype="meanFRP")
        """
        if files is None:
            if date is None:
                raise ValueError("Either 'files' or 'date' must be provided.")
            files = self.build_urls(date, ftype=ftype)

        if "preprocess" not in kwargs:
            kwargs["preprocess"] = partial(nesdis_frp_preprocess, ftype=ftype)

        if "read_method" not in kwargs:
            kwargs["read_method"] = read_nesdis_frp_binary

        # Forward ftype to read_method
        kwargs["ftype"] = ftype

        # We concatenate tiles in the reading step if possible, or use XarrayDriver's concat
        # Actually, each file is a tile.
        if "concat_dim" not in kwargs:
            kwargs["concat_dim"] = "tile"
        if "combine" not in kwargs:
            kwargs["combine"] = "nested"

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

        # Update history
        ds = update_history(ds, f"Read NESDIS {ftype} data.")

        return ds

    def build_urls(
        self, date: datetime.datetime | str | pd.Timestamp, ftype: str = "meanFRP"
    ) -> list[str]:
        """
        Build URLs for NESDIS FRP data based on date.

        Parameters
        ----------
        date : datetime.datetime, str, or pd.Timestamp
            Date to retrieve.
        ftype : str, optional
            File type (e.g., 'meanFRP'), by default "meanFRP".

        Returns
        -------
        list[str]
            List of URLs.

        Examples
        --------
        >>> reader = NESDISFRPReader()
        >>> urls = reader.build_urls("2023-01-01")
        """
        date = pd.Timestamp(date)
        yyyymmdd = date.strftime("%Y%m%d")
        url_ftype = f"&files={ftype}."

        urls = []
        for i in range(1, 7):
            tile = f".FV3C384Grid.tile{i}.bin"
            url = f"{BASE_URL}{yyyymmdd}{url_ftype}{yyyymmdd}{tile}"
            urls.append(url)

        return urls

build_urls(date, ftype='meanFRP')

Build URLs for NESDIS FRP data based on date.

Parameters:

Name Type Description Default
date datetime.datetime, str, or pd.Timestamp

Date to retrieve.

required
ftype str

File type (e.g., 'meanFRP'), by default "meanFRP".

'meanFRP'

Returns:

Type Description
list[str]

List of URLs.

Examples:

>>> reader = NESDISFRPReader()
>>> urls = reader.build_urls("2023-01-01")
Source code in monetio/readers/nesdis_frp.py
 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
def build_urls(
    self, date: datetime.datetime | str | pd.Timestamp, ftype: str = "meanFRP"
) -> list[str]:
    """
    Build URLs for NESDIS FRP data based on date.

    Parameters
    ----------
    date : datetime.datetime, str, or pd.Timestamp
        Date to retrieve.
    ftype : str, optional
        File type (e.g., 'meanFRP'), by default "meanFRP".

    Returns
    -------
    list[str]
        List of URLs.

    Examples
    --------
    >>> reader = NESDISFRPReader()
    >>> urls = reader.build_urls("2023-01-01")
    """
    date = pd.Timestamp(date)
    yyyymmdd = date.strftime("%Y%m%d")
    url_ftype = f"&files={ftype}."

    urls = []
    for i in range(1, 7):
        tile = f".FV3C384Grid.tile{i}.bin"
        url = f"{BASE_URL}{yyyymmdd}{url_ftype}{yyyymmdd}{tile}"
        urls.append(url)

    return urls

open_dataset(files=None, date=None, ftype='meanFRP', **kwargs)

Reads NESDIS FRP data.

Parameters:

Name Type Description Default
files str or list[str]

File path(s) or URL(s).

None
date datetime.datetime, str, or pd.Timestamp

Date to retrieve. If files is None, this is used to build URLs.

None
ftype str

Type of FRP data (e.g., 'meanFRP'). Default is 'meanFRP'.

'meanFRP'
**kwargs dict

Additional arguments passed to XarrayDriver.open.

{}

Returns:

Type Description
Dataset

The NESDIS FRP dataset.

Examples:

>>> reader = NESDISFRPReader()
>>> ds = reader.open_dataset(date="2023-01-01", ftype="meanFRP")
Source code in monetio/readers/nesdis_frp.py
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
def open_dataset(
    self,
    files: str | list[str] = None,
    date: datetime.datetime | str | pd.Timestamp = None,
    ftype: str = "meanFRP",
    **kwargs,
) -> xr.Dataset:
    """
    Reads NESDIS FRP data.

    Parameters
    ----------
    files : str or list[str], optional
        File path(s) or URL(s).
    date : datetime.datetime, str, or pd.Timestamp, optional
        Date to retrieve. If files is None, this is used to build URLs.
    ftype : str, optional
        Type of FRP data (e.g., 'meanFRP'). Default is 'meanFRP'.
    **kwargs : dict
        Additional arguments passed to XarrayDriver.open.

    Returns
    -------
    xr.Dataset
        The NESDIS FRP dataset.

    Examples
    --------
    >>> reader = NESDISFRPReader()
    >>> ds = reader.open_dataset(date="2023-01-01", ftype="meanFRP")
    """
    if files is None:
        if date is None:
            raise ValueError("Either 'files' or 'date' must be provided.")
        files = self.build_urls(date, ftype=ftype)

    if "preprocess" not in kwargs:
        kwargs["preprocess"] = partial(nesdis_frp_preprocess, ftype=ftype)

    if "read_method" not in kwargs:
        kwargs["read_method"] = read_nesdis_frp_binary

    # Forward ftype to read_method
    kwargs["ftype"] = ftype

    # We concatenate tiles in the reading step if possible, or use XarrayDriver's concat
    # Actually, each file is a tile.
    if "concat_dim" not in kwargs:
        kwargs["concat_dim"] = "tile"
    if "combine" not in kwargs:
        kwargs["combine"] = "nested"

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

    # Update history
    ds = update_history(ds, f"Read NESDIS {ftype} data.")

    return ds

nesdis_frp_preprocess(ds, ftype='meanFRP')

Preprocess NESDIS FRP dataset: assign coordinates and metadata.

Parameters:

Name Type Description Default
ds Dataset

Input dataset.

required
ftype str

File type, by default "meanFRP".

'meanFRP'

Returns:

Type Description
Dataset

Processed dataset.

Examples:

>>> ds = nesdis_frp_preprocess(ds, ftype="meanFRP")
Source code in monetio/readers/nesdis_frp.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
def nesdis_frp_preprocess(ds: xr.Dataset, ftype: str = "meanFRP") -> xr.Dataset:
    """
    Preprocess NESDIS FRP dataset: assign coordinates and metadata.

    Parameters
    ----------
    ds : xr.Dataset
        Input dataset.
    ftype : str, optional
        File type, by default "meanFRP".

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

    Examples
    --------
    >>> ds = nesdis_frp_preprocess(ds, ftype="meanFRP")
    """
    # 1. Rename to ftype if it was generic
    if "frp" in ds.data_vars and ftype != "frp":
        ds = ds.rename({"frp": ftype})

    # 2. Handle Grid and Coordinates
    # We assume C384 for now as per legacy reader
    res = "C384"
    # ds.tile is usually a scalar coordinate if it's from a single file (tile)
    # but could be an array if concatenated.
    try:
        tile = int(ds.tile.values) if not hasattr(ds.tile.data, "dask") else None
    except (TypeError, ValueError):
        tile = None

    # If tile is dask-backed, we might need to be careful.
    # But tile should be a coordinate, usually small and eager.
    if tile is not None:
        try:
            import fv3grid as fg

            grid = fg.get_fv3_grid(res=res, tile=tile)
            # Wrap longitudes to [-180, 180]
            lon = (grid.longitude + 180) % 360 - 180
            lat = grid.latitude

            ds = ds.assign_coords(
                latitude=(("x", "y"), lat),
                longitude=(("x", "y"), lon),
            )

            ds.latitude.attrs.update({"units": "degrees_north", "standard_name": "latitude"})
            ds.longitude.attrs.update({"units": "degrees_east", "standard_name": "longitude"})
        except ImportError:
            pass

    # 3. Scientific Hygiene: Metadata
    if ftype in ds.data_vars:
        ds[ftype].attrs.update(
            {
                "long_name": f"NESDIS {ftype} Fire Radiative Power",
                "units": "MW",  # Assuming MW for FRP
            }
        )

    # Provenance
    ds = update_history(ds, f"Preprocessed NESDIS {ftype} data using standardized preprocessing.")

    return ds

read_nesdis_frp_binary(fname, **kwargs)

Read a single NESDIS FRP tile from a binary file. Supports streaming from fsspec-compatible files.

Parameters:

Name Type Description Default
fname str

Path or URL to the binary file.

required
**kwargs dict

Additional arguments (res, dtype, lazy).

{}

Returns:

Type Description
Dataset

The tile data as a Dataset.

Examples:

>>> ds = read_nesdis_frp_binary("meanFRP.20230101.FV3.C384Grid.tile1.bin")
Source code in monetio/readers/nesdis_frp.py
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
def read_nesdis_frp_binary(fname: str, **kwargs) -> xr.Dataset:
    """
    Read a single NESDIS FRP tile from a binary file.
    Supports streaming from fsspec-compatible files.

    Parameters
    ----------
    fname : str
        Path or URL to the binary file.
    **kwargs : dict
        Additional arguments (res, dtype, lazy).

    Returns
    -------
    xr.Dataset
        The tile data as a Dataset.

    Examples
    --------
    >>> ds = read_nesdis_frp_binary("meanFRP.20230101.FV3.C384Grid.tile1.bin")
    """
    res = kwargs.get("res", "C384")
    dtype = kwargs.get("dtype", "f4")
    lazy = kwargs.get("lazy", "chunks" in kwargs)

    r = int(res[1:])
    shape = (r, r)

    def _read_core(filename):
        from scipy.io import FortranFile

        from .drivers import FileUtility

        fs = FileUtility.get_fs(filename)
        with fs.open(filename, "rb") as f:
            # We need to wrap it in a seekable stream for FortranFile if it's remote
            # But FortranFile might not like fsspec file objects if they aren't fully seekable/buffered
            # Alternatively, read it all and use BytesIO
            import io

            # Ensure we are at the start and the stream is seekable for FortranFile
            buffer = io.BytesIO(f.read())
            w = FortranFile(buffer)
            try:
                a = w.read_reals(dtype=dtype)
            except Exception:
                # Fallback: maybe it's not a reals record but a simple binary dump
                # FortranFile expects header/footer. If missing, it fails.
                buffer.seek(0)
                a = np.frombuffer(buffer.read(), dtype=dtype)
        return a.reshape((r, r), order="F").copy()

    if lazy:
        import dask.array as da
        from dask import delayed

        load_tile = delayed(_read_core)(fname)
        data = da.from_delayed(load_tile, shape=shape, dtype=np.dtype(dtype))
    else:
        data = _read_core(fname)

    # Extract tile and date from filename if possible
    # Example: meanFRP.20230101.FV3.C384Grid.tile1.bin
    tile = 1
    date = None
    basename = os.path.basename(fname)
    try:
        import re

        tile_match = re.search(r"tile(\d+)", basename)
        if tile_match:
            tile = int(tile_match.group(1))

        date_match = re.search(r"(\d{8})", basename)
        if date_match:
            date = pd.to_datetime(date_match.group(1))
    except (ValueError, TypeError):
        pass

    ds = xr.Dataset(data_vars={"frp": (("x", "y"), data)}, coords={"tile": tile})

    if date:
        ds = ds.assign_coords(time=date).expand_dims("time")

    return ds