diff --git a/satpy/etc/readers/olci_l1b.yaml b/satpy/etc/readers/olci_l1b.yaml
index d36279a262..5be42d7a7e 100644
--- a/satpy/etc/readers/olci_l1b.yaml
+++ b/satpy/etc/readers/olci_l1b.yaml
@@ -1,7 +1,7 @@
reader:
name: olci_l1b
short_name: OLCI Level 1b
- long_name: Sentinel-3 A and B OLCI Level 1B data in netCDF4 format
+ long_name: Sentinel-3 OLCI Level 1B data in netCDF4 format
description: NC Reader for OLCI data
status: Nominal
supports_fsspec: true
diff --git a/satpy/etc/readers/olci_l2.yaml b/satpy/etc/readers/olci_l2.yaml
index 95cb024e72..f08475e254 100644
--- a/satpy/etc/readers/olci_l2.yaml
+++ b/satpy/etc/readers/olci_l2.yaml
@@ -1,7 +1,7 @@
reader:
name: olci_l2
short_name: OLCI Level 2
- long_name: Sentinel-3 A and B OLCI Level 2 data in netCDF4 format
+ long_name: Sentinel-3 OLCI Level 2 data in netCDF4 format
description: NC Reader for OLCI data
status: Nominal
supports_fsspec: true
diff --git a/satpy/etc/readers/slstr_l1b.yaml b/satpy/etc/readers/slstr_l1b.yaml
index 1ca76c5294..99fc19e444 100644
--- a/satpy/etc/readers/slstr_l1b.yaml
+++ b/satpy/etc/readers/slstr_l1b.yaml
@@ -1,7 +1,7 @@
reader:
name: slstr_l1b
short_name: SLSTR l1b
- long_name: Sentinel-3 A and B SLSTR data in netCDF4 format
+ long_name: Sentinel-3 SLSTR data in netCDF4 format
description: NC Reader for SLSTR data
status: Alpha
supports_fsspec: false
@@ -57,19 +57,22 @@ reader:
file_types:
esa_l1b_refl:
file_reader: !!python/name:satpy.readers.slstr_l1b.NCSLSTR1B
- file_patterns: ['{mission_id:3s}_SL_{processing_level:1s}_{datatype_id:_<6s}_{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}_{baseline:3d}.SEN3/{dataset_name}_radiance_{stripe:1s}{view:1s}.nc']
+ file_patterns: ['{mission_id:3s}_SL_1_{datatype_id:_<6s}_{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}_{baseline:3d}.SEN3/{dataset_name}_radiance_{stripe:1s}{view:1s}.nc']
esa_l1b_tir:
file_reader: !!python/name:satpy.readers.slstr_l1b.NCSLSTR1B
- file_patterns: ['{mission_id:3s}_SL_{processing_level:1s}_{datatype_id:_<6s}_{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}_{baseline:3d}.SEN3/{dataset_name}_BT_{stripe:1s}{view:1s}.nc']
+ file_patterns: ['{mission_id:3s}_SL_1_{datatype_id:_<6s}_{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}_{baseline:3d}.SEN3/{dataset_name}_BT_{stripe:1s}{view:1s}.nc']
esa_angles:
- file_reader: !!python/name:satpy.readers.slstr_l1b.NCSLSTRAngles
- file_patterns: ['{mission_id:3s}_SL_{processing_level:1s}_{datatype_id:_<6s}_{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}_{baseline:3d}.SEN3/geometry_t{view:1s}.nc']
+ file_reader: !!python/name:satpy.readers.core.slstr.NCSLSTRAngles
+ file_patterns: ['{mission_id:3s}_SL_1_{datatype_id:_<6s}_{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}_{baseline:3d}.SEN3/geometry_t{view:1s}.nc']
esa_geo:
- file_reader: !!python/name:satpy.readers.slstr_l1b.NCSLSTRGeo
- file_patterns: ['{mission_id:3s}_SL_{processing_level:1s}_{datatype_id:_<6s}_{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}_{baseline:3d}.SEN3/geodetic_{stripe:1s}{view:1s}.nc']
+ file_reader: !!python/name:satpy.readers.core.slstr.NCSLSTRGeo
+ file_patterns: ['{mission_id:3s}_SL_1_{datatype_id:_<6s}_{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}_{baseline:3d}.SEN3/geodetic_{stripe:1s}{view:1s}.nc']
esa_l1b_flag:
- file_reader: !!python/name:satpy.readers.slstr_l1b.NCSLSTRFlag
- file_patterns: ['{mission_id:3s}_SL_{processing_level:1s}_{datatype_id:_<6s}_{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}_{baseline:3d}.SEN3/flags_{stripe:1s}{view:1s}.nc']
+ file_reader: !!python/name:satpy.readers.core.slstr.NCSLSTRFlag
+ file_patterns: ['{mission_id:3s}_SL_1_{datatype_id:_<6s}_{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}_{baseline:3d}.SEN3/flags_{stripe:1s}{view:1s}.nc']
+ esa_l1b_cartesian:
+ file_reader: !!python/name:satpy.readers.core.slstr.NCSLSTRGeo
+ file_patterns: ['{mission_id:3s}_SL_1_{datatype_id:_<6s}_{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}_{baseline:3d}.SEN3/cartesian_{stripe:1s}{view:1s}.nc']
datasets:
longitude:
@@ -82,6 +85,16 @@ datasets:
standard_name: longitude
units: degree
+ longitude_orphan:
+ name: longitude_orphan
+ resolution: [500, 1000]
+ view: [nadir, oblique]
+ stripe: [a, b, i, f]
+ file_type: esa_geo
+ file_key: longitude_orphan_{stripe:1s}{view:1s}
+ standard_name: longitude
+ units: degree
+
latitude:
name: latitude
resolution: [500, 1000]
@@ -92,6 +105,16 @@ datasets:
standard_name: latitude
units: degree
+ latitude_orphan:
+ name: latitude_orphan
+ resolution: [500, 1000]
+ view: [nadir, oblique]
+ stripe: [a, b, i, f]
+ file_type: esa_geo
+ file_key: latitude_orphan_{stripe:1s}{view:1s}
+ standard_name: latitude_orphan
+ units: degree
+
elevation:
name: elevation
resolution: [500, 1000]
@@ -102,6 +125,56 @@ datasets:
standard_name: elevation
units: m
+ elevation_orphan:
+ name: elevation_orphan
+ resolution: [500, 1000]
+ view: [nadir, oblique]
+ stripe: [a, b, i, f]
+ file_type: esa_geo
+ file_key: elevation_{stripe:1s}{view:1s}
+ standard_name: elevation_orphan
+ units: m
+
+ x_coords:
+ name: x_coords
+ resolution: [500, 1000]
+ view: [nadir, oblique]
+ stripe: [a, b, i, f]
+ file_type: esa_geo
+ file_key: x_{stripe:1s}{view:1s}
+ standard_name: x_coords
+ units: m
+
+ y_coords:
+ name: y_coords
+ resolution: [500, 1000]
+ view: [nadir, oblique]
+ stripe: [a, b, i, f]
+ file_type: esa_geo
+ file_key: y_{stripe:1s}{view:1s}
+ standard_name: y_coords
+ units: m
+
+ x_orphan_coords:
+ name: x_orphan_coords
+ resolution: [500, 1000]
+ view: [nadir, oblique]
+ stripe: [a, b, i, f]
+ file_type: esa_geo
+ file_key: x_orphan_{stripe:1s}{view:1s}
+ standard_name: x_orphan_coords
+ units: m
+
+ y_orphan_coords:
+ name: y_orphan_coords
+ resolution: [500, 1000]
+ view: [nadir, oblique]
+ stripe: [a, b, i, f]
+ file_type: esa_geo
+ file_key: y_orphan_{stripe:1s}{view:1s}
+ standard_name: y_orphan_coords
+ units: m
+
# The channels S1-S3 are available in nadir (default) and oblique view.
S1:
name: S1
diff --git a/satpy/etc/readers/slstr_l2.yaml b/satpy/etc/readers/slstr_l2.yaml
new file mode 100644
index 0000000000..4252324750
--- /dev/null
+++ b/satpy/etc/readers/slstr_l2.yaml
@@ -0,0 +1,245 @@
+reader:
+ name: slstr_l2
+ short_name: SLSTR l2
+ long_name: Sentinel-3 SLSTR Level-2 data in netCDF4 format
+ description: NC Reader for SLSTR L2 data
+ status: Alpha
+ supports_fsspec: false
+ sensors: [slstr]
+ reader: !!python/name:satpy.readers.core.yaml_reader.FileYAMLReader
+ data_identification_keys:
+ name:
+ required: true
+ wavelength:
+ type: !!python/name:satpy.dataset.dataid.WavelengthRange
+ resolution:
+ transitive: false
+ calibration:
+ enum:
+ - reflectance
+ - brightness_temperature
+ - radiance
+ - counts
+ transitive: true
+ view:
+ enum:
+ - nadir
+ - oblique
+ transitive: true
+ stripe:
+ enum:
+ - a
+ - b
+ - i
+ - f
+ modifiers:
+ default: []
+ type: !!python/name:satpy.dataset.dataid.ModifierTuple
+
+ coord_identification_keys:
+ name:
+ required: true
+ resolution:
+ transitive: false
+ view:
+ enum:
+ - nadir
+ - oblique
+ transitive: true
+ stripe:
+ enum:
+ - a
+ - b
+ - i
+ - f
+
+file_types:
+ esa_lst:
+ file_reader: !!python/name:satpy.readers.slstr_l2_nc.NCSLSTRL2LST
+ file_patterns:
+ - '{mission_id:3s}_SL_2_LST____{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:4d}_{centre:3s}_{platform_mode:1s}_{timeliness:2s}_{collection:3s}.SEN3/LST_{stripe:1s}{view:1s}.nc'
+ esa_angles:
+ file_reader: !!python/name:satpy.readers.core.slstr.NCSLSTRAngles
+ file_patterns:
+ - '{mission_id:3s}_SL_2_{datatype_id:_<6s}_{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}_{baseline:3d}.SEN3/geometry_{stripe:1s}{view:1s}.nc'
+ esa_geo:
+ file_reader: !!python/name:satpy.readers.core.slstr.NCSLSTRGeo
+ file_patterns:
+ - '{mission_id:3s}_SL_2_{datatype_id:_<6s}_{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}_{baseline:3d}.SEN3/geodetic_{stripe:1s}{view:1s}.nc'
+ esa_cartesian:
+ file_reader: !!python/name:satpy.readers.core.slstr.NCSLSTRGeo
+ file_patterns:
+ - '{mission_id:3s}_SL_2_{datatype_id:_<6s}_{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}_{baseline:3d}.SEN3/cartesian{stripe:1s}{view:1s}.nc'
+
+datasets:
+ longitude:
+ name: longitude
+ resolution: 1000
+ view: nadir
+ stripe: i
+ file_type: esa_geo
+ file_key: longitude_{stripe:1s}{view:1s}
+ standard_name: longitude
+ units: degree
+
+ longitude_orphan:
+ name: longitude_orphan
+ resolution: 1000
+ view: nadir
+ stripe: i
+ file_type: esa_geo
+ file_key: longitude_orphan_{stripe:1s}{view:1s}
+ standard_name: longitude
+ units: degree
+
+ latitude:
+ name: latitude
+ resolution: 1000
+ view: nadir
+ stripe: i
+ file_type: esa_geo
+ file_key: latitude_{stripe:1s}{view:1s}
+ standard_name: latitude
+ units: degree
+
+ latitude_orphan:
+ name: latitude_orphan
+ resolution: 1000
+ view: nadir
+ stripe: i
+ file_type: esa_geo
+ file_key: latitude_orphan_{stripe:1s}{view:1s}
+ standard_name: latitude_orphan
+ units: degree
+
+ elevation:
+ name: elevation
+ resolution: 1000
+ view: nadir
+ stripe: i
+ file_type: esa_geo
+ file_key: elevation_{stripe:1s}{view:1s}
+ standard_name: elevation
+ units: m
+
+ elevation_orphan:
+ name: elevation_orphan
+ resolution: 1000
+ view: nadir
+ stripe: i
+ file_type: esa_geo
+ file_key: elevation_{stripe:1s}{view:1s}
+ standard_name: elevation_orphan
+ units: m
+
+ x_coords:
+ name: x_coords
+ resolution: 1000
+ view: nadir
+ stripe: i
+ file_type: esa_geo
+ file_key: x_{stripe:1s}{view:1s}
+ standard_name: x_coords
+ units: m
+
+ y_coords:
+ name: y_coords
+ resolution: 1000
+ view: nadir
+ stripe: i
+ file_type: esa_geo
+ file_key: y_{stripe:1s}{view:1s}
+ standard_name: y_coords
+ units: m
+
+ x_orphan_coords:
+ name: x_orphan_coords
+ resolution: 1000
+ view: nadir
+ stripe: i
+ file_type: esa_geo
+ file_key: x_orphan_{stripe:1s}{view:1s}
+ standard_name: x_orphan_coords
+ units: m
+
+ y_orphan_coords:
+ name: y_orphan_coords
+ resolution: 1000
+ view: nadir
+ stripe: i
+ file_type: esa_geo
+ file_key: y_orphan_{stripe:1s}{view:1s}
+ standard_name: y_orphan_coords
+ units: m
+
+ LST:
+ name: LST
+ sensor: slstr
+ stripe: i
+ view: nadir
+ resolution: 1000
+ standard_name: land_surface_temperature
+ units: K
+ coordinates: [longitude, latitude]
+ file_type: esa_lst
+ file_key: LST
+
+ LST_orphan:
+ name: LST_orphan
+ sensor: slstr
+ stripe: i
+ view: nadir
+ resolution: 1000
+ standard_name: land_surface_temperature
+ units: K
+ coordinates: [longitude_orphan, latitude_orphan]
+ file_type: esa_lst
+ file_key: LST_orphan
+
+ LST_exception:
+ name: LST_exception
+ sensor: slstr
+ stripe: i
+ view: nadir
+ resolution: 1000
+ standard_name: LST_quality_flag
+ units: 1
+ coordinates: [longitude, latitude]
+ file_type: esa_lst
+ file_key: exception
+
+ LST_exception_orphan:
+ name: LST_exception_orphan
+ sensor: slstr
+ stripe: i
+ view: nadir
+ resolution: 1000
+ standard_name: LST_quality_flag
+ units: 1
+ coordinates: [longitude_orphan, latitude_orphan]
+ file_type: esa_lst
+ file_key: exception_orphan
+
+ LST_uncertainty:
+ name: LST_uncertainty
+ sensor: slstr
+ stripe: i
+ view: nadir
+ resolution: 1000
+ standard_name: LST_uncertainty
+ units: K
+ coordinates: [longitude, latitude]
+ file_type: esa_lst
+ file_key: LST_uncertainty
+
+ LST_uncertainty_orphan:
+ name: LST_uncertainty_orphan
+ sensor: slstr
+ stripe: i
+ view: nadir
+ resolution: 1000
+ standard_name: LST_uncertainty
+ units: K
+ coordinates: [longitude_orphan, latitude_orphan]
+ file_type: esa_lst
+ file_key: LST_uncertainty_orphan
diff --git a/satpy/readers/core/config.py b/satpy/readers/core/config.py
index 34abc56d0a..3363c55b95 100644
--- a/satpy/readers/core/config.py
+++ b/satpy/readers/core/config.py
@@ -31,9 +31,7 @@
# Old Name -> New Name
PENDING_OLD_READER_NAMES = {"fci_l1c_fdhsi": "fci_l1c_nc", "viirs_l2_cloud_mask_nc": "viirs_edr"}
-OLD_READER_NAMES: dict[str, str] = {
- "slstr_l2": "ghrsst_l2",
-}
+OLD_READER_NAMES: dict[str, str] = {}
def read_reader_config(config_files, loader=UnsafeLoader):
diff --git a/satpy/readers/core/slstr.py b/satpy/readers/core/slstr.py
new file mode 100644
index 0000000000..c054670d71
--- /dev/null
+++ b/satpy/readers/core/slstr.py
@@ -0,0 +1,283 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (c) 2016-2020 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 .
+
+"""SLSTR common (L1+L2) reader functions."""
+
+import datetime as dt
+import logging
+import os
+
+import dask.array as da
+import numpy as np
+import xarray as xr
+
+from satpy.readers.core.file_handlers import BaseFileHandler
+from satpy.utils import get_legacy_chunk_size
+
+logger = logging.getLogger(__name__)
+
+CHUNK_SIZE = get_legacy_chunk_size()
+
+PLATFORM_NAMES = {"S3A": "Sentinel-3A",
+ "S3B": "Sentinel-3B",
+ "S3C": "Sentinel-3C",
+ "S3D": "Sentinel-3D"}
+
+# These are the default channel adjustment factors.
+# Defined in the product notice: S3.PN-SLSTR-L1.10
+# https://user.eumetsat.int/s3/eup-strapi-media/S3_PN_SLSTR_L1_10_i1r0_SLSTR_L1_PB_SL_L1_004_05_00_fbf01b8813.pdf
+CHANCALIB_FACTORS = {"S1_nadir": 0.97,
+ "S2_nadir": 0.98,
+ "S3_nadir": 0.98,
+ "S4_nadir": 1.0,
+ "S5_nadir": 1.11,
+ "S6_nadir": 1.13,
+ "S7_nadir": 1.0,
+ "S8_nadir": 1.0,
+ "S9_nadir": 1.0,
+ "S1_oblique": 0.94,
+ "S2_oblique": 0.95,
+ "S3_oblique": 0.95,
+ "S4_oblique": 1.0,
+ "S5_oblique": 1.04,
+ "S6_oblique": 1.07,
+ "S7_oblique": 1.0,
+ "S8_oblique": 1.0,
+ "S9_oblique": 1.0, }
+
+
+class NCSLSTRGeo(BaseFileHandler):
+ """Filehandler for geo info."""
+
+ def __init__(self, filename, filename_info, filetype_info):
+ """Initialize the geo filehandler."""
+ super(NCSLSTRGeo, self).__init__(filename, filename_info,
+ filetype_info)
+ self.nc = xr.open_dataset(self.filename,
+ decode_cf=True,
+ mask_and_scale=True,
+ chunks={"columns": CHUNK_SIZE,
+ "rows": CHUNK_SIZE})
+ self.nc = self.nc.rename({"columns": "x", "rows": "y"})
+
+ self.cache = {}
+
+ def get_dataset(self, key, info):
+ """Load a dataset."""
+ logger.debug("Reading %s.", key["name"])
+ file_key = info["file_key"].format(view=key["view"].name[0],
+ stripe=key["stripe"].name)
+ try:
+ variable = self.nc[file_key]
+ except KeyError:
+ return
+
+ info = info.copy()
+ info.update(variable.attrs)
+
+ variable.attrs = info
+ return variable
+
+ @property
+ def start_time(self):
+ """Get the start time."""
+ return dt.datetime.strptime(self.nc.attrs["start_time"], "%Y-%m-%dT%H:%M:%S.%fZ")
+
+ @property
+ def end_time(self):
+ """Get the end time."""
+ return dt.datetime.strptime(self.nc.attrs["stop_time"], "%Y-%m-%dT%H:%M:%S.%fZ")
+
+
+class NCSLSTRAngles(BaseFileHandler):
+ """Filehandler for angles."""
+
+ def _loadcart(self, fname):
+ """Load a cartesian file of appropriate type."""
+ cartf = xr.open_dataset(fname,
+ decode_cf=True,
+ mask_and_scale=True,
+ chunks={"columns": CHUNK_SIZE,
+ "rows": CHUNK_SIZE})
+ return cartf
+
+ def __init__(self, filename, filename_info, filetype_info):
+ """Initialize the angles reader."""
+ super(NCSLSTRAngles, self).__init__(filename, filename_info,
+ filetype_info)
+
+ self.nc = xr.open_dataset(self.filename,
+ decode_cf=True,
+ mask_and_scale=True,
+ chunks={"columns": CHUNK_SIZE,
+ "rows": CHUNK_SIZE})
+
+ # TODO: get metadata from the manifest file (xfdumanifest.xml)
+ self.platform_name = PLATFORM_NAMES[filename_info["mission_id"]]
+ self.sensor = "slstr"
+ self.view = filename_info["view"]
+ self._start_time = filename_info["start_time"]
+ self._end_time = filename_info["end_time"]
+
+ carta_file = os.path.join(
+ os.path.dirname(self.filename), "cartesian_a{}.nc".format(self.view[0]))
+ carti_file = os.path.join(
+ os.path.dirname(self.filename), "cartesian_i{}.nc".format(self.view[0]))
+ cartx_file = os.path.join(
+ os.path.dirname(self.filename), "cartesian_tx.nc")
+ if os.path.exists(carta_file):
+ self.carta = self._loadcart(carta_file)
+ if os.path.exists(carti_file):
+ self.carti = self._loadcart(carti_file)
+ if os.path.exists(cartx_file):
+ self.cartx = self._loadcart(cartx_file)
+
+ @staticmethod
+ def _interp_data(indata, full_grid, tie_grid, ds_name):
+ """Interpolate data from tie point grid to full image grid."""
+ from scipy.interpolate import RectBivariateSpline
+
+ # Check if we are interpolating angles
+ if "angle" in ds_name:
+ # If we are interpolating the angles, we need to do so with the sine and cosine to prevent
+ # interpolation artifacts such as values <0 or >360.
+ indat = indata[:, ::-1]
+ sin_angles = np.sin(np.radians(indat))
+ cos_angles = np.cos(np.radians(indat))
+ sin_interp = RectBivariateSpline(tie_grid[1], tie_grid[0], sin_angles)
+ cos_interp = RectBivariateSpline(tie_grid[1], tie_grid[0], cos_angles)
+ values_sin = sin_interp.ev(full_grid[1], full_grid[0])
+ values_cos = cos_interp.ev(full_grid[1], full_grid[0])
+ values = np.degrees(np.arctan2(values_sin, values_cos)) % 360
+ else:
+ # Otherwise, interpolate as normal.
+ spl = RectBivariateSpline(tie_grid[1], tie_grid[0], indata[:, ::-1])
+ values = spl.ev(full_grid[1], full_grid[0])
+ return values
+
+
+ def get_dataset(self, key, info):
+ """Load a dataset."""
+ if not key["view"].name.startswith(self.view[0]):
+ return
+ logger.debug("Reading %s.", key["name"])
+ # Check if file_key is specified in the yaml
+ file_key = info["file_key"].format(view=key["view"].name[0])
+
+ variable = self.nc[file_key]
+ l_step = self.nc.attrs.get("al_subsampling_factor", 1)
+ c_step = self.nc.attrs.get("ac_subsampling_factor", 16)
+
+ if key.get("resolution", 1000) == 500:
+ l_step *= 2
+ c_step *= 2
+
+ if c_step != 1 or l_step != 1:
+ logger.debug("Interpolating %s.", key["name"])
+ # TODO: do it in cartesian coordinates ! pbs at date line and
+ # possible
+ tie_x = self.cartx["x_tx"].data[0, :][::-1]
+ tie_y = self.cartx["y_tx"].data[:, 0]
+ if key.get("resolution", 1000) == 500:
+ full_x = self.carta["x_a" + self.view[0]].data
+ full_y = self.carta["y_a" + self.view[0]].data
+ else:
+ full_x = self.carti["x_i" + self.view[0]].data
+ full_y = self.carti["y_i" + self.view[0]].data
+
+ variable = variable.fillna(0)
+ variable.attrs["resolution"] = key.get("resolution", 1000)
+
+ # Interpolate the data to the full image grid
+ values = self._interp_data(variable.data,
+ [full_x, full_y],
+ [tie_x, tie_y],
+ key["name"])
+
+ variable = xr.DataArray(da.from_array(values, chunks=(CHUNK_SIZE, CHUNK_SIZE)),
+ dims=["y", "x"], attrs=variable.attrs)
+
+ variable.attrs["platform_name"] = self.platform_name
+ variable.attrs["sensor"] = self.sensor
+
+ if "units" not in variable.attrs:
+ variable.attrs["units"] = "degrees"
+
+ variable.attrs.update(key.to_dict())
+
+ return variable
+
+ @property
+ def start_time(self):
+ """Get the start time."""
+ return dt.datetime.strptime(self.nc.attrs["start_time"], "%Y-%m-%dT%H:%M:%S.%fZ")
+
+ @property
+ def end_time(self):
+ """Get the end time."""
+ return dt.datetime.strptime(self.nc.attrs["stop_time"], "%Y-%m-%dT%H:%M:%S.%fZ")
+
+
+class NCSLSTRFlag(BaseFileHandler):
+ """File handler for flags."""
+
+ def __init__(self, filename, filename_info, filetype_info):
+ """Initialize the flag reader."""
+ super(NCSLSTRFlag, self).__init__(filename, filename_info,
+ filetype_info)
+ self.nc = xr.open_dataset(self.filename,
+ decode_cf=True,
+ mask_and_scale=True,
+ chunks={"columns": CHUNK_SIZE,
+ "rows": CHUNK_SIZE})
+ self.nc = self.nc.rename({"columns": "x", "rows": "y"})
+ self.stripe = filename_info["stripe"]
+ views = {"n": "nadir", "o": "oblique"}
+ self.view = views[filename_info["view"]]
+ # TODO: get metadata from the manifest file (xfdumanifest.xml)
+ self.platform_name = PLATFORM_NAMES[filename_info["mission_id"]]
+ self.sensor = "slstr"
+
+ def get_dataset(self, key, info):
+ """Load a dataset."""
+ if (self.stripe != key["stripe"].name or
+ self.view != key["view"].name):
+ return
+ logger.debug("Reading %s.", key["name"])
+ file_key = info["file_key"].format(view=key["view"].name[0],
+ stripe=key["stripe"].name)
+ variable = self.nc[file_key]
+
+ info = info.copy()
+ info.update(variable.attrs)
+ info.update(key.to_dict())
+ info.update(dict(platform_name=self.platform_name,
+ sensor=self.sensor))
+
+ variable.attrs = info
+ return variable
+
+ @property
+ def start_time(self):
+ """Get the start time."""
+ return dt.datetime.strptime(self.nc.attrs["start_time"], "%Y-%m-%dT%H:%M:%S.%fZ")
+
+ @property
+ def end_time(self):
+ """Get the end time."""
+ return dt.datetime.strptime(self.nc.attrs["stop_time"], "%Y-%m-%dT%H:%M:%S.%fZ")
diff --git a/satpy/readers/slstr_l1b.py b/satpy/readers/slstr_l1b.py
index fb29732b32..bbab60816e 100644
--- a/satpy/readers/slstr_l1b.py
+++ b/satpy/readers/slstr_l1b.py
@@ -29,83 +29,13 @@
import xarray as xr
from satpy.readers.core.file_handlers import BaseFileHandler
+from satpy.readers.core.slstr import CHANCALIB_FACTORS, PLATFORM_NAMES
from satpy.utils import get_legacy_chunk_size
logger = logging.getLogger(__name__)
CHUNK_SIZE = get_legacy_chunk_size()
-PLATFORM_NAMES = {"S3A": "Sentinel-3A",
- "S3B": "Sentinel-3B",
- "S3C": "Sentinel-3C",
- "S3D": "Sentinel-3D"}
-
-# These are the default channel adjustment factors.
-# Defined in the product notice: S3.PN-SLSTR-L1.10
-# https://user.eumetsat.int/s3/eup-strapi-media/S3_PN_SLSTR_L1_10_i1r0_SLSTR_L1_PB_SL_L1_004_05_00_fbf01b8813.pdf
-CHANCALIB_FACTORS = {"S1_nadir": 0.97,
- "S2_nadir": 0.98,
- "S3_nadir": 0.98,
- "S4_nadir": 1.0,
- "S5_nadir": 1.11,
- "S6_nadir": 1.13,
- "S7_nadir": 1.0,
- "S8_nadir": 1.0,
- "S9_nadir": 1.0,
- "S1_oblique": 0.94,
- "S2_oblique": 0.95,
- "S3_oblique": 0.95,
- "S4_oblique": 1.0,
- "S5_oblique": 1.04,
- "S6_oblique": 1.07,
- "S7_oblique": 1.0,
- "S8_oblique": 1.0,
- "S9_oblique": 1.0, }
-
-
-class NCSLSTRGeo(BaseFileHandler):
- """Filehandler for geo info."""
-
- def __init__(self, filename, filename_info, filetype_info):
- """Initialize the geo filehandler."""
- super(NCSLSTRGeo, self).__init__(filename, filename_info,
- filetype_info)
- self.nc = xr.open_dataset(self.filename,
- decode_cf=True,
- mask_and_scale=True,
- chunks={"columns": CHUNK_SIZE,
- "rows": CHUNK_SIZE})
- self.nc = self.nc.rename({"columns": "x", "rows": "y"})
-
- self.cache = {}
-
- def get_dataset(self, key, info):
- """Load a dataset."""
- logger.debug("Reading %s.", key["name"])
- file_key = info["file_key"].format(view=key["view"].name[0],
- stripe=key["stripe"].name)
- try:
- variable = self.nc[file_key]
- except KeyError:
- return
-
- info = info.copy()
- info.update(variable.attrs)
-
- variable.attrs = info
- return variable
-
- @property
- def start_time(self):
- """Get the start time."""
- return dt.datetime.strptime(self.nc.attrs["start_time"], "%Y-%m-%dT%H:%M:%S.%fZ")
-
- @property
- def end_time(self):
- """Get the end time."""
- return dt.datetime.strptime(self.nc.attrs["stop_time"], "%Y-%m-%dT%H:%M:%S.%fZ")
-
-
class NCSLSTR1B(BaseFileHandler):
"""Filehandler for l1 SLSTR data.
@@ -237,179 +167,3 @@ def start_time(self):
def end_time(self):
"""Get the end time."""
return dt.datetime.strptime(self.nc.attrs["stop_time"], "%Y-%m-%dT%H:%M:%S.%fZ")
-
-
-class NCSLSTRAngles(BaseFileHandler):
- """Filehandler for angles."""
-
- def _loadcart(self, fname):
- """Load a cartesian file of appropriate type."""
- cartf = xr.open_dataset(fname,
- decode_cf=True,
- mask_and_scale=True,
- chunks={"columns": CHUNK_SIZE,
- "rows": CHUNK_SIZE})
- return cartf
-
- def __init__(self, filename, filename_info, filetype_info):
- """Initialize the angles reader."""
- super(NCSLSTRAngles, self).__init__(filename, filename_info,
- filetype_info)
-
- self.nc = xr.open_dataset(self.filename,
- decode_cf=True,
- mask_and_scale=True,
- chunks={"columns": CHUNK_SIZE,
- "rows": CHUNK_SIZE})
-
- # TODO: get metadata from the manifest file (xfdumanifest.xml)
- self.platform_name = PLATFORM_NAMES[filename_info["mission_id"]]
- self.sensor = "slstr"
- self.view = filename_info["view"]
- self._start_time = filename_info["start_time"]
- self._end_time = filename_info["end_time"]
-
- carta_file = os.path.join(
- os.path.dirname(self.filename), "cartesian_a{}.nc".format(self.view[0]))
- carti_file = os.path.join(
- os.path.dirname(self.filename), "cartesian_i{}.nc".format(self.view[0]))
- cartx_file = os.path.join(
- os.path.dirname(self.filename), "cartesian_tx.nc")
- self.carta = self._loadcart(carta_file)
- self.carti = self._loadcart(carti_file)
- self.cartx = self._loadcart(cartx_file)
-
- @staticmethod
- def _interp_data(indata, full_grid, tie_grid, ds_name):
- """Interpolate data from tie point grid to full image grid."""
- from scipy.interpolate import RectBivariateSpline
-
- # Check if we are interpolating angles
- if "angle" in ds_name:
- # If we are interpolating the angles, we need to do so with the sine and cosine to prevent
- # interpolation artifacts such as values <0 or >360.
- indat = indata[:, ::-1]
- sin_angles = np.sin(np.radians(indat))
- cos_angles = np.cos(np.radians(indat))
- sin_interp = RectBivariateSpline(tie_grid[1], tie_grid[0], sin_angles)
- cos_interp = RectBivariateSpline(tie_grid[1], tie_grid[0], cos_angles)
- values_sin = sin_interp.ev(full_grid[1], full_grid[0])
- values_cos = cos_interp.ev(full_grid[1], full_grid[0])
- values = np.degrees(np.arctan2(values_sin, values_cos)) % 360
- else:
- # Otherwise, interpolate as normal.
- spl = RectBivariateSpline(tie_grid[1], tie_grid[0], indata[:, ::-1])
- values = spl.ev(full_grid[1], full_grid[0])
- return values
-
-
- def get_dataset(self, key, info):
- """Load a dataset."""
- if not key["view"].name.startswith(self.view[0]):
- return
- logger.debug("Reading %s.", key["name"])
- # Check if file_key is specified in the yaml
- file_key = info["file_key"].format(view=key["view"].name[0])
-
- variable = self.nc[file_key]
- l_step = self.nc.attrs.get("al_subsampling_factor", 1)
- c_step = self.nc.attrs.get("ac_subsampling_factor", 16)
-
- if key.get("resolution", 1000) == 500:
- l_step *= 2
- c_step *= 2
-
- if c_step != 1 or l_step != 1:
- logger.debug("Interpolating %s.", key["name"])
- # TODO: do it in cartesian coordinates ! pbs at date line and
- # possible
- tie_x = self.cartx["x_tx"].data[0, :][::-1]
- tie_y = self.cartx["y_tx"].data[:, 0]
- if key.get("resolution", 1000) == 500:
- full_x = self.carta["x_a" + self.view[0]].data
- full_y = self.carta["y_a" + self.view[0]].data
- else:
- full_x = self.carti["x_i" + self.view[0]].data
- full_y = self.carti["y_i" + self.view[0]].data
-
- variable = variable.fillna(0)
- variable.attrs["resolution"] = key.get("resolution", 1000)
-
- # Interpolate the data to the full image grid
- values = self._interp_data(variable.data,
- [full_x, full_y],
- [tie_x, tie_y],
- key["name"])
-
- variable = xr.DataArray(da.from_array(values, chunks=(CHUNK_SIZE, CHUNK_SIZE)),
- dims=["y", "x"], attrs=variable.attrs)
-
- variable.attrs["platform_name"] = self.platform_name
- variable.attrs["sensor"] = self.sensor
-
- if "units" not in variable.attrs:
- variable.attrs["units"] = "degrees"
-
- variable.attrs.update(key.to_dict())
-
- return variable
-
- @property
- def start_time(self):
- """Get the start time."""
- return dt.datetime.strptime(self.nc.attrs["start_time"], "%Y-%m-%dT%H:%M:%S.%fZ")
-
- @property
- def end_time(self):
- """Get the end time."""
- return dt.datetime.strptime(self.nc.attrs["stop_time"], "%Y-%m-%dT%H:%M:%S.%fZ")
-
-
-class NCSLSTRFlag(BaseFileHandler):
- """File handler for flags."""
-
- def __init__(self, filename, filename_info, filetype_info):
- """Initialize the flag reader."""
- super(NCSLSTRFlag, self).__init__(filename, filename_info,
- filetype_info)
- self.nc = xr.open_dataset(self.filename,
- decode_cf=True,
- mask_and_scale=True,
- chunks={"columns": CHUNK_SIZE,
- "rows": CHUNK_SIZE})
- self.nc = self.nc.rename({"columns": "x", "rows": "y"})
- self.stripe = filename_info["stripe"]
- views = {"n": "nadir", "o": "oblique"}
- self.view = views[filename_info["view"]]
- # TODO: get metadata from the manifest file (xfdumanifest.xml)
- self.platform_name = PLATFORM_NAMES[filename_info["mission_id"]]
- self.sensor = "slstr"
-
- def get_dataset(self, key, info):
- """Load a dataset."""
- if (self.stripe != key["stripe"].name or
- self.view != key["view"].name):
- return
- logger.debug("Reading %s.", key["name"])
- file_key = info["file_key"].format(view=key["view"].name[0],
- stripe=key["stripe"].name)
- variable = self.nc[file_key]
-
- info = info.copy()
- info.update(variable.attrs)
- info.update(key.to_dict())
- info.update(dict(platform_name=self.platform_name,
- sensor=self.sensor))
-
- variable.attrs = info
- return variable
-
- @property
- def start_time(self):
- """Get the start time."""
- return dt.datetime.strptime(self.nc.attrs["start_time"], "%Y-%m-%dT%H:%M:%S.%fZ")
-
- @property
- def end_time(self):
- """Get the end time."""
- return dt.datetime.strptime(self.nc.attrs["stop_time"], "%Y-%m-%dT%H:%M:%S.%fZ")
diff --git a/satpy/readers/slstr_l2_nc.py b/satpy/readers/slstr_l2_nc.py
new file mode 100644
index 0000000000..961c5e23fe
--- /dev/null
+++ b/satpy/readers/slstr_l2_nc.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (c) 2016-2020 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 .
+
+"""SLSTR L2 reader."""
+
+import datetime as dt
+import logging
+
+import xarray as xr
+
+from satpy.readers.core.file_handlers import BaseFileHandler
+from satpy.readers.core.slstr import PLATFORM_NAMES
+from satpy.utils import get_legacy_chunk_size
+
+logger = logging.getLogger(__name__)
+
+CHUNK_SIZE = get_legacy_chunk_size()
+
+class NCSLSTRL2LST(BaseFileHandler):
+ """Filehandler for L2 SLSTR Land Surface Temperature (LST) data."""
+
+ def __init__(self, filename, filename_info, filetype_info,
+ user_calibration=None):
+ """Initialize the SLSTR l2 data filehandler."""
+ super(NCSLSTRL2LST, self).__init__(filename, filename_info,
+ filetype_info)
+
+ self.nc = xr.open_dataset(self.filename,
+ decode_cf=True,
+ mask_and_scale=True,
+ chunks={"columns": CHUNK_SIZE,
+ "rows": CHUNK_SIZE})
+ self.nc = self.nc.rename({"columns": "x", "rows": "y"})
+ self.stripe = filename_info["stripe"]
+ views = {"n": "nadir", "o": "oblique"}
+ self.view = views[filename_info["view"]]
+
+ self.platform_name = PLATFORM_NAMES[filename_info["mission_id"]]
+ self.sensor = "slstr"
+
+ def get_dataset(self, key, info):
+ """Load a dataset."""
+ if (self.stripe != key["stripe"].name or self.view != key["view"].name):
+ return
+ file_key = info["file_key"]
+ logger.debug("Reading %s.", key["name"])
+ data = self.nc[f"{file_key}"]
+
+ units = data.attrs["units"]
+
+ info = info.copy()
+ info.update(data.attrs)
+ info.update(key.to_dict())
+ info.update(dict(units=units,
+ platform_name=self.platform_name,
+ sensor=self.sensor,
+ view=self.view))
+
+ data.attrs = info
+ return data
+
+ @property
+ def start_time(self):
+ """Get the start time."""
+ return dt.datetime.strptime(self.nc.attrs["start_time"], "%Y-%m-%dT%H:%M:%S.%fZ")
+
+ @property
+ def end_time(self):
+ """Get the end time."""
+ return dt.datetime.strptime(self.nc.attrs["stop_time"], "%Y-%m-%dT%H:%M:%S.%fZ")
diff --git a/satpy/tests/reader_tests/test_slstr_common.py b/satpy/tests/reader_tests/test_slstr_common.py
new file mode 100644
index 0000000000..25d365bd87
--- /dev/null
+++ b/satpy/tests/reader_tests/test_slstr_common.py
@@ -0,0 +1,317 @@
+#!/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 .
+
+"""Module for testing the satpy.readers.nc_slstr module."""
+
+import datetime as dt
+import unittest
+import unittest.mock as mock
+
+import numpy as np
+import xarray as xr
+
+from satpy.dataset.dataid import DataID, ModifierTuple, WavelengthRange
+from satpy.readers.core.slstr import NCSLSTRAngles, NCSLSTRFlag, NCSLSTRGeo
+
+local_id_keys_config = {
+ "name": {
+ "required": True,
+ },
+ "wavelength": {
+ "type": WavelengthRange,
+ },
+ "resolution": None,
+ "calibration": {
+ "enum": [
+ "reflectance",
+ "brightness_temperature",
+ "radiance",
+ "counts",
+ ],
+ },
+ "stripe": {
+ "enum": [
+ "a",
+ "b",
+ "c",
+ "i",
+ "f",
+ ],
+ },
+ "view": {
+ "enum": [
+ "nadir",
+ "oblique",
+ ],
+ },
+ "modifiers": {
+ "required": True,
+ "default": ModifierTuple(),
+ "type": ModifierTuple,
+ },
+}
+
+
+class TestSLSTRL1B(unittest.TestCase):
+ """Common setup for SLSTR_L1B tests."""
+
+ def _make_fake_dataset(self):
+ """Create a fake dataset for use in testing."""
+ det = xr.DataArray(
+ self.base_data,
+ dims=("columns", "rows"),
+ attrs={
+ "scale_factor": 1.0,
+ "add_offset": 0.0,
+ "_FillValue": 255,
+ },
+ )
+
+ x_in = xr.DataArray(self.ix_data, dims=("columns", "rows"))
+ y_in = xr.DataArray(self.iy_data, dims=("columns", "rows"))
+
+ saa = xr.DataArray(self.ang_data, dims=("columns_t", "rows_t"))
+ x_tx = xr.DataArray(self.tx_data, dims=("columns_t", "rows_t"))
+ y_tx = xr.DataArray(self.ty_data, dims=("columns_t", "rows_t"))
+
+ self.fake_dataset = xr.Dataset(
+ data_vars={
+ "S5_radiance_an": self.rad,
+ "S9_BT_ao": self.rad,
+ "foo_radiance_an": self.rad,
+ "S5_solar_irradiances": self.rad,
+ "geometry_tn": self.rad,
+ "latitude_an": self.rad,
+ "x_in": x_in,
+ "y_in": y_in,
+ "x_an": x_in,
+ "y_an": y_in,
+ "flags_an": self.rad,
+ "detector_an": det,
+ "x_tx": x_tx,
+ "y_tx": y_tx,
+ "solar_azimuth_tn": saa,
+ },
+ attrs={
+ "start_time": self.start_time,
+ "stop_time": self.end_time,
+ },
+ )
+
+ def setUp(self):
+ """Create a fake dataset using the given radiance data."""
+ self.base_data = np.array((
+ [1., 2., 3., 4., 5., 6., 7., 8., 9.],
+ [7., 8., 9., 10., 11., 12., 13., 14., 15.],
+ [16., 17., 18., 19., 20., 21., 22., 23., 24.],
+ [25., 26., 27., 28., 29., 30., 31., 32., 33.],
+ ))
+
+ self.ang_data = np.array((
+ [345., 355., 1., 5., 10.],
+ [346., 355., 0.5, 4.2, 15.],
+ [342., 356., 1., 5.3, 12.],
+ [344.3, 356.1, 0.0001, 4.9, 9.2],
+ ))
+
+ self.tx_data = np.array((
+ [101000, 100500, 100000, 99500, 99000],
+ [101000, 100500, 100000, 99500, 99000],
+ [101000, 100500, 100000, 99500, 99000],
+ [101000, 100500, 100000, 99500, 99000],
+ ))
+
+ self.ty_data = np.array((
+ [199500, 199500, 199500, 199500, 199500],
+ [200000, 200000, 200000, 200000, 200000],
+ [200500, 200500, 200500, 200500, 200500],
+ [201000, 201000, 201000, 201000, 201000],
+ ))
+
+ self.ix_data = np.array((
+ [100800, 100600, 100400, 100200, 100000, 99800, 99600, 99400, 99200],
+ [100800, 100600, 100400, 100200, 100000, 99800, 99600, 99400, 99200],
+ [100800, 100600, 100400, 100200, 100000, 99800, 99600, 99400, 99200],
+ [100800, 100600, 100400, 100200, 100000, 99800, 99600, 99400, 99200],
+ ))
+
+ self.iy_data = np.array((
+ [199800, 199800, 199800, 199800, 199800, 199800, 199800, 199800, 199800],
+ [200000, 200000, 200000, 200000, 200000, 200000, 200000, 200000, 200000],
+ [200600, 200600, 200600, 200600, 200600, 200600, 200600, 200600, 200600],
+ [200800, 200800, 200800, 200800, 200800, 200800, 200800, 200800, 200800],
+ ))
+
+ self.det_data = np.array((
+ [0, 1, 1, 0, 1, 0, 1, 0, 1],
+ [1, 0, 0, 0, 0, 1, 0, 1, 1],
+ [1, 1, 1, 1, 1, 1, 1, 0, 1],
+ [0, 1, 1, 0, 1, 1, 1, 1, 0],
+ ))
+
+ self.start_time = "2020-05-10T12:01:15.585Z"
+ self.end_time = "2020-05-10T12:06:18.012Z"
+
+ self.rad = xr.DataArray(
+ self.base_data,
+ dims=("columns", "rows"),
+ attrs={
+ "scale_factor": 1.0,
+ "add_offset": 0.0,
+ "_FillValue": -32768,
+ "units": "mW.m-2.sr-1.nm-1",
+ },
+ )
+
+ self._make_fake_dataset()
+
+
+
+
+def make_dataid(**items):
+ """Make a data id."""
+ return DataID(local_id_keys_config, **items)
+
+
+class TestSLSTRReader(TestSLSTRL1B):
+ """Test various nc_slstr file handlers."""
+
+ class FakeSpl:
+ """Fake return function for SPL interpolation."""
+
+ @staticmethod
+ def ev(foo_x, foo_y):
+ """Fake function to return interpolated data."""
+ return np.zeros((3, 2))
+
+ @mock.patch("satpy.readers.core.slstr.os.path.exists")
+ @mock.patch("satpy.readers.core.slstr.xr.open_dataset")
+ @mock.patch("scipy.interpolate.RectBivariateSpline")
+ def test_instantiate(self, bvs_, open_dataset_, exists_):
+ """Test initialization of file handlers."""
+ bvs_.return_value = self.FakeSpl
+ open_dataset_.return_value = self.fake_dataset
+ exists_.side_effect = lambda path: "cartesian_" in path
+
+ good_start = dt.datetime.strptime(self.start_time, "%Y-%m-%dT%H:%M:%S.%fZ")
+ good_end = dt.datetime.strptime(self.end_time, "%Y-%m-%dT%H:%M:%S.%fZ")
+
+ ds_id = make_dataid(
+ name="foo",
+ calibration="radiance",
+ stripe="a",
+ view="nadir",
+ )
+ ds_id_500 = make_dataid(
+ name="foo",
+ calibration="radiance",
+ stripe="a",
+ view="nadir",
+ resolution=500,
+ )
+
+ filename_info = {
+ "mission_id": "S3A",
+ "dataset_name": "foo",
+ "start_time": 0,
+ "end_time": 0,
+ "stripe": "a",
+ "view": "n",
+ "baseline": 4,
+ }
+
+ test = NCSLSTRGeo("somedir/geometry_an.nc", filename_info, "c")
+ test.get_dataset(ds_id, dict(filename_info, **{"file_key": "latitude_{stripe:1s}{view:1s}"}))
+ assert test.start_time == good_start
+ assert test.end_time == good_end
+ open_dataset_.assert_called()
+ open_dataset_.reset_mock()
+
+ test = NCSLSTRFlag("somedir/S1_radiance_an.nc", filename_info, "c")
+ test.get_dataset(ds_id, dict(filename_info, **{"file_key": "flags_{stripe:1s}{view:1s}"}),)
+ assert test.view == "nadir"
+ assert test.stripe == "a"
+ assert test.start_time == good_start
+ assert test.end_time == good_end
+ open_dataset_.assert_called()
+ open_dataset_.reset_mock()
+
+ test = NCSLSTRAngles("somedir/S1_radiance_an.nc", filename_info, "c")
+
+ assert hasattr(test, "cartx")
+ assert hasattr(test, "carti")
+ assert hasattr(test, "carta")
+
+ test.get_dataset(ds_id, dict(filename_info, **{"file_key": "geometry_t{view:1s}"}))
+ assert test.start_time == good_start
+ assert test.end_time == good_end
+ open_dataset_.assert_called()
+ open_dataset_.reset_mock()
+
+ test.get_dataset(ds_id_500, dict(filename_info, **{"file_key": "geometry_t{view:1s}"}))
+
+
+class TestSLSTRAngles(TestSLSTRL1B):
+ """Test the implementation of the angle reconstruction."""
+
+ @mock.patch("satpy.readers.core.slstr.os.path.exists")
+ @mock.patch("satpy.readers.core.slstr.xr.open_dataset")
+ def test_angles_loading(self, open_dataset_, exists_):
+ """Test ability to load interpolated SLSTR angle data."""
+ open_dataset_.return_value = self.fake_dataset
+ exists_.side_effect = lambda path: "cartesian_" in path
+
+ expected = np.array([
+ [350.17659522, 353.31335956, 356.1441152, 358.55896366,
+ 0.4400064, 1.79903533, 3.18424353, 5.28496166, 8.80226547],
+ [350.02717525, 353.48225908, 356.37905054, 358.72059252,
+ 0.5, 1.81840943, 3.23819125, 5.44552178, 9.13958444],
+ [349.30986294, 354.38594648, 357.53203205, 359.49732861,
+ 1.00001215, 2.61842911, 4.45389453, 6.49348929, 8.72979111],
+ [349.68116993, 354.62824808, 357.55405421, 359.31582013,
+ 0.74004914, 2.46431559, 4.44154365, 6.45655991, 8.30055946],
+ ])
+
+ ds_id = make_dataid(name="solar_azimuth_angle", view="nadir")
+ filename_info = {
+ "mission_id": "S3A",
+ "dataset_name": "solar_azimuth_angle",
+ "start_time": 0,
+ "end_time": 0,
+ "stripe": "t",
+ "view": "n",
+ "baseline": 4,
+ }
+
+ test = NCSLSTRAngles("somedir/geometry_tn.nc", filename_info, "c")
+
+ assert hasattr(test, "cartx")
+ assert hasattr(test, "carti")
+ assert hasattr(test, "carta")
+
+ data = test.get_dataset(
+ ds_id,
+ dict(filename_info, **{"file_key": "solar_azimuth_tn"}),
+ )
+
+ assert isinstance(data, xr.DataArray)
+ assert data.attrs["units"] == "degrees"
+ assert data.attrs["platform_name"] == "Sentinel-3A"
+ assert data.attrs["sensor"] == "slstr"
+ assert data.shape == expected.shape
+ np.testing.assert_allclose(data.values, expected)
diff --git a/satpy/tests/reader_tests/test_slstr_l1b.py b/satpy/tests/reader_tests/test_slstr_l1b.py
index a15cf999c8..ee43812c78 100644
--- a/satpy/tests/reader_tests/test_slstr_l1b.py
+++ b/satpy/tests/reader_tests/test_slstr_l1b.py
@@ -27,7 +27,7 @@
import xarray as xr
from satpy.dataset.dataid import DataID, ModifierTuple, WavelengthRange
-from satpy.readers.slstr_l1b import NCSLSTR1B, NCSLSTRAngles, NCSLSTRFlag, NCSLSTRGeo
+from satpy.readers.slstr_l1b import NCSLSTR1B
local_id_keys_config = {"name": {
"required": True,
@@ -71,7 +71,8 @@ class TestSLSTRL1B(unittest.TestCase):
"""Common setup for SLSTR_L1B tests."""
@mock.patch("satpy.readers.slstr_l1b.xr")
- def setUp(self, xr_):
+ @mock.patch("satpy.readers.core.slstr.xr")
+ def setUp(self, xr_, xr2_):
"""Create a fake dataset using the given radiance data."""
self.base_data = np.array(([1., 2., 3., 4., 5., 6., 7., 8., 9.],
[7., 8., 9., 10., 11., 12., 13., 14., 15.],
@@ -119,7 +120,7 @@ def setUp(self, xr_):
x_in = xr.DataArray(self.ix_data,dims=("columns", "rows"))
y_in = xr.DataArray(self.iy_data,dims=("columns", "rows"))
- saa = xr.DataArray(self.ang_data,dims=("columns_t", "rows_t"))
+ xr.DataArray(self.ang_data,dims=("columns_t", "rows_t"))
x_tx = xr.DataArray(self.tx_data,dims=("columns_t", "rows_t"))
y_tx = xr.DataArray(self.ty_data,dims=("columns_t", "rows_t"))
@@ -129,17 +130,13 @@ def setUp(self, xr_):
"S9_BT_ao": self.rad,
"foo_radiance_an": self.rad,
"S5_solar_irradiances": self.rad,
- "geometry_tn": self.rad,
- "latitude_an": self.rad,
"x_in": x_in,
"y_in": y_in,
"x_an": x_in,
"y_an": y_in,
- "flags_an": self.rad,
"detector_an": det,
"x_tx": x_tx,
"y_tx": y_tx,
- "solar_azimuth_tn": saa,
},
attrs={
"start_time": self.start_time,
@@ -178,8 +175,6 @@ def test_instantiate(self, bvs_, xr_):
ds_id = make_dataid(name="foo", calibration="radiance",
stripe="a", view="nadir")
- ds_id_500 = make_dataid(name="foo", calibration="radiance",
- stripe="a", view="nadir", resolution=500)
filename_info = {"mission_id": "S3A", "dataset_name": "foo",
"start_time": 0, "end_time": 0,
"stripe": "a", "view": "n", "baseline":4}
@@ -205,41 +200,13 @@ def test_instantiate(self, bvs_, xr_):
xr_.open_dataset.assert_called()
xr_.open_dataset.reset_mock()
- filename_info = {"mission_id": "S3A", "dataset_name": "foo",
- "start_time": 0, "end_time": 0,
- "stripe": "a", "view": "n", "baseline": 4}
- test = NCSLSTRGeo("somedir/geometry_an.nc", filename_info, "c")
- test.get_dataset(ds_id, dict(filename_info, **{"file_key": "latitude_{stripe:1s}{view:1s}"}))
- assert test.start_time == good_start
- assert test.end_time == good_end
- xr_.open_dataset.assert_called()
- xr_.open_dataset.reset_mock()
-
- test = NCSLSTRFlag("somedir/S1_radiance_an.nc", filename_info, "c")
- test.get_dataset(ds_id, dict(filename_info, **{"file_key": "flags_{stripe:1s}{view:1s}"}))
- assert test.view == "nadir"
- assert test.stripe == "a"
- assert test.start_time == good_start
- assert test.end_time == good_end
- xr_.open_dataset.assert_called()
- xr_.open_dataset.reset_mock()
-
- test = NCSLSTRAngles("somedir/S1_radiance_an.nc", filename_info, "c")
- test.get_dataset(ds_id, dict(filename_info, **{"file_key": "geometry_t{view:1s}"}))
- assert test.start_time == good_start
- assert test.end_time == good_end
- xr_.open_dataset.assert_called()
- xr_.open_dataset.reset_mock()
- test.get_dataset(ds_id_500, dict(filename_info, **{"file_key": "geometry_t{view:1s}"}))
-
-
class TestSLSTRCalibration(TestSLSTRL1B):
"""Test the implementation of the calibration factors."""
@mock.patch("satpy.readers.slstr_l1b.xr")
def test_radiance_calibration(self, xr_):
"""Test radiance calibration steps."""
- from satpy.readers.slstr_l1b import CHANCALIB_FACTORS
+ from satpy.readers.core.slstr import CHANCALIB_FACTORS
xr_.open_dataset.return_value = self.fake_dataset
ds_id = make_dataid(name="foo", calibration="radiance",
@@ -315,32 +282,3 @@ def test_cal_rad(self):
out_rad = NCSLSTR1B._cal_rad(rad, didx, solflux)
np.testing.assert_allclose(out_rad, good_rad)
-
-
-class TestSLSTRAngles(TestSLSTRL1B):
- """Test the implementation of the angle reconstruction."""
-
- @mock.patch("satpy.readers.slstr_l1b.xr.open_dataset")
- def test_radiance_calibration(self, xr_):
- """Test radiance calibration steps."""
- xr_.return_value = self.fake_dataset
-
- res = np.array([[350.17659522, 353.31335956, 356.1441152, 358.55896366,
- 0.4400064, 1.79903533, 3.18424353, 5.28496166, 8.80226547],
- [350.02717525, 353.48225908, 356.37905054, 358.72059252,
- 0.5, 1.81840943, 3.23819125, 5.44552178, 9.13958444],
- [349.30986294, 354.38594648, 357.53203205, 359.49732861,
- 1.00001215, 2.61842911, 4.45389453, 6.49348929, 8.72979111],
- [349.68116993, 354.62824808, 357.55405421, 359.31582013,
- 0.74004914, 2.46431559, 4.44154365, 6.45655991, 8.30055946]])
-
- ds_id = make_dataid(name="solar_azimuth_angle", view="nadir")
- filename_info = {"mission_id": "S3A", "dataset_name": "solar_azimuth_angle",
- "start_time": 0, "end_time": 0,
- "stripe": "t", "view": "n", "baseline": 4}
-
- test = NCSLSTRAngles("somedir/geometry_tn.nc", filename_info, "c")
- data = test.get_dataset(ds_id, dict(filename_info, **{"file_key": "solar_azimuth_tn"}))
- assert data.attrs["units"] == "degrees"
- assert data.shape == res.shape
- np.testing.assert_allclose(data.values, res)