Source code for satpy.tests.reader_tests.test_grib

#!/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.grib module."""

import os
import sys
from unittest import mock

import numpy as np
import pytest
import xarray as xr

from satpy.dataset import DataQuery

# Parameterized cases
TEST_ARGS = ("proj_params", "lon_corners", "lat_corners")
TEST_PARAMS = (
    (None, None, None),  # cyl default case
    (
        {
            "a": 6371229, "b": 6371229, "proj": "lcc",
            "lon_0": 265.0, "lat_0": 25.0,
            "lat_1": 25.0, "lat_2": 25.0
        },
        [-133.459, -65.12555139, -152.8786225, -49.41598659],
        [12.19, 14.34208538, 54.56534318, 57.32843565]
    ),
)


[docs] def fake_gribdata(): """Return some faked data for use as grib values.""" return np.arange(25.).reshape((5, 5))
[docs] def _round_trip_projection_lonlat_check(area): """Check that X/Y coordinates can be transformed multiple times. Many GRIB files include non-standard projects that work for the initial transformation of X/Y coordinates to longitude/latitude, but may fail in the reverse transformation. For example, an eqc projection that goes from 0 longitude to 360 longitude. The X/Y coordinates may accurately go from the original X/Y metered space to the correct longitude/latitude, but transforming those coordinates back to X/Y space will produce the wrong result. """ from pyproj import Proj p = Proj(area.crs) x, y = area.get_proj_vectors() lon, lat = p(x, y, inverse=True) x2, y2 = p(lon, lat) np.testing.assert_almost_equal(x, x2) np.testing.assert_almost_equal(y, y2)
[docs] class FakeMessage(object): """Fake message returned by pygrib.open().message(x).""" def __init__(self, values, proj_params=None, latlons=None, **attrs): """Init the message.""" super(FakeMessage, self).__init__() self.attrs = attrs self.values = values if proj_params is None: proj_params = {"a": 6371229, "b": 6371229, "proj": "cyl"} self.projparams = proj_params self._latlons = latlons
[docs] def keys(self): """Get message keys.""" return self.attrs.keys()
[docs] def latlons(self): """Get coordinates.""" return self._latlons
def __getitem__(self, item): """Get item.""" return self.attrs[item]
[docs] def valid_key(self, key): """Validate key.""" return True
[docs] class FakeGRIB(object): """Fake GRIB file returned by pygrib.open.""" def __init__(self, messages=None, proj_params=None, latlons=None): """Init the grib file.""" super(FakeGRIB, self).__init__() if messages is not None: self._messages = messages else: self._messages = [ FakeMessage( values=fake_gribdata(), name="TEST", shortName="t", level=100, pressureUnits="hPa", cfName="air_temperature", units="K", dataDate=20180504, dataTime=1200, validityDate=20180504, validityTime=1800, distinctLongitudes=np.arange(5.), distinctLatitudes=np.arange(5.), missingValue=9999, modelName="notknown", minimum=100., maximum=200., typeOfLevel="isobaricInhPa", jScansPositively=0, proj_params=proj_params, latlons=latlons, ), FakeMessage( values=fake_gribdata(), name="TEST", shortName="t", level=200, pressureUnits="hPa", cfName="air_temperature", units="K", dataDate=20180504, dataTime=1200, validityDate=20180504, validityTime=1800, distinctLongitudes=np.arange(5.), distinctLatitudes=np.arange(5.), missingValue=9999, modelName="notknown", minimum=100., maximum=200., typeOfLevel="isobaricInhPa", jScansPositively=1, proj_params=proj_params, latlons=latlons, ), FakeMessage( values=fake_gribdata(), name="TEST", shortName="t", level=300, pressureUnits="hPa", cfName="air_temperature", units="K", dataDate=20180504, dataTime=1200, validityDate=20180504, validityTime=1800, distinctLongitudes=np.arange(5.), distinctLatitudes=np.arange(5.), missingValue=9999, minimum=100., maximum=200., typeOfLevel="isobaricInhPa", jScansPositively=0, proj_params=proj_params, latlons=latlons, ), ] self.messages = len(self._messages)
[docs] def message(self, msg_num): """Get a message.""" return self._messages[msg_num - 1]
[docs] def seek(self, loc): """Seek.""" return
def __iter__(self): """Iterate.""" return iter(self._messages) def __enter__(self): """Enter.""" return self def __exit__(self, exc_type, exc_val, exc_tb): """Exit."""
[docs] class TestGRIBReader: """Test GRIB Reader.""" yaml_file = "grib.yaml"
[docs] def setup_method(self): """Wrap pygrib to read fake data.""" from satpy._config import config_search_paths self.reader_configs = config_search_paths(os.path.join("readers", self.yaml_file)) try: import pygrib except ImportError: pygrib = None self.orig_pygrib = pygrib sys.modules["pygrib"] = mock.MagicMock()
[docs] def teardown_method(self): """Re-enable pygrib import.""" sys.modules["pygrib"] = self.orig_pygrib
[docs] def _get_test_datasets(self, dataids, fake_pygrib=None): from satpy.readers import load_reader if fake_pygrib is None: fake_pygrib = FakeGRIB() with mock.patch("satpy.readers.grib.pygrib") as pg: pg.open.return_value = fake_pygrib r = load_reader(self.reader_configs) loadables = r.select_files_from_pathnames([ "gfs.t18z.sfluxgrbf106.grib2", ]) r.create_filehandlers(loadables) datasets = r.load(dataids) return datasets
[docs] @staticmethod def _get_fake_pygrib(proj_params, lon_corners, lat_corners): latlons = None if lon_corners is not None: lats = np.array([ [lat_corners[0], 0, 0, 0, lat_corners[1]], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [lat_corners[2], 0, 0, 0, lat_corners[3]]]) lons = np.array([ [lon_corners[0], 0, 0, 0, lon_corners[1]], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [lon_corners[2], 0, 0, 0, lon_corners[3]]]) latlons = (lats, lons) fake_pygrib = FakeGRIB( proj_params=proj_params, latlons=latlons) return fake_pygrib
[docs] def test_init(self): """Test basic init with no extra parameters.""" from satpy.readers import load_reader with mock.patch("satpy.readers.grib.pygrib") as pg: pg.open.return_value = FakeGRIB() r = load_reader(self.reader_configs) loadables = r.select_files_from_pathnames([ "gfs.t18z.sfluxgrbf106.grib2", ]) assert len(loadables) == 1 r.create_filehandlers(loadables) # make sure we have some files assert r.file_handlers
[docs] def test_file_pattern(self): """Test matching of file patterns.""" from satpy.readers import load_reader filenames = [ "quinoa.grb", "tempeh.grb2", "tofu.grib2", "falafel.grib", "S_NWC_NWP_1900-01-01T00:00:00Z_999.grib"] r = load_reader(self.reader_configs) files = r.select_files_from_pathnames(filenames) assert len(files) == 4
[docs] @pytest.mark.parametrize(TEST_ARGS, TEST_PARAMS) def test_load_all(self, proj_params, lon_corners, lat_corners): """Test loading all test datasets.""" fake_pygrib = self._get_fake_pygrib(proj_params, lon_corners, lat_corners) dataids = [ DataQuery(name="t", level=100, modifiers=tuple()), DataQuery(name="t", level=200, modifiers=tuple()), DataQuery(name="t", level=300, modifiers=tuple()) ] datasets = self._get_test_datasets(dataids, fake_pygrib) assert len(datasets) == 3 for v in datasets.values(): assert v.attrs["units"] == "K" assert isinstance(v, xr.DataArray)
[docs] @pytest.mark.parametrize(TEST_ARGS, TEST_PARAMS) def test_area_def_crs(self, proj_params, lon_corners, lat_corners): """Check that the projection is accurate.""" fake_pygrib = self._get_fake_pygrib(proj_params, lon_corners, lat_corners) dataids = [DataQuery(name="t", level=100, modifiers=tuple())] datasets = self._get_test_datasets(dataids, fake_pygrib) area = datasets["t"].attrs["area"] if not hasattr(area, "crs"): pytest.skip("Can't test with pyproj < 2.0") _round_trip_projection_lonlat_check(area)
[docs] @pytest.mark.parametrize(TEST_ARGS, TEST_PARAMS) def test_missing_attributes(self, proj_params, lon_corners, lat_corners): """Check that the grib reader handles missing attributes in the grib file.""" fake_pygrib = self._get_fake_pygrib(proj_params, lon_corners, lat_corners) # This has modelName query_contains = DataQuery(name="t", level=100, modifiers=tuple()) # This does not have modelName query_not_contains = DataQuery(name="t", level=300, modifiers=tuple()) dataset = self._get_test_datasets([query_contains, query_not_contains], fake_pygrib) assert dataset[query_contains].attrs["modelName"] == "notknown" assert dataset[query_not_contains].attrs["modelName"] == "unknown"
[docs] @pytest.mark.parametrize(TEST_ARGS, TEST_PARAMS) def test_jscanspositively(self, proj_params, lon_corners, lat_corners): """Check that data is flipped if the jScansPositively is present.""" fake_pygrib = self._get_fake_pygrib(proj_params, lon_corners, lat_corners) # This has no jScansPositively query_not_contains = DataQuery(name="t", level=100, modifiers=tuple()) # This contains jScansPositively query_contains = DataQuery(name="t", level=200, modifiers=tuple()) dataset = self._get_test_datasets([query_contains, query_not_contains], fake_pygrib) np.testing.assert_allclose(fake_gribdata(), dataset[query_not_contains].values) np.testing.assert_allclose(fake_gribdata(), dataset[query_contains].values[::-1])