|
| 1 | +# Copyright iris-grib contributors |
| 2 | +# |
| 3 | +# This file is part of iris-grib and is released under the BSD license. |
| 4 | +# See LICENSE in the root of the repository for full licensing details. |
| 5 | +""" |
| 6 | +Module to support loading of GRIB1 data. |
| 7 | +
|
| 8 | +Converts a GRIB1 message into cube metadata. |
| 9 | +
|
| 10 | +This is a re-implementation of '._grib1_legacy.grib1_load_rules', but now using a |
| 11 | +:class:`iris_grib.message.GribMessage` in place of a |
| 12 | +:class:`iris_grib._grib1_legacy.grib_wrapper.GribWrapper`. |
| 13 | +""" |
| 14 | + |
| 15 | +import numpy as np |
| 16 | + |
| 17 | +from iris.coords import DimCoord |
| 18 | +from iris import coord_systems |
| 19 | +from iris.exceptions import TranslationError |
| 20 | +from iris.fileformats.rules import ( |
| 21 | + ConversionMetadata, |
| 22 | +) |
| 23 | + |
| 24 | +from iris_grib.message import GribMessage |
| 25 | +from iris_grib import grib_phenom_translation as gptx |
| 26 | + |
| 27 | +_CENTRE_NAME_NUMBERS_BACKREFS = { |
| 28 | + "egrr": 74, |
| 29 | + "ecmwf": 98, |
| 30 | +} |
| 31 | + |
| 32 | + |
| 33 | +def phenomenon(field: GribMessage, metadata: ConversionMetadata): |
| 34 | + section1 = field.sections[1] |
| 35 | + |
| 36 | + tables_version = section1["table2Version"] |
| 37 | + centre_name = section1["centre"] |
| 38 | + # It seems that, for GRIB1, we have no access to the original number here. |
| 39 | + centre_number = _CENTRE_NAME_NUMBERS_BACKREFS.get(centre_name, 0) |
| 40 | + param_number = section1["indicatorOfParameter"] |
| 41 | + |
| 42 | + grib_code = gptx.GRIBCode( |
| 43 | + edition=1, |
| 44 | + table_version=tables_version, |
| 45 | + centre_number=centre_number, |
| 46 | + number=param_number, |
| 47 | + ) |
| 48 | + metadata["attributes"]["GRIB_PARAM"] = grib_code |
| 49 | + |
| 50 | + cf_data = gptx.grib1_phenom_to_cf_info( |
| 51 | + table2_version=tables_version, |
| 52 | + centre_number=centre_number, |
| 53 | + param_number=param_number, |
| 54 | + ) |
| 55 | + standard_name, long_name, units = None, None, None |
| 56 | + if cf_data is not None: |
| 57 | + standard_name = cf_data.standard_name |
| 58 | + long_name = cf_data.standard_name or cf_data.long_name |
| 59 | + units = cf_data.units |
| 60 | + elif tables_version < 128: |
| 61 | + # built-in decodes... |
| 62 | + match param_number: |
| 63 | + case 11: |
| 64 | + standard_name = "air_temperature" |
| 65 | + units = "kelvin" |
| 66 | + |
| 67 | + case 33: |
| 68 | + standard_name = "x_wind" |
| 69 | + units = "m s-1" |
| 70 | + |
| 71 | + case 34: |
| 72 | + standard_name = "y_wind" |
| 73 | + units = "m s-1" |
| 74 | + |
| 75 | + if tables_version >= 128 or (tables_version == 1 and param_number >= 128): |
| 76 | + long_name = f"UNKNOWN LOCAL PARAM {param_number}.{tables_version}" |
| 77 | + units = "???" |
| 78 | + |
| 79 | + metadata["standard_name"] = standard_name |
| 80 | + metadata["long_name"] = long_name |
| 81 | + metadata["units"] = units |
| 82 | + |
| 83 | + |
| 84 | +def grid(field: GribMessage, metadata: ConversionMetadata): |
| 85 | + grid_type = field.sections[1]["gridDefinition"] |
| 86 | + section2 = field.sections[2] |
| 87 | + grid_type = section2["dataRepresentationType"] |
| 88 | + dim_coords_and_dims = metadata["dim_coords_and_dims"] |
| 89 | + match grid_type: |
| 90 | + case 10: |
| 91 | + # rotated latitude-longitude |
| 92 | + |
| 93 | + # just pin this aspect for now |
| 94 | + scanning_code = section2["scanningMode"] |
| 95 | + scanning_code &= 7 # only 3 bits significant?? |
| 96 | + # TODO: don't understand why we can get value=64 |
| 97 | + # only 3 bits : bits 3-7 seem to be unspecified (upto 2015?) |
| 98 | + |
| 99 | + if scanning_code != 0: |
| 100 | + raise TranslationError( |
| 101 | + f"bad scanning code: scanningMode={scanning_code}" |
| 102 | + ) |
| 103 | + |
| 104 | + res_comp_flags = section2["resolutionAndComponentFlags"] |
| 105 | + res_comp_flags &= 1 | 2 | 16 |
| 106 | + # TODO: AGAIN don't understand why we can get value=64 |
| 107 | + # only 4 bits : bits 3-4 and 6-7 unspecified (upto 2015?) |
| 108 | + |
| 109 | + if res_comp_flags != 0: |
| 110 | + raise TranslationError( |
| 111 | + f"bad grid scanning: resolutionAndComponentFlags={scanning_code}" |
| 112 | + ) |
| 113 | + |
| 114 | + spole_lon = 0.001 * section2["longitudeOfSouthernPole"] |
| 115 | + spole_lat = 0.001 * section2["latitudeOfSouthernPole"] |
| 116 | + rot_ang = section2["angleOfRotationInDegrees"] |
| 117 | + geoid = coord_systems.GeogCS(semi_major_axis=6367470) |
| 118 | + coord_system = coord_systems.RotatedGeogCS( |
| 119 | + -spole_lat, |
| 120 | + (spole_lon + 180.0) % 360.0, |
| 121 | + rot_ang, |
| 122 | + geoid, |
| 123 | + ) |
| 124 | + |
| 125 | + nlats = section2["Nj"] |
| 126 | + y0 = 0.001 * section2["latitudeOfFirstGridPoint"] |
| 127 | + dy = 0.001 * section2["jDirectionIncrement"] |
| 128 | + y_points = np.arange(nlats, dtype=np.float64) * dy + y0 |
| 129 | + dim_coords_and_dims.append( |
| 130 | + ( |
| 131 | + DimCoord( |
| 132 | + y_points, |
| 133 | + "grid_latitude", |
| 134 | + units="degrees", |
| 135 | + coord_system=coord_system, |
| 136 | + ), |
| 137 | + 0, |
| 138 | + ) |
| 139 | + ) |
| 140 | + |
| 141 | + nlons = section2["Ni"] |
| 142 | + x0 = 0.001 * section2["longitudeOfFirstGridPoint"] |
| 143 | + dx = 0.001 * section2["iDirectionIncrement"] |
| 144 | + x_points = np.arange(nlons, dtype=np.float64) * dx + x0 |
| 145 | + dim_coords_and_dims.append( |
| 146 | + ( |
| 147 | + DimCoord( |
| 148 | + x_points, |
| 149 | + "grid_longitude", |
| 150 | + units="degrees", |
| 151 | + coord_system=coord_system, |
| 152 | + ), |
| 153 | + 1, |
| 154 | + ) |
| 155 | + ) |
| 156 | + |
| 157 | + case _: |
| 158 | + msg = ( |
| 159 | + "Unsupported grid type in grib message: " |
| 160 | + f"dataRepresentationType={grid_type}" |
| 161 | + ) |
| 162 | + raise TranslationError(msg) |
| 163 | + |
| 164 | + |
| 165 | +def grib1_convert(field: GribMessage, metadata: ConversionMetadata): |
| 166 | + assert hasattr(field, "sections") |
| 167 | + assert field.sections[0]["editionNumber"] == 1 |
| 168 | + |
| 169 | + # Section 1 == product definitions |
| 170 | + phenomenon(field, metadata) |
| 171 | + grid(field, metadata) |
0 commit comments