Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix osisaf SST reader #1410

Merged
merged 21 commits into from Apr 14, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions doc/source/index.rst
Expand Up @@ -181,7 +181,7 @@ the base Satpy installation.
- `slstr_l1b`
- In development
* - OSISAF SST data in GHRSST (netcdf) format
- `ghrsst_l3c_sst`
- `ghrsst_l2`
- In development
* - NUCAPS EDR Retrieval in NetCDF4 format
- `nucaps`
Expand Down Expand Up @@ -245,7 +245,7 @@ the base Satpy installation.
- `glm_l2`
- Beta
* - Sentinel-3 SLSTR SST data in NetCDF4 format
- `slstr_l2`
- `ghrsst_l2`
- Beta
* - IASI level 2 SO2 in BUFR format
- `iasi_l2_so2_bufr`
Expand Down
4 changes: 2 additions & 2 deletions satpy/composites/__init__.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2015-2020 Satpy developers
# Copyright (c) 2015-2020, 2022 Satpy developers
#
# This file is part of satpy.
#
Expand Down Expand Up @@ -495,7 +495,7 @@ def build_colormap(palette, dtype, info):
Colormaps come in different forms, but they are all supposed to have
color values between 0 and 255. The following cases are considered:

- Palettes comprised of only a list on colors. If *dtype* is uint8,
- Palettes comprised of only a list of colors. If *dtype* is uint8,
the values of the colormap are the enumeration of the colors.
Otherwise, the colormap values will be spread evenly from the min
to the max of the valid_range provided in `info`.
Expand Down
97 changes: 97 additions & 0 deletions satpy/etc/readers/ghrsst_l2.yaml
@@ -0,0 +1,97 @@
reader:
description: NC Reader for GHRSST Level 2 data
name: ghrsst_l2
adybbroe marked this conversation as resolved.
Show resolved Hide resolved
sensors: ['slstr', 'avhrr/3', 'viirs']
default_channels: []
reader: !!python/name:satpy.readers.yaml_reader.FileYAMLReader

file_types:
GHRSST_OSISAF:
file_reader: !!python/name:satpy.readers.ghrsst_l2.GHRSSTL2FileHandler
# S-OSI_-FRA_-NPP_-NARSST_FIELD-202010141300Z.nc
file_patterns: ['S-OSI_-{generating_centre:4s}-{satid:s}-{field_type:s}_FIELD-{valid_time:%Y%m%d%H%M}Z.nc']

SLSTR:
file_reader: !!python/name:satpy.readers.ghrsst_l2.GHRSSTL2FileHandler
file_patterns: ['{dt1:%Y%m%d%H%M%S}-{generating_centre:3s}-{type_id:3s}_GHRSST-SSTskin-SLSTR{something:1s}-{dt2:%Y%m%d%H%M%S}-{version}.nc',
'{mission_id:3s}_SL_{processing_level:1s}_WST____{start_time:%Y%m%dT%H%M%S}_{end_time:%Y%m%dT%H%M%S}_{creation_time:%Y%m%dT%H%M%S}_{duration:4d}_{cycle:3d}_{relative_orbit:3d}_{frame:4s}_{centre:3s}_{mode:1s}_{timeliness:2s}_{collection:3s}.SEN3.tar']

datasets:
# SLSTR SST and Sea Ice products
longitude_slstr:
name: longitude_slstr
resolution: 1000
view: nadir
file_type: SLSTR
standard_name: lon
units: degree

latitude_slstr:
name: latitude_slstr
resolution: 1000
view: nadir
file_type: SLSTR
standard_name: lat
units: degree

sea_surface_temperature_slstr:
name: sea_surface_temperature
sensor: slstr
coordinates: [longitude_slstr, latitude_slstr]
file_type: SLSTR
resolution: 1000
view: nadir
units: kelvin
standard_name: sea_surface_temperature

sea_ice_fraction_slstr:
name: sea_ice_fraction
sensor: slstr
coordinates: [longitude_slstr, latitude_slstr]
file_type: SLSTR
resolution: 1000
view: nadir
units: "%"
standard_name: sea_ice_fraction

# Quality estimation 0-5: no data, cloud, worst, low, acceptable, best
quality_level_slstr:
name: quality_level
sensor: slstr
coordinates: [longitude_slstr, latitude_slstr]
file_type: SLSTR
resolution: 1000
view: nadir
standard_name: quality_level


# OSISAF SST:
longitude_osisaf:
name: longitude_osisaf
resolution: 2000
file_type: GHRSST_OSISAF
standard_name: lon
units: degree

latitude_osisaf:
name: latitude_osisaf
resolution: 2000
file_type: GHRSST_OSISAF
standard_name: lat
units: degree

sea_surface_temperature_osisaf:
name: sea_surface_temperature
coordinates: [longitude_osisaf, latitude_osisaf]
file_type: GHRSST_OSISAF
resolution: 2000
units: kelvin
standard_name: sea_surface_temperature

sea_ice_fraction_osisaf:
name: sea_ice_fraction
coordinates: [longitude_osisaf, latitude_osisaf]
file_type: GHRSST_OSISAF
resolution: 2000
units: "%"
standard_name: sea_ice_fraction
17 changes: 0 additions & 17 deletions satpy/etc/readers/ghrsst_l3c_sst.yaml

This file was deleted.

61 changes: 38 additions & 23 deletions satpy/readers/slstr_l2.py → satpy/readers/ghrsst_l2.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Satpy developers
# Copyright (c) 2017 - 2022 Satpy developers
#
# This file is part of satpy.
# 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
Expand All @@ -14,8 +14,11 @@
#
# You should have received a copy of the GNU General Public License along with
# satpy. If not, see <http://www.gnu.org/licenses/>.
"""Reader for Sentinel-3 SLSTR SST data."""
"""Reader for the GHRSST level-2 formatted data."""

import os
import tarfile
from contextlib import suppress
from datetime import datetime

import xarray as xr
Expand All @@ -24,29 +27,24 @@
from satpy.readers.file_handlers import BaseFileHandler


class SLSTRL2FileHandler(BaseFileHandler):
"""File handler for Sentinel-3 SSL L2 netCDF files."""
class GHRSSTL2FileHandler(BaseFileHandler):
"""File handler for GHRSST L2 netCDF files."""

def __init__(self, filename, filename_info, filetype_info, engine=None):
"""Initialize the file handler for Sentinel-3 SSL L2 netCDF data."""
super(SLSTRL2FileHandler, self).__init__(filename, filename_info, filetype_info)
"""Initialize the file handler for GHRSST L2 netCDF data."""
super().__init__(filename, filename_info, filetype_info)

if filename.endswith('tar'):
import os
import tarfile
import tempfile
with tempfile.TemporaryDirectory() as tempdir:
with tarfile.open(name=filename, mode='r') as tf:
sst_filename = next((name for name in tf.getnames()
if name.endswith('nc') and 'GHRSST-SSTskin' in name))
tf.extract(sst_filename, tempdir)
fullpath = os.path.join(tempdir, sst_filename)
self.nc = xr.open_dataset(fullpath,
decode_cf=True,
mask_and_scale=True,
engine=engine,
chunks={'ni': CHUNK_SIZE,
'nj': CHUNK_SIZE})
if os.fspath(filename).endswith('tar'):
self._tarfile = tarfile.open(name=filename, mode='r')
sst_filename = next((name for name in self._tarfile.getnames()
if self._is_sst_file(name)))
file_obj = self._tarfile.extractfile(sst_filename)
self.nc = xr.open_dataset(file_obj,
decode_cf=True,
mask_and_scale=True,
engine=engine,
chunks={'ni': CHUNK_SIZE,
'nj': CHUNK_SIZE})
else:
self.nc = xr.open_dataset(filename,
decode_cf=True,
Expand All @@ -61,6 +59,13 @@ def __init__(self, filename, filename_info, filetype_info, engine=None):
self.filename_info['end_time'] = datetime.strptime(
self.nc.stop_time, '%Y%m%dT%H%M%SZ')

@staticmethod
def _is_sst_file(name):
"""Check if file in the tar archive is a valid SST file."""
if name.endswith('nc') and 'GHRSST-SSTskin' in name:
return True
return False
adybbroe marked this conversation as resolved.
Show resolved Hide resolved

def get_dataset(self, key, info):
"""Get any available dataset."""
stdname = info.get('standard_name')
Expand All @@ -75,3 +80,13 @@ def start_time(self):
def end_time(self):
"""Get end time."""
return self.filename_info['end_time']

@property
def sensor(self):
"""Get the sensor name."""
return self.nc.attrs['sensor'].lower()

def __del__(self):
"""Close the tarfile object."""
with suppress(AttributeError):
self._tarfile.close()
146 changes: 146 additions & 0 deletions satpy/tests/reader_tests/test_ghrsst_l2.py
@@ -0,0 +1,146 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2018, 2022 Satpy developers
#
# 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/>.
"""Module for testing the satpy.readers.ghrsst_l2 module."""

import os
import tarfile
import tempfile
import unittest
from datetime import datetime
from pathlib import Path
from unittest import mock

import numpy as np
import xarray as xr

from satpy.readers.ghrsst_l2 import GHRSSTL2FileHandler


class TestGHRSSTL2Reader(unittest.TestCase):
"""Test Sentinel-3 SST L2 reader."""

@mock.patch('satpy.readers.ghrsst_l2.xr')
def setUp(self, xr_):
"""Create a fake osisaf ghrsst dataset."""
self.base_data = np.array(([-32768, 1135, 1125], [1138, 1128, 1080]))
self.lon_data = np.array(([-13.43, 1.56, 11.25], [-11.38, 1.28, 10.80]))
self.lat_data = np.array(([43.43, 55.56, 61.25], [41.38, 50.28, 60.80]))
self.lon = xr.DataArray(
self.lon_data,
dims=('nj', 'ni'),
attrs={'standard_name': 'longitude',
'units': 'degrees_east',
}
)
self.lat = xr.DataArray(
self.lat_data,
dims=('nj', 'ni'),
attrs={'standard_name': 'latitude',
'units': 'degrees_north',
}
)
self.sst = xr.DataArray(
self.base_data,
dims=('nj', 'ni'),
attrs={'scale_factor': 0.01, 'add_offset': 273.15,
'_FillValue': -32768, 'units': 'kelvin',
}
)
self.fake_dataset = xr.Dataset(
data_vars={
'sea_surface_temperature': self.sst,
'longitude': self.lon,
'latitude': self.lat,
},
attrs={
"start_time": "20220321T112640Z",
"stop_time": "20220321T145711Z",
"platform": 'NOAA20',
"sensor": "VIIRS",
},
)
self._tmpfile = tempfile.NamedTemporaryFile(suffix='.nc')
self.fake_dataset.to_netcdf(self._tmpfile.name)

self._create_tarfile_with_testdata()

def _create_tarfile_with_testdata(self):
"""Create a 'fake' testdata set in a tar file."""
self._tmpdir = tempfile.TemporaryDirectory()

slstr_fakename = "S3A_SL_2_WST_MAR_O_NR_003.SEN3"
tarfile_fakename = "S3A_SL_2_WST_MAR_O_NR_003.SEN3.tar"
self._slstrdir = Path(self._tmpdir.name) / slstr_fakename
self._slstrdir.mkdir(parents=True, exist_ok=True)
self._tarfile_path = Path(self._tmpdir.name) / tarfile_fakename

with tempfile.NamedTemporaryFile(suffix='.nc',
prefix='L2P_GHRSST-SSTskin', dir=self._slstrdir) as ncfilename:
self.fake_dataset.to_netcdf(ncfilename.name)
xmlfile_path = tempfile.NamedTemporaryFile(prefix='xfdumanifest', suffix='.xml',
dir=Path(self._tmpdir.name) / slstr_fakename)

with tarfile.open(name=self._tarfile_path, mode='w') as tar:
tar.add(ncfilename.name, arcname=Path(slstr_fakename) / os.path.basename(ncfilename.name))
tar.add(xmlfile_path.name, arcname=Path(slstr_fakename) / os.path.basename(xmlfile_path.name))

def test_instantiate(self):
"""Test initialization of file handlers."""
filename_info = {}
GHRSSTL2FileHandler(self._tmpfile.name, filename_info, None)
GHRSSTL2FileHandler(os.fspath(self._tarfile_path), filename_info, None)

def test_get_dataset(self):
"""Test retrieval of datasets."""
filename_info = {}
test = GHRSSTL2FileHandler(self._tmpfile.name, filename_info, None)
test.get_dataset('longitude', {'standard_name': 'longitude'})
test.get_dataset('latitude', {'standard_name': 'latitude'})
test.get_dataset('sea_surface_temperature', {'standard_name': 'sea_surface_temperature'})

with self.assertRaises(KeyError):
test.get_dataset('erroneous dataset', {'standard_name': 'erroneous dataset'})

def test_get_sensor(self):
"""Test retrieval of the sensor name from the netCDF file."""
dt_valid = datetime(2022, 3, 21, 11, 26, 40) # 202203211200Z
filename_info = {'field_type': 'NARSST', 'generating_centre': 'FRA_',
'satid': 'NOAA20_', 'valid_time': dt_valid}

test = GHRSSTL2FileHandler(self._tmpfile.name, filename_info, None)
assert test.sensor == 'viirs'

def test_get_start_and_end_times(self):
"""Test retrieval of the sensor name from the netCDF file."""
dt_valid = datetime(2022, 3, 21, 11, 26, 40) # 202203211200Z
good_start_time = datetime(2022, 3, 21, 11, 26, 40) # 20220321T112640Z
good_stop_time = datetime(2022, 3, 21, 14, 57, 11) # 20220321T145711Z

filename_info = {'field_type': 'NARSST', 'generating_centre': 'FRA_',
'satid': 'NOAA20_', 'valid_time': dt_valid}

test = GHRSSTL2FileHandler(self._tmpfile.name, filename_info, None)

assert test.start_time == good_start_time
assert test.end_time == good_stop_time

def tearDown(self):
"""Clean up."""
print(self._tmpfile.name)
adybbroe marked this conversation as resolved.
Show resolved Hide resolved
self._tmpfile.close()
self._tmpdir.cleanup()