Skip to content

nesdis_viirs_jrr

NESDIS VIIRS JRR Reader

VIIRSJRRReader

Bases: GriddedReader

Reader for NESDIS VIIRS JRR (Joint Polar Satellite System Risk Reduction) products. Supports AOD, ADP, CloudMask, and others. Available on AWS Open Data.

Source code in monetio/readers/nesdis_viirs_jrr.py
 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
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
@register_reader("nesdis_viirs_jrr")
@register_reader("viirs_jrr")
class VIIRSJRRReader(GriddedReader):
    """
    Reader for NESDIS VIIRS JRR (Joint Polar Satellite System Risk Reduction) products.
    Supports AOD, ADP, CloudMask, and others.
    Available on AWS Open Data.
    """

    def open_dataset(
        self,
        files: str | list[str] = None,
        dates: pd.DatetimeIndex | list[datetime.datetime] | datetime.datetime | str = None,
        satellite: str = "snpp",
        product: str = "AOD",
        qa_threshold: float | None = None,
        **kwargs,
    ) -> xr.Dataset:
        """
        Reads NESDIS VIIRS JRR data.

        Parameters
        ----------
        files : Union[str, List[str]], optional
            File path(s) or URL(s).
        dates : Union[pd.DatetimeIndex, List[datetime], datetime, str], optional
            Dates to retrieve. If files is None, this is used to build URLs.
        satellite : str, optional
            Satellite identifier: 'snpp', 'n20' (NOAA-20/J01), or 'n21' (NOAA-21/J02).
            Default is 'snpp'.
        product : str, optional
            JRR product: 'AOD' (default), 'ADP', 'CloudMask', 'CloudHeight', etc.
        qa_threshold : float, optional
            Quality threshold for masking, by default None.
        **kwargs : dict
            Additional arguments passed to XarrayDriver.open.

        Returns
        -------
        xr.Dataset
            The VIIRS JRR dataset.
        """
        if files is None:
            if dates is None:
                raise ValueError("Either 'files' or 'dates' must be provided.")
            files = self.build_urls(dates, satellite=satellite, product=product)

        if "preprocess" not in kwargs:
            from functools import partial

            kwargs["preprocess"] = partial(
                viirs_jrr_preprocess, product=product, qa_threshold=qa_threshold
            )

        if "engine" not in kwargs:
            kwargs["engine"] = "h5netcdf"

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

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

        # Update history
        ds = update_history(ds, f"Read NESDIS VIIRS JRR {product} data from {satellite}.")

        return ds

    def build_urls(
        self,
        dates: pd.DatetimeIndex | list[datetime.datetime] | datetime.datetime | str,
        satellite: str = "snpp",
        product: str = "AOD",
    ) -> list[str]:
        """
        Build S3 URLs for NESDIS VIIRS JRR data based on dates.

        Parameters
        ----------
        dates : Union[pd.DatetimeIndex, List[datetime], datetime, str]
            Dates to retrieve.
        satellite : str, optional
            Satellite identifier ('snpp', 'n20', 'n21', 'j01', 'j02').
        product : str, optional
            JRR product.

        Returns
        -------
        List[str]
            List of S3 URLs.
        """
        import s3fs

        if isinstance(dates, str | datetime.datetime | pd.Timestamp):
            dates = pd.DatetimeIndex([pd.to_datetime(dates)])
        else:
            dates = pd.to_datetime(dates)

        sat_map = {
            "snpp": "noaa-nesdis-snpp-pds",
            "j01": "noaa-nesdis-n20-pds",
            "n20": "noaa-nesdis-n20-pds",
            "j02": "noaa-nesdis-n21-pds",
            "n21": "noaa-nesdis-n21-pds",
        }
        bucket = sat_map.get(satellite.lower())
        if not bucket:
            raise ValueError(f"Unknown satellite: {satellite}. Choose from {list(sat_map.keys())}")

        fs = s3fs.S3FileSystem(anon=True)
        urls = []
        for d in dates.floor("D").unique():
            prefix = f"{bucket}/VIIRS-JRR-{product}/{d.strftime('%Y/%m/%d')}/"
            # We use glob to find all granules for the day
            try:
                found = fs.glob(f"{prefix}*.nc")
                urls.extend([f"s3://{f}" for f in found])
            except Exception:
                continue

        return sorted(urls)

build_urls(dates, satellite='snpp', product='AOD')

Build S3 URLs for NESDIS VIIRS JRR data based on dates.

Parameters:

Name Type Description Default
dates Union[DatetimeIndex, List[datetime], datetime, str]

Dates to retrieve.

required
satellite str

Satellite identifier ('snpp', 'n20', 'n21', 'j01', 'j02').

'snpp'
product str

JRR product.

'AOD'

Returns:

Type Description
List[str]

List of S3 URLs.

Source code in monetio/readers/nesdis_viirs_jrr.py
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
def build_urls(
    self,
    dates: pd.DatetimeIndex | list[datetime.datetime] | datetime.datetime | str,
    satellite: str = "snpp",
    product: str = "AOD",
) -> list[str]:
    """
    Build S3 URLs for NESDIS VIIRS JRR data based on dates.

    Parameters
    ----------
    dates : Union[pd.DatetimeIndex, List[datetime], datetime, str]
        Dates to retrieve.
    satellite : str, optional
        Satellite identifier ('snpp', 'n20', 'n21', 'j01', 'j02').
    product : str, optional
        JRR product.

    Returns
    -------
    List[str]
        List of S3 URLs.
    """
    import s3fs

    if isinstance(dates, str | datetime.datetime | pd.Timestamp):
        dates = pd.DatetimeIndex([pd.to_datetime(dates)])
    else:
        dates = pd.to_datetime(dates)

    sat_map = {
        "snpp": "noaa-nesdis-snpp-pds",
        "j01": "noaa-nesdis-n20-pds",
        "n20": "noaa-nesdis-n20-pds",
        "j02": "noaa-nesdis-n21-pds",
        "n21": "noaa-nesdis-n21-pds",
    }
    bucket = sat_map.get(satellite.lower())
    if not bucket:
        raise ValueError(f"Unknown satellite: {satellite}. Choose from {list(sat_map.keys())}")

    fs = s3fs.S3FileSystem(anon=True)
    urls = []
    for d in dates.floor("D").unique():
        prefix = f"{bucket}/VIIRS-JRR-{product}/{d.strftime('%Y/%m/%d')}/"
        # We use glob to find all granules for the day
        try:
            found = fs.glob(f"{prefix}*.nc")
            urls.extend([f"s3://{f}" for f in found])
        except Exception:
            continue

    return sorted(urls)

open_dataset(files=None, dates=None, satellite='snpp', product='AOD', qa_threshold=None, **kwargs)

Reads NESDIS VIIRS JRR data.

Parameters:

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

File path(s) or URL(s).

None
dates Union[DatetimeIndex, List[datetime], datetime, str]

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

None
satellite str

Satellite identifier: 'snpp', 'n20' (NOAA-20/J01), or 'n21' (NOAA-21/J02). Default is 'snpp'.

'snpp'
product str

JRR product: 'AOD' (default), 'ADP', 'CloudMask', 'CloudHeight', etc.

'AOD'
qa_threshold float

Quality threshold for masking, by default None.

None
**kwargs dict

Additional arguments passed to XarrayDriver.open.

{}

Returns:

Type Description
Dataset

The VIIRS JRR dataset.

Source code in monetio/readers/nesdis_viirs_jrr.py
 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
def open_dataset(
    self,
    files: str | list[str] = None,
    dates: pd.DatetimeIndex | list[datetime.datetime] | datetime.datetime | str = None,
    satellite: str = "snpp",
    product: str = "AOD",
    qa_threshold: float | None = None,
    **kwargs,
) -> xr.Dataset:
    """
    Reads NESDIS VIIRS JRR data.

    Parameters
    ----------
    files : Union[str, List[str]], optional
        File path(s) or URL(s).
    dates : Union[pd.DatetimeIndex, List[datetime], datetime, str], optional
        Dates to retrieve. If files is None, this is used to build URLs.
    satellite : str, optional
        Satellite identifier: 'snpp', 'n20' (NOAA-20/J01), or 'n21' (NOAA-21/J02).
        Default is 'snpp'.
    product : str, optional
        JRR product: 'AOD' (default), 'ADP', 'CloudMask', 'CloudHeight', etc.
    qa_threshold : float, optional
        Quality threshold for masking, by default None.
    **kwargs : dict
        Additional arguments passed to XarrayDriver.open.

    Returns
    -------
    xr.Dataset
        The VIIRS JRR dataset.
    """
    if files is None:
        if dates is None:
            raise ValueError("Either 'files' or 'dates' must be provided.")
        files = self.build_urls(dates, satellite=satellite, product=product)

    if "preprocess" not in kwargs:
        from functools import partial

        kwargs["preprocess"] = partial(
            viirs_jrr_preprocess, product=product, qa_threshold=qa_threshold
        )

    if "engine" not in kwargs:
        kwargs["engine"] = "h5netcdf"

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

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

    # Update history
    ds = update_history(ds, f"Read NESDIS VIIRS JRR {product} data from {satellite}.")

    return ds

viirs_jrr_preprocess(ds, product='AOD', qa_threshold=None)

Preprocess VIIRS JRR dataset.

Parameters:

Name Type Description Default
ds Dataset

Input dataset.

required
product str

Product type, by default "AOD".

'AOD'
qa_threshold float

Quality threshold for masking, by default None.

None

Returns:

Type Description
Dataset

Processed dataset.

Source code in monetio/readers/nesdis_viirs_jrr.py
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
def viirs_jrr_preprocess(
    ds: xr.Dataset, product: str = "AOD", qa_threshold: float | None = None
) -> xr.Dataset:
    """
    Preprocess VIIRS JRR dataset.

    Parameters
    ----------
    ds : xr.Dataset
        Input dataset.
    product : str, optional
        Product type, by default "AOD".
    qa_threshold : float, optional
        Quality threshold for masking, by default None.

    Returns
    -------
    xr.Dataset
        Processed dataset.
    """
    ds = standardize_satellite_coords(ds)
    ds = add_time_coord(ds, time_attr="time_coverage_start")

    spec = JRR_SPECS.get(product.upper(), {})

    # 1. Product-specific renaming
    rename_dict = {k: v for k, v in spec.get("rename", {}).items() if k in ds.data_vars}
    if rename_dict:
        ds = ds.rename(rename_dict)

    # 2. Attribute assignment
    for var, attrs in spec.get("metadata", {}).items():
        if var in ds.data_vars:
            ds[var].attrs.update(attrs)

    # 3. Quality Flagging (Lazy & Vectorized)
    qa_var = spec.get("qa_var")
    if qa_threshold is not None and qa_var in ds.variables:
        # Mask data variables where quality is below threshold
        # We assume higher is better or use exact match for bitmasks if needed.
        # For JRR, we use >= threshold logic similar to TROPOMI/MODIS.
        mask = ds[qa_var] >= qa_threshold
        # Keep qa_var and coords, mask data_vars
        qa = ds[qa_var].copy()
        ds = ds.where(mask)
        ds[qa_var] = qa

    # Update history
    ds = update_history(ds, f"Preprocessed NESDIS VIIRS JRR {product} data.")

    return ds