diff --git a/satpy/composites/__init__.py b/satpy/composites/__init__.py index c7bcce36ca..d4ef34a070 100644 --- a/satpy/composites/__init__.py +++ b/satpy/composites/__init__.py @@ -369,6 +369,22 @@ def __call__(self, projectables, nonprojectables=None, **info): return super(Filler, self).__call__([filled_projectable], **info) +class MultiFiller(GenericCompositor): + """Fix holes in projectable 1 with data from the next projectables.""" + + def __call__(self, projectables, nonprojectables=None, **info): + """Generate the composite.""" + projectables = self.match_data_arrays(projectables) + filled_projectable = projectables[0] + for next_projectable in projectables[1:]: + filled_projectable = filled_projectable.fillna(next_projectable) + if 'optional_datasets' in info.keys(): + for next_projectable in info['optional_datasets']: + filled_projectable = filled_projectable.fillna(next_projectable) + + return super(MultiFiller, self).__call__([filled_projectable], **info) + + class RGBCompositor(GenericCompositor): """Make a composite from three color bands (deprecated).""" @@ -1283,3 +1299,38 @@ def _get_flag_value(mask, val): index = flag_meanings.index(val) return flag_values[index] + + +class LongitudeMaskingCompositor(GenericCompositor): + """Masks areas outside defined longitudes.""" + + def __init__(self, name, lon_min=None, lon_max=None, **kwargs): + """Collect custom configuration values. + + Args: + lon_min (float): lower longitude limit + lon_max (float): upper longitude limit + """ + self.lon_min = lon_min + self.lon_max = lon_max + if self.lon_min is None and self.lon_max is None: + raise ValueError("Masking conditions not defined. \ + At least lon_min or lon_max has to be specified.") + if not self.lon_min: + self.lon_min = -180. + if not self.lon_max: + self.lon_max = 180. + super(LongitudeMaskingCompositor, self).__init__(name, **kwargs) + + def __call__(self, projectables, nonprojectables=None, **info): + """Generate the composite.""" + projectable = projectables[0] + lons, lats = projectable.attrs["area"].get_lonlats() + + if self.lon_max > self.lon_min: + lon_min_max = np.logical_and(lons >= self.lon_min, lons <= self.lon_max) + else: + lon_min_max = np.logical_or(lons >= self.lon_min, lons <= self.lon_max) + + masked_projectable = projectable.where(lon_min_max) + return super(LongitudeMaskingCompositor, self).__call__([masked_projectable], **info) diff --git a/satpy/tests/test_composites.py b/satpy/tests/test_composites.py index 046c0e417b..c11c9a2a83 100644 --- a/satpy/tests/test_composites.py +++ b/satpy/tests/test_composites.py @@ -358,6 +358,23 @@ def test_fill(self): np.testing.assert_allclose(res.sel(bands='B').data, blue.data) +class TestMultiFiller(unittest.TestCase): + """Test case for the MultiFiller compositor.""" + + def test_fill(self): + """Test filling.""" + from satpy.composites import MultiFiller + comp = MultiFiller(name='fill_test') + a = xr.DataArray(np.array([1, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan])) + b = xr.DataArray(np.array([np.nan, 2, 3, np.nan, np.nan, np.nan, np.nan])) + c = xr.DataArray(np.array([np.nan, 22, 3, np.nan, np.nan, np.nan, 7])) + d = xr.DataArray(np.array([np.nan, np.nan, np.nan, np.nan, np.nan, 6, np.nan])) + e = xr.DataArray(np.array([np.nan, np.nan, np.nan, np.nan, 5, np.nan, np.nan])) + expected = xr.DataArray(np.array([1, 2, 3, np.nan, 5, 6, 7])) + res = comp([a, b, c], optional_datasets=[d, e]) + np.testing.assert_allclose(res.data, expected.data) + + class TestLuminanceSharpeningCompositor(unittest.TestCase): """Test luminance sharpening compositor.""" @@ -1405,3 +1422,36 @@ def test_no_bands_is_l(self): from satpy.composites import GenericCompositor arr = xr.DataArray(np.ones((5, 5)), dims=('x', 'y')) assert GenericCompositor.infer_mode(arr) == 'L' + + +class TestLongitudeMaskingCompositor(unittest.TestCase): + """Test case for the LongitudeMaskingCompositor compositor.""" + + def test_masking(self): + """Test longitude masking.""" + from satpy.composites import LongitudeMaskingCompositor + + area = mock.MagicMock() + lons = np.array([-180., -100., -50., 0., 50., 100., 180.]) + area.get_lonlats = mock.MagicMock(return_value=[lons, []]) + a = xr.DataArray(np.array([1, 2, 3, 4, 5, 6, 7]), attrs={'area': area}) + + comp = LongitudeMaskingCompositor(name='test', lon_min=-40., lon_max=120.) + expected = xr.DataArray(np.array([np.nan, np.nan, np.nan, 4, 5, 6, np.nan])) + res = comp([a]) + np.testing.assert_allclose(res.data, expected.data) + + comp = LongitudeMaskingCompositor(name='test', lon_min=-40.) + expected = xr.DataArray(np.array([np.nan, np.nan, np.nan, 4, 5, 6, 7])) + res = comp([a]) + np.testing.assert_allclose(res.data, expected.data) + + comp = LongitudeMaskingCompositor(name='test', lon_max=120.) + expected = xr.DataArray(np.array([1, 2, 3, 4, 5, 6, np.nan])) + res = comp([a]) + np.testing.assert_allclose(res.data, expected.data) + + comp = LongitudeMaskingCompositor(name='test', lon_min=120., lon_max=-40.) + expected = xr.DataArray(np.array([1, 2, 3, np.nan, np.nan, np.nan, 7])) + res = comp([a]) + np.testing.assert_allclose(res.data, expected.data)