3535import iris
3636import iris .exceptions
3737import iris .unit
38+ from iris .coord_systems import GeogCS , RotatedGeogCS , TransverseMercator
3839from iris .fileformats .rules import is_regular , regular_step
3940from iris .fileformats .grib import grib_phenom_translation as gptx
4041from iris .fileformats .grib ._load_convert import (_STATISTIC_TYPE_NAMES ,
@@ -92,6 +93,19 @@ def fixup_int32_as_uint32(value):
9293 return value
9394
9495
96+ def ensure_set_int32_value (grib , key , value ):
97+ """
98+ Ensure the workaround function :func:`fixup_int32_as_uint32` is applied as
99+ necessary to problem keys.
100+
101+ """
102+ try :
103+ gribapi .grib_set (grib , key , value )
104+ except gribapi .GribInternalError :
105+ value = fixup_int32_as_uint32 (value )
106+ gribapi .grib_set (grib , key , value )
107+
108+
95109###############################################################################
96110#
97111# Constants
@@ -104,13 +118,16 @@ def fixup_int32_as_uint32(value):
104118# Reference Regulation 92.1.6
105119_DEFAULT_DEGREES_UNITS = 1.0e-6
106120
121+ # Units scaling for Grid Definition Template 3.12
122+ _TRANSVERSE_MERCATOR_DEGREES_UNITS = 1.0e-2
107123
108124###############################################################################
109125#
110126# Identification Section 1
111127#
112128###############################################################################
113129
130+
114131def centre (cube , grib ):
115132 # TODO: read centre from cube
116133 gribapi .grib_set_long (grib , "centre" , 74 ) # UKMO
@@ -159,28 +176,35 @@ def identification(cube, grib):
159176#
160177###############################################################################
161178
162- def shape_of_the_earth (cube , grib ):
163179
180+ def shape_of_the_earth (cube , grib ):
164181 # assume latlon
165182 cs = cube .coord (dimensions = [0 ]).coord_system
166183
167- # Turn them all missing to start with (255 for byte, -1 for long)
184+ # Initially set shape_of_earth keys to missing (255 for byte, -1 for long).
168185 gribapi .grib_set_long (grib , "scaleFactorOfRadiusOfSphericalEarth" , 255 )
169186 gribapi .grib_set_long (grib , "scaledValueOfRadiusOfSphericalEarth" , - 1 )
170187 gribapi .grib_set_long (grib , "scaleFactorOfEarthMajorAxis" , 255 )
171188 gribapi .grib_set_long (grib , "scaledValueOfEarthMajorAxis" , - 1 )
172189 gribapi .grib_set_long (grib , "scaleFactorOfEarthMinorAxis" , 255 )
173190 gribapi .grib_set_long (grib , "scaledValueOfEarthMinorAxis" , - 1 )
174191
175- ellipsoid = cs
176- if isinstance (cs , iris .coord_systems .RotatedGeogCS ):
192+ if isinstance (cs , GeogCS ):
193+ ellipsoid = cs
194+ else :
177195 ellipsoid = cs .ellipsoid
196+ if ellipsoid is None :
197+ msg = "Could not determine shape of the earth from coord system " \
198+ "of horizontal grid."
199+ raise iris .exceptions .TranslationError (msg )
178200
201+ # Spherical earth.
179202 if ellipsoid .inverse_flattening == 0.0 :
180203 gribapi .grib_set_long (grib , "shapeOfTheEarth" , 1 )
181204 gribapi .grib_set_long (grib , "scaleFactorOfRadiusOfSphericalEarth" , 0 )
182205 gribapi .grib_set_long (grib , "scaledValueOfRadiusOfSphericalEarth" ,
183206 ellipsoid .semi_major_axis )
207+ # Oblate spheroid earth.
184208 else :
185209 gribapi .grib_set_long (grib , "shapeOfTheEarth" , 7 )
186210 gribapi .grib_set_long (grib , "scaleFactorOfEarthMajorAxis" , 0 )
@@ -223,9 +247,8 @@ def latlon_first_last(x_coord, y_coord, grib):
223247def dx_dy (x_coord , y_coord , grib ):
224248 x_step = regular_step (x_coord )
225249 y_step = regular_step (y_coord )
226- # TODO: THIS USED BE "Dx" and "Dy"!!! DID THE API CHANGE AGAIN???
227- gribapi .grib_set_double (grib , "DxInDegrees" , float (abs (x_step )))
228- gribapi .grib_set_double (grib , "DyInDegrees" , float (abs (y_step )))
250+ gribapi .grib_set (grib , "DxInDegrees" , float (abs (x_step )))
251+ gribapi .grib_set (grib , "DyInDegrees" , float (abs (y_step )))
229252
230253
231254def scanning_mode_flags (x_coord , y_coord , grib ):
@@ -235,7 +258,7 @@ def scanning_mode_flags(x_coord, y_coord, grib):
235258 int (y_coord .points [1 ] - y_coord .points [0 ] > 0 ))
236259
237260
238- def latlon_grid_common (cube , grib ):
261+ def horizontal_grid_common (cube , grib ):
239262 # Grib encoding of the sequences of X and Y points.
240263 y_coord = cube .coord (dimensions = [0 ])
241264 x_coord = cube .coord (dimensions = [1 ])
@@ -312,7 +335,7 @@ def grid_definition_template_0(cube, grib):
312335 """
313336 # Constant resolution, aka 'regular' true lat-lon grid.
314337 gribapi .grib_set_long (grib , "gridDefinitionTemplateNumber" , 0 )
315- latlon_grid_common (cube , grib )
338+ horizontal_grid_common (cube , grib )
316339 latlon_points_regular (cube , grib )
317340
318341
@@ -333,7 +356,7 @@ def grid_definition_template_1(cube, grib):
333356 rotated_pole (cube , grib )
334357
335358 # Encode the lat/lon points.
336- latlon_grid_common (cube , grib )
359+ horizontal_grid_common (cube , grib )
337360 latlon_points_regular (cube , grib )
338361
339362
@@ -351,19 +374,84 @@ def grid_definition_template_5(cube, grib):
351374 # Without this, setting "gridDefinitionTemplateNumber" = 5 causes an
352375 # immediate error.
353376 # See: https://software.ecmwf.int/issues/browse/SUP-1095
354- # This is acceptable, as the subsequent call to 'latlon_grid_common' will
355- # set these to the correct horizontal dimensions (by calling 'grid_dims').
377+ # This is acceptable, as the subsequent call to 'horizontal_grid_common'
378+ # will set these to the correct horizontal dimensions
379+ # (by calling 'grid_dims').
356380 gribapi .grib_set (grib , "Ni" , 1 )
357381 gribapi .grib_set (grib , "Nj" , 1 )
358382 gribapi .grib_set (grib , "gridDefinitionTemplateNumber" , 5 )
359383
360384 # Record details of the rotated coordinate system.
361385 rotated_pole (cube , grib )
362386 # Encode the lat/lon points.
363- latlon_grid_common (cube , grib )
387+ horizontal_grid_common (cube , grib )
364388 latlon_points_irregular (cube , grib )
365389
366390
391+ def grid_definition_template_12 (cube , grib ):
392+ """
393+ Set keys within the provided grib message based on
394+ Grid Definition Template 3.12.
395+
396+ Template 3.12 is used to represent a Transverse Mercator grid.
397+
398+ """
399+ gribapi .grib_set (grib , "gridDefinitionTemplateNumber" , 12 )
400+
401+ # Retrieve some information from the cube.
402+ y_coord = cube .coord (dimensions = [0 ])
403+ x_coord = cube .coord (dimensions = [1 ])
404+ cs = y_coord .coord_system
405+
406+ # Set some keys specific to GDT12.
407+ # Encode the horizontal points.
408+ x_step = regular_step (x_coord )
409+ y_step = regular_step (y_coord )
410+ gribapi .grib_set (grib , "Di" ,
411+ float (abs (x_step )) / _TRANSVERSE_MERCATOR_DEGREES_UNITS )
412+ gribapi .grib_set (grib , "Dj" ,
413+ float (abs (y_step )) / _TRANSVERSE_MERCATOR_DEGREES_UNITS )
414+ horizontal_grid_common (cube , grib )
415+
416+ # GRIBAPI expects unsigned ints in X1, X2, Y1, Y2 but it should accept
417+ # signed ints, so work around this.
418+ # See https://software.ecmwf.int/issues/browse/SUP-1101
419+ ensure_set_int32_value (
420+ grib , "Y1" ,
421+ int (y_coord .points [0 ] / _TRANSVERSE_MERCATOR_DEGREES_UNITS ))
422+ ensure_set_int32_value (
423+ grib , "Y2" ,
424+ int (y_coord .points [- 1 ] / _TRANSVERSE_MERCATOR_DEGREES_UNITS ))
425+ ensure_set_int32_value (
426+ grib , "X1" ,
427+ int (x_coord .points [0 ] / _TRANSVERSE_MERCATOR_DEGREES_UNITS ))
428+ ensure_set_int32_value (
429+ grib , "X2" ,
430+ int (x_coord .points [- 1 ] / _TRANSVERSE_MERCATOR_DEGREES_UNITS ))
431+
432+ # Lat and lon of reference point are measured in millionths of a degree.
433+ gribapi .grib_set (grib , "latitudeOfReferencePoint" ,
434+ cs .latitude_of_projection_origin / _DEFAULT_DEGREES_UNITS )
435+ gribapi .grib_set (grib , "longitudeOfReferencePoint" ,
436+ cs .longitude_of_central_meridian / _DEFAULT_DEGREES_UNITS )
437+
438+ # False easting and false northing are measured in units of (10^-2)m.
439+ gribapi .grib_set (grib , "XR" ,
440+ cs .false_easting / _TRANSVERSE_MERCATOR_DEGREES_UNITS )
441+ gribapi .grib_set (grib , "YR" ,
442+ cs .false_northing / _TRANSVERSE_MERCATOR_DEGREES_UNITS )
443+
444+ # GRIBAPI expects a signed int for scaleFactorAtReferencePoint
445+ # but it should accept a float, so work around this.
446+ # See https://software.ecmwf.int/issues/browse/SUP-1100
447+ value = cs .scale_factor_at_central_meridian
448+ key_type = gribapi .grib_get_native_type (grib ,
449+ "scaleFactorAtReferencePoint" )
450+ if key_type is not float :
451+ value = fixup_float32_as_int32 (value )
452+ gribapi .grib_set (grib , "scaleFactorAtReferencePoint" , value )
453+
454+
367455def grid_definition_section (cube , grib ):
368456 """
369457 Set keys within the grid definition section of the provided grib message,
@@ -375,22 +463,26 @@ def grid_definition_section(cube, grib):
375463 cs = x_coord .coord_system # N.B. already checked same cs for x and y.
376464 regular_x_and_y = is_regular (x_coord ) and is_regular (y_coord )
377465
378- if isinstance (cs , iris . coord_systems . GeogCS ):
466+ if isinstance (cs , GeogCS ):
379467 if not regular_x_and_y :
380468 raise iris .exceptions .TranslationError (
381469 'Saving an irregular latlon grid to GRIB (PDT3.4) is not '
382470 'yet supported.' )
383471
384472 grid_definition_template_0 (cube , grib )
385473
386- elif isinstance (cs , iris . coord_systems . RotatedGeogCS ):
474+ elif isinstance (cs , RotatedGeogCS ):
387475 # Rotated coordinate system cases.
388476 # Choose between GDT 3.1 and 3.5 according to coordinate regularity.
389477 if regular_x_and_y :
390478 grid_definition_template_1 (cube , grib )
391479 else :
392480 grid_definition_template_5 (cube , grib )
393481
482+ elif isinstance (cs , TransverseMercator ):
483+ # Transverse Mercator coordinate system (template 3.12).
484+ grid_definition_template_12 (cube , grib )
485+
394486 else :
395487 raise ValueError ('Grib saving is not supported for coordinate system: '
396488 '{:s}' .format (cs ))
0 commit comments