diff --git a/satpy/etc/readers/thr3mi_l1c_nc.yaml b/satpy/etc/readers/thr3mi_l1c_nc.yaml new file mode 100644 index 0000000000..6d31fe79ae --- /dev/null +++ b/satpy/etc/readers/thr3mi_l1c_nc.yaml @@ -0,0 +1,301 @@ +reader: + name: thr3mi_l1c_nc + short_name: 3MI L1C RAD NetCDF4 + long_name: EPS-SG 3MI L1C Radiance (NetCDF4) + description: > + Reader for EUMETSAT EPS-SG 3MI Multi-Angle Polarimeter Level 1C Radiance files in NetCDF4 format per PFS + sensors: [3mi] + reader: !!python/name:satpy.readers.yaml_reader.FileYAMLReader + + data_identification_keys: + name: + required: true + # wavelength: + # type: !!python/name:satpy.dataset.dataid.WavelengthRange + view: + enum: + - view1 + - view2 + - view3 + - view4 + - view5 + - view6 + - view7 + - view8 + - view9 + - view10 + - view11 + - view12 + - view13 + - view14 + transitive: true + polarization: + transitive: true + # modifiers: + # default: [] + # type: !!python/name:satpy.dataset.dataid.ModifierTuple + + +file_types: + # EUMETSAT EPS-SG Multi-view, Multi-channel, Multi-polarisation Imager (3MI) Level 1C Radiance files in NetCDF4 format + nc_3mi_l1c_rad: + + file_reader: !!python/name:satpy.readers.thr3mi_l1c_nc.Thr3miL1cNCFileHandler + file_patterns: ['W_XX-EUMETSAT-Darmstadt,SAT,{spacecraft_name:s}-3MI-1C-RAD_C_EUMT_{creation_time:%Y%m%d%H%M%S}_{mission_type:s}_{environment:s}_{sensing_start_time:%Y%m%d%H%M%S}_{sensing_end_time:%Y%m%d%H%M%S}_{disposition_mode:s}_{processing_mode:s}____.nc'] + +datasets: + +# --- Coordinates --- + lon_pixels: + name: lon_pixels + file_type: nc_3mi_l1c_rad + file_key: data/overlap_XXX/geolocation_data/longitude + file_key_overlap: /dimension/overlaps + standard_name: longitude + + lat_pixels: + name: lat_pixels + file_type: nc_3mi_l1c_rad + file_key: data/overlap_XXX/geolocation_data/latitude + file_key_overlap: /dimension/overlaps + standard_name: latitude + +# --- Measurement data --- + 3mi_410: + name: 3mi_410 + file_type: nc_3mi_l1c_rad + file_key: data/overlap_XXX/measurement_data/r_0410/reflectance_ + file_key_overlap: /dimension/overlaps + coordinates: [lat_pixels, lon_pixels] + view: [view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14] + polarization: [I, Q, U] + calibration: + reflectance: + standard_name: reflectance_factor + units: "" + radiance: + standard_name: toa_outgoing_radiance_per_unit_wavelength + chan_solar_index: 1 + wavelength: [0.410, 0.420, 0.430] + + 3mi_443: + name: 3mi_443 + file_type: nc_3mi_l1c_rad + file_key: data/overlap_XXX/measurement_data/r_0443/reflectance_ + file_key_overlap: /dimension/overlaps + coordinates: [lat_pixels, lon_pixels] + view: [view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14] + polarization: [I, Q, U] + calibration: + reflectance: + standard_name: reflectance_factor + units: "" + radiance: + standard_name: toa_outgoing_radiance_per_unit_wavelength + chan_solar_index: 2 + wavelength: [0.433, 0.443, 0.453] + + 3mi_490: + name: 3mi_490 + file_type: nc_3mi_l1c_rad + file_key: data/overlap_XXX/measurement_data/r_0490/reflectance_ + file_key_overlap: /dimension/overlaps + coordinates: [lat_pixels, lon_pixels] + overlaps: number_overlaps + view: [view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14] + polarization: [I, Q, U] + calibration: + reflectance: + standard_name: reflectance_factor + units: "" + radiance: + standard_name: toa_outgoing_radiance_per_unit_wavelength + chan_solar_index: 3 + wavelength: [0.480, 0.490, 0.500] + + 3mi_555: + name: 3mi_555 + file_type: nc_3mi_l1c_rad + file_key: data/overlap_XXX/measurement_data/r_0555/reflectance_ + file_key_overlap: /dimension/overlaps + coordinates: [lat_pixels, lon_pixels] + view: [view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14] + polarization: [I, Q, U] + calibration: + reflectance: + standard_name: reflectance_factor + units: "" + radiance: + standard_name: toa_outgoing_radiance_per_unit_wavelength + chan_solar_index: 4 + wavelength: [0.545, 0.555, 0.565] + + 3mi_670: + name: 3mi_670 + file_type: nc_3mi_l1c_rad + file_key: data/overlap_XXX/measurement_data/r_0670/reflectance_ + file_key_overlap: /dimension/overlaps + coordinates: [lat_pixels, lon_pixels] + view: [view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14] + polarization: [I, Q, U] + calibration: + reflectance: + standard_name: reflectance_factor + units: "" + radiance: + standard_name: toa_outgoing_radiance_per_unit_wavelength + chan_solar_index: 5 + wavelength: [0.660, 0.670, 0.680] + + 3mi_763: + name: 3mi_763 + file_type: nc_3mi_l1c_rad + file_key: data/overlap_XXX/measurement_data/r_0763/reflectance_ + file_key_overlap: /dimension/overlaps + coordinates: [lat_pixels, lon_pixels] + view: [view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14] + polarization: [I, Q, U] + calibration: + reflectance: + standard_name: reflectance_factor + units: "" + radiance: + standard_name: toa_outgoing_radiance_per_unit_wavelength + chan_solar_index: 6 + wavelength: [0.753, 0.763, 0.773] + + 3mi_765: + name: 3mi_765 + file_type: nc_3mi_l1c_rad + file_key: data/overlap_XXX/measurement_data/r_0765/reflectance_ + file_key_overlap: /dimension/overlaps + coordinates: [lat_pixels, lon_pixels] + view: [view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14] + polarization: [I, Q, U] + calibration: + reflectance: + standard_name: reflectance_factor + units: "" + radiance: + standard_name: toa_outgoing_radiance_per_unit_wavelength + chan_solar_index: 7 + wavelength: [0.745, 0.765, 0.785] + + 3mi_865: + name: 3mi_865 + file_type: nc_3mi_l1c_rad + file_key: data/overlap_XXX/measurement_data/r_0865/reflectance_ + file_key_overlap: /dimension/overlaps + coordinates: [lat_pixels, lon_pixels] + view: [view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14] + polarization: [I, Q, U] + calibration: + reflectance: + standard_name: reflectance_factor + units: "" + radiance: + standard_name: toa_outgoing_radiance_per_unit_wavelength + chan_solar_index: 8 + wavelength: [0.845, 0.865, 0.885] + + 3mi_910: + name: 3mi_910 + file_type: nc_3mi_l1c_rad + file_key: data/overlap_XXX/measurement_data/r_0910/reflectance_ + file_key_overlap: /dimension/overlaps + coordinates: [lat_pixels, lon_pixels] + view: [view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14] + polarization: [I, Q, U] + calibration: + reflectance: + standard_name: reflectance_factor + units: "" + radiance: + standard_name: toa_outgoing_radiance_per_unit_wavelength + chan_solar_index: 9 + wavelength: [0.900, 0.910, 0.920] + + 3mi_1370: + name: 3mi_1370 + file_type: nc_3mi_l1c_rad + file_key: data/overlap_XXX/measurement_data/r_0910/reflectance_ + file_key_overlap: /dimension/overlaps + coordinates: [lat_pixels, lon_pixels] + view: [view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12] + polarization: [I, Q, U] + calibration: + reflectance: + standard_name: reflectance_factor + units: "" + radiance: + standard_name: toa_outgoing_radiance_per_unit_wavelength + chan_solar_index: 10 + wavelength: [1.360, 1.370, 1.380] + + 3mi_1650: + name: 3mi_1650 + file_type: nc_3mi_l1c_rad + file_key: data/overlap_XXX/measurement_data/r_0910/reflectance_ + file_key_overlap: /dimension/overlaps + coordinates: [lat_pixels, lon_pixels] + view: [view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12] + polarization: [I, Q, U] + calibration: + reflectance: + standard_name: reflectance_factor + units: "" + radiance: + standard_name: toa_outgoing_radiance_per_unit_wavelength + chan_solar_index: 11 + wavelength: [1.640, 1.650, 1.660] + + 3mi_2130: + name: 3mi_2130 + file_type: nc_3mi_l1c_rad + file_key: data/overlap_XXX/measurement_data/r_0910/reflectance_ + file_key_overlap: /dimension/overlaps + coordinates: [lat_pixels, lon_pixels] + view: [view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12] + polarization: [I, Q, U] + calibration: + reflectance: + standard_name: reflectance_factor + units: "" + radiance: + standard_name: toa_outgoing_radiance_per_unit_wavelength + chan_solar_index: 12 + wavelength: [2.120, 2.130, 2.140] + +#------------------------------------------------------------------------------------------------------------------------------------- + + # --- Geometric data --- + solar_zenith: + name: solar_zenith_angle + standard_name: solar_zenith_angle + file_type: nc_3mi_l1c_rad + file_key: data/overlap_XXX/geolocation_data/solar_zenith_angle + file_key_overlap: /dimension/overlaps + file_key001: data/overlap_001/geolocation_data/solar_zenith_angle + view: [view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14] + coordinates: [lat_pixels, lon_pixels] + + relative_azimuth: + name: relative_azimuth_angle + standard_name: relative_azimuth_angle + file_type: nc_3mi_l1c_rad + file_key: data/overlap_XXX/geolocation_data/relative_azimuth_angle + file_key_overlap: /dimension/overlaps + view: [view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14] + coordinates: [lat_pixels, lon_pixels] + + sensor_zenith: + name: sensor_zenith_angle + standard_name: sensor_zenith_angle + file_type: nc_3mi_l1c_rad + file_key: data/overlap_XXX/geolocation_data/satellite_zenith_angle + file_key_overlap: /dimension/overlaps + view: [view1, view2, view3, view4, view5, view6, view7, view8, view9, view10, view11, view12, view13, view14] + coordinates: [lat_pixels, lon_pixels] + + + diff --git a/satpy/readers/thr3mi_l1c_nc.py b/satpy/readers/thr3mi_l1c_nc.py new file mode 100644 index 0000000000..a9e460337c --- /dev/null +++ b/satpy/readers/thr3mi_l1c_nc.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Satpy developers +# +# 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 . +"""EUMETSAT EPS-SG Multi-view, Multi-channel, Multi-polarisation Imager (3MI) Level 1C products reader. + +The ``3mi_l1c_nc`` reader reads EPS-SG 3MI L1C image data in netCDF format. The format is explained +in the `EPS-SG 3MI Level 1C Product Format Specification`_. Details of format and test data can be +found at: +https://user.eumetsat.int/resources/user-guides/metop-sg-3-mi-l1b-and-l1c-data-guide + +This version is an initial draft trial version. + +""" + +import logging +from datetime import datetime +import xarray as xr +from satpy.readers.netcdf_utils import NetCDF4FileHandler + +logger = logging.getLogger(__name__) + + +class Thr3miL1cNCFileHandler(NetCDF4FileHandler): + + """Base reader class for 3MI products in netCDF format. + + Args: + filename (str): File to read + filename_info (dict): Dictionary with filename information + filetype_info (dict): Dictionary with filetype information + + """ + + def __init__(self, filename, filename_info, filetype_info, **kwargs): + """Prepare the class for dataset reading.""" + super().__init__(filename, filename_info, filetype_info, auto_maskandscale=True) + + def _standardize_dims(self, variable): + """Standardize dims to y, note only 1D data for 3MI""" + + # lat/lon dimensions + if 'geo_reference_grid_cells' in variable.dims: + variable = variable.rename({'geo_reference_grid_cells': 'y'}) + return variable + + def get_dataset(self, dataset_id, dataset_info): + """Get dataset using file_key in dataset_info.""" + var_key_xxx = dataset_info['file_key'] + var_key_overlap = dataset_info['file_key_overlap'] + view_key = 0 + try: + number_overlaps = self[var_key_overlap] + except KeyError: + logger.warning("Could not find key %s in NetCDF file, no valid Dataset created", var_key_overlap) + return None + + # Loop over the number of overlaps present in the granule + for i_overlap in range(number_overlaps): + # set file_key for current overlap + str_overlap = '0' + '0' + str(i_overlap) + var_key = var_key_xxx.replace('XXX', str_overlap) + # Radiance data has multiple views and polarisations, geolocation not. + # If radiance data then get the view and append the polarisation. + if var_key[-9:] != 'longitude' and var_key[-8:] != 'latitude': + view_key = dataset_info['view'] + var_key = var_key + dataset_info['polarization'] + + logger.debug('Reading in file to get dataset with key %s.', var_key) + try: + variable = self[var_key] + except KeyError: + logger.warning("Could not find key %s in NetCDF file, no valid Dataset created", var_key) + return None + if i_overlap > 0: + if var_key[-9:] != 'longitude' and var_key[-8:] != 'latitude': + variable = xr.concat([variable[:, view_key], variable_old[:, view_key]], dim="geo_reference_grid_cells") + else: + variable = xr.concat([variable, variable_old], dim="geo_reference_grid_cells") + variable_old = variable.copy(deep=True) + # Manage the attributes of the dataset + variable.attrs.setdefault('units', None) + variable.attrs.update(dataset_info) + variable.attrs.update(self._get_global_attributes()) + variable = self._standardize_dims(variable) + return variable + + def _get_global_attributes(self): + """Create a dictionary of global attributes to be added to all datasets.""" + attributes = { + 'filename': self.filename, + 'start_time': self.start_time, + 'end_time': self.end_time, + 'spacecraft_name': self.spacecraft_name, + 'ssp_lon': self.ssp_lon, + 'sensor': self.sensor, + 'filename_start_time': self.filename_info['sensing_start_time'], + 'filename_end_time': self.filename_info['sensing_end_time'], + 'platform_name': self.spacecraft_name, + } + + # Add a "quality_group" item to the dictionary with all the variables and attributes + # which are found in the 'quality' group of the 3MI product + quality_group = self['quality'] + quality_dict = {} + for key in quality_group: + # Add the values (as Numpy array) of each variable in the group where possible + try: + quality_dict[key] = quality_group[key].values + except ValueError: + quality_dict[key] = None + # Add the attributes of the quality group + quality_dict.update(quality_group.attrs) + + attributes['quality_group'] = quality_dict + + return attributes + + @property + def start_time(self): + """Get observation start time.""" + try: + start_time = datetime.strptime(self['/attr/sensing_start_time_utc'], '%Y%m%d%H%M%S.%f') + except ValueError: + start_time = datetime.strptime(self['/attr/sensing_start_time_utc'], '%Y-%m-%d %H:%M:%S.%f') + return start_time + + @property + def end_time(self): + """Get observation end time.""" + try: + end_time = datetime.strptime(self['/attr/sensing_end_time_utc'], '%Y%m%d%H%M%S.%f') + except ValueError: + end_time = datetime.strptime(self['/attr/sensing_end_time_utc'], '%Y-%m-%d %H:%M:%S.%f') + return end_time + + @property + def spacecraft_name(self): + """Return spacecraft name.""" + return self['/attr/spacecraft'] + + @property + def sensor(self): + """Return sensor.""" + return self['/attr/instrument'] + + @property + def ssp_lon(self): + """Return subsatellite point longitude.""" + # This parameter is not applicable to 3MI + return None \ No newline at end of file diff --git a/satpy/tests/reader_tests/test_thr3mi_l1c_nc.py b/satpy/tests/reader_tests/test_thr3mi_l1c_nc.py new file mode 100644 index 0000000000..b0e4b03e72 --- /dev/null +++ b/satpy/tests/reader_tests/test_thr3mi_l1c_nc.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Satpy developers +# +# 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 . + +"""The Thr3mi_l1c_nc reader tests package.""" + +import datetime +import os +import unittest +import uuid +#from unittest import mock + +import numpy as np +import pytest +import xarray as xr +from netCDF4 import Dataset + +from satpy.readers.thr3mi_l1c_nc import Thr3miL1cNCFileHandler + +TEST_FILE = "test_file_thr3mi_l1c_nc.nc" + + +class TestThr3miNCL1cFileHandler(unittest.TestCase): + """Test the Thr3miNCL1cFileHandler reader.""" + + def setUp(self): + """Set up the test.""" + # Easiest way to test the reader is to create a test netCDF file on the fly + # uses a UUID to avoid permission conflicts during execution of tests in parallel + self.test_file_name = TEST_FILE + str(uuid.uuid1()) + ".nc" + + with Dataset(self.test_file_name, "w") as nc: + # Add global attributes + nc.sensing_start_time_utc = "20170920173040.888" + nc.sensing_end_time_utc = "20170920174117.555" + nc.spacecraft = "test_spacecraft" + nc.instrument = "test_instrument" + + nc.createDimension("overlaps", 1) + + # Create data group + g1 = nc.createGroup("data") + + # Create data/measurement_data group + g1_1 = g1.createGroup("overlap_000") + + # Add dimensions to data/measurement_data group + g1_1.createDimension("geo_reference_grid_cells", 10) + g1_1.createDimension("viewing_directions_VNIR", 3) + + g1_1_1 = g1_1.createGroup("measurement_data") + g1_1_2 = g1_1.createGroup("geolocation_data") + + g1_1_1_1 = g1_1_1.createGroup("r_865") + + # Add variables to data/measurement_data group + reflectance_Q = g1_1_1_1.createVariable("reflectance_", np.float32, + dimensions=("geo_reference_grid_cells", "viewing_directions_VNIR")) + reflectance_Q[:,0] = 75. + reflectance_Q[:,1] = 76. + reflectance_Q.test_attr = "attr" + + lon = g1_1_2.createVariable("longitude", + np.float32, + dimensions=("geo_reference_grid_cells")) + lon[:] = 150. + lat = g1_1_2.createVariable("latitude", + np.float32, + dimensions=("geo_reference_grid_cells")) + lat[:] = 12. + + # Create quality group + g2 = nc.createGroup("quality") + + # Add dimensions to quality group + g2.createDimension("gap_items", 2) + + # Add variables to quality group + var = g2.createVariable("duration_of_product", np.double, dimensions=()) + var[:] = 1.0 + var = g2.createVariable("duration_of_data_present", np.double, dimensions=()) + var[:] = 2.0 + var = g2.createVariable("duration_of_data_missing", np.double, dimensions=()) + var[:] = 3.0 + var = g2.createVariable("duration_of_data_degraded", np.double, dimensions=()) + var[:] = 4.0 + var = g2.createVariable("gap_start_time_utc", np.double, dimensions=("gap_items",)) + var[:] = [5.0, 6.0] + var = g2.createVariable("gap_end_time_utc", np.double, dimensions=("gap_items",)) + var[:] = [7.0, 8.0] + + # Filename info valid for all readers + filename_info = { + "creation_time": datetime.datetime(year=2017, month=9, day=22, + hour=22, minute=40, second=10), + "sensing_start_time": datetime.datetime(year=2017, month=9, day=20, + hour=12, minute=30, second=30), + "sensing_end_time": datetime.datetime(year=2017, month=9, day=20, + hour=18, minute=30, second=50) + } + + # Create a reader + self.reader = Thr3miL1cNCFileHandler( + filename=self.test_file_name, + filename_info=filename_info, + filetype_info={} + ) + + def tearDown(self): + """Remove the previously created test file.""" + # Catch Windows PermissionError for removing the created test file. + try: + os.remove(self.test_file_name) + except OSError: + pass + + def test_file_reading(self): + """Test the file product reading.""" + # Checks that the basic functionalities are correctly executed + expected_start_time = datetime.datetime(year=2017, month=9, day=20, + hour=17, minute=30, second=40, microsecond=888000) + assert self.reader.start_time == expected_start_time + + expected_end_time = datetime.datetime(year=2017, month=9, day=20, + hour=17, minute=41, second=17, microsecond=555000) + assert self.reader.end_time == expected_end_time + + assert self.reader.spacecraft_name == "test_spacecraft" + assert self.reader.sensor == "test_instrument" + assert self.reader.ssp_lon is None + + # Checks that the global attributes are correctly read + expected_global_attributes = { + "filename": self.test_file_name, + "start_time": expected_start_time, + "end_time": expected_end_time, + "spacecraft_name": "test_spacecraft", + "ssp_lon": None, + "sensor": "test_instrument", + "filename_start_time": datetime.datetime(year=2017, month=9, day=20, + hour=12, minute=30, second=30), + "filename_end_time": datetime.datetime(year=2017, month=9, day=20, + hour=18, minute=30, second=50), + "platform_name": "test_spacecraft", + "quality_group": { + "duration_of_product": 1., + "duration_of_data_present": 2., + "duration_of_data_missing": 3., + "duration_of_data_degraded": 4., + "gap_start_time_utc": (5., 6.), + "gap_end_time_utc": (7., 8.) + } + } + + global_attributes = self.reader._get_global_attributes() + # Since the global_attributes dictionary contains numpy arrays, + # it is not possible to peform a simple equality test + # Must iterate on all keys to confirm that the dictionaries are equal + assert global_attributes.keys() == expected_global_attributes.keys() + for key in expected_global_attributes: + if key not in ["quality_group"]: + # Quality check must be valid for both iterable and not iterable elements + try: + equal = all(global_attributes[key] == expected_global_attributes[key]) + except (TypeError, ValueError): + equal = global_attributes[key] == expected_global_attributes[key] + assert equal + else: + assert global_attributes[key].keys() == expected_global_attributes[key].keys() + for inner_key in global_attributes[key]: + # Equality check must be valid for both iterable and not iterable elements + try: + equal = all(global_attributes[key][inner_key] == expected_global_attributes[key][inner_key]) + except (TypeError, ValueError): + equal = global_attributes[key][inner_key] == expected_global_attributes[key][inner_key] + assert equal + + + def test_standardize_dims(self): + """Test the standardize dims function.""" + test_variable = xr.DataArray( + dims=("geo_reference_grid_cells"), + name="test_data", + attrs={ + "key_1": "value_lat_1", + "key_2": "value_lat_2" + }, + data=np.ones((10)) * 1. + ) + out_variable = self.reader._standardize_dims(test_variable) + print("out_variable ", out_variable) + assert np.allclose(out_variable.values, np.ones(10)) + assert out_variable.dims == ("y",) + assert out_variable.attrs["key_1"] == "value_lat_1" + + + + +