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

Feature Sentinel-3 Level-2 SST #1020

Merged
merged 17 commits into from Jan 27, 2020
Merged
Show file tree
Hide file tree
Changes from 16 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
3 changes: 3 additions & 0 deletions doc/source/index.rst
Expand Up @@ -224,6 +224,9 @@ the base Satpy installation.
* - GOES-R GLM Grided Level 2 in NetCDF4 format
- `glm_l2`
- Beta
* - Sentinel-3 SLSTR SST data in NetCDF4 format
- `slstr_l2`
- Beta


Indices and tables
Expand Down
59 changes: 59 additions & 0 deletions satpy/etc/readers/slstr_l2.yaml
@@ -0,0 +1,59 @@
reader:
description: NC Reader for Sentinel-3 SLSTR Level 2 data
name: slstr_l2
sensors: [slstr_l2]
default_channels: []
reader: !!python/name:satpy.readers.yaml_reader.FileYAMLReader

file_types:
SLSTRB:
file_reader: !!python/name:satpy.readers.slstr_l2.SLSTRL2FileHandler
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:
longitude:
name: longitude
resolution: 1000
view: nadir
file_type: SLSTRB
standard_name: lon
units: degree

latitude:
name: latitude
resolution: 1000
view: nadir
file_type: SLSTRB
standard_name: lat
units: degree

sea_surface_temperature:
name: sea_surface_temperature
sensor: slstr_l2
coordinates: [longitude, latitude]
file_type: SLSTRB
resolution: 1000
view: nadir
units: kelvin
standard_name: sea_surface_temperature

sea_ice_fraction:
name: sea_ice_fraction
sensor: slstr_l2
coordinates: [longitude, latitude]
file_type: SLSTRB
resolution: 1000
view: nadir
units: "%"
standard_name: sea_ice_fraction

# Quality estimation 0-5: no data, cloud, worst, low, acceptable, best
quality_level:
name: quality_level
sensor: slstr_l2
coordinates: [longitude, latitude]
file_type: SLSTRB
resolution: 1000
view: nadir
standard_name: sea_ice_fraction
75 changes: 75 additions & 0 deletions satpy/readers/slstr_l2.py
@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017 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/>.
"""Reader for Sentinel-3 SLSTR SST data."""

from datetime import datetime
from satpy.readers.file_handlers import BaseFileHandler
from satpy import CHUNK_SIZE
import xarray as xr


class SLSTRL2FileHandler(BaseFileHandler):
"""File handler for Sentinel-3 SSL 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)

if filename.endswith('tar'):
import tarfile
import os
import tempfile
with tempfile.TemporaryDirectory() as tempdir:
mraspaud marked this conversation as resolved.
Show resolved Hide resolved
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})
else:
self.nc = xr.open_dataset(filename,
decode_cf=True,
mask_and_scale=True,
engine=engine,
chunks={'ni': CHUNK_SIZE,
'nj': CHUNK_SIZE})

self.nc = self.nc.rename({'ni': 'x', 'nj': 'y'})
self.filename_info['start_time'] = datetime.strptime(
self.nc.start_time, '%Y%m%dT%H%M%SZ')
self.filename_info['end_time'] = datetime.strptime(
self.nc.stop_time, '%Y%m%dT%H%M%SZ')

def get_dataset(self, key, info):
"""Get any available dataset."""
stdname = info.get('standard_name')
return self.nc[stdname].squeeze()

@property
def start_time(self):
"""Get start time."""
return self.filename_info['start_time']

@property
def end_time(self):
"""Get end time."""
return self.filename_info['end_time']
3 changes: 2 additions & 1 deletion satpy/tests/reader_tests/__init__.py
Expand Up @@ -41,7 +41,7 @@
test_hsaf_grib, test_abi_l2_nc, test_eum_base,
test_ami_l1b, test_viirs_compact, test_seviri_l2_bufr,
test_geos_area, test_nwcsaf_msg, test_glm_l2,
test_seviri_l1b_icare, test_mimic_TPW2_nc,)
test_seviri_l1b_icare, test_mimic_TPW2_nc, test_slstr_l2,)

if sys.version_info < (2, 7):
import unittest2 as unittest
Expand Down Expand Up @@ -106,5 +106,6 @@ def suite():
mysuite.addTests(test_mimic_TPW2_nc.suite())
mysuite.addTests(test_glm_l2.suite())
mysuite.addTests(test_seviri_l1b_icare.suite())
mysuite.addTests(test_slstr_l2.suite())

return mysuite
73 changes: 73 additions & 0 deletions satpy/tests/reader_tests/test_slstr_l2.py
@@ -0,0 +1,73 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2018 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.slstr_l2 module."""

import unittest
from unittest import mock
from unittest.mock import MagicMock
import xarray as xr
from satpy.readers.slstr_l2 import SLSTRL2FileHandler


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

@mock.patch('xarray.open_dataset')
def test_instantiate(self, mocked_dataset):
"""Test initialization of file handlers."""
filename_info = {}
tmp = MagicMock(start_time='20191120T125002Z', stop_time='20191120T125002Z')
tmp.rename.return_value = tmp
xr.open_dataset.return_value = tmp
SLSTRL2FileHandler('somedir/somefile.nc', filename_info, None)
mocked_dataset.assert_called()
mocked_dataset.reset_mock()

@mock.patch('xarray.open_dataset')
def test_get_dataset(self, mocked_dataset):
"""Test retrieval of datasets."""
filename_info = {}
tmp = MagicMock(start_time='20191120T125002Z', stop_time='20191120T125002Z')
tmp.rename.return_value = tmp
xr.open_dataset.return_value = tmp
test = SLSTRL2FileHandler('somedir/somefile.nc', filename_info, None)
test.nc = {'longitude': xr.Dataset(),
'latitude': xr.Dataset(),
'sea_surface_temperature': xr.Dataset(),
'sea_ice_fraction': xr.Dataset(),
}
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'})
test.get_dataset('sea_ice_fraction', {'standard_name': 'sea_ice_fraction'})
with self.assertRaises(KeyError):
test.get_dataset('erroneous dataset', {'standard_name': 'erroneous dataset'})
mocked_dataset.assert_called()
mocked_dataset.reset_mock()


def suite():
"""Test suite for test_slstr_l2."""
loader = unittest.TestLoader()
mysuite = unittest.TestSuite()
mysuite.addTest(loader.loadTestsFromTestCase(TestSLSTRL2Reader))
return mysuite


if __name__ == '__main__':
unittest.main()