@@ -214,6 +214,40 @@ def _hindcast_fix(forecast_time):
214214 return forecast_time
215215
216216
217+ def fixup_float32_from_int32 (value ):
218+ """
219+ Workaround for use when reading an IEEE 32-bit floating-point value
220+ which the ECMWF GRIB API has erroneously treated as a 4-byte signed
221+ integer.
222+
223+ """
224+ # Convert from two's complement to sign-and-magnitude.
225+ # NB. The bit patterns 0x00000000 and 0x80000000 will both be
226+ # returned by the ECMWF GRIB API as an integer 0. Because they
227+ # correspond to positive and negative zero respectively it is safe
228+ # to treat an integer 0 as a positive zero.
229+ if value < 0 :
230+ value = 0x80000000 - value
231+ value_as_uint32 = np .array (value , dtype = 'u4' )
232+ value_as_float32 = value_as_uint32 .view (dtype = 'f4' )
233+ return float (value_as_float32 )
234+
235+
236+ def fixup_int32_from_uint32 (value ):
237+ """
238+ Workaround for use when reading a signed, 4-byte integer which the
239+ ECMWF GRIB API has erroneously treated as an unsigned, 4-byte
240+ integer.
241+
242+ NB. This workaround is safe to use with values which are already
243+ treated as signed, 4-byte integers.
244+
245+ """
246+ if value >= 0x80000000 :
247+ value = 0x80000000 - value
248+ return value
249+
250+
217251###############################################################################
218252#
219253# Identification Section 1
@@ -644,6 +678,83 @@ def grid_definition_template_5(section, metadata):
644678 'grid_latitude' , 'grid_longitude' , cs )
645679
646680
681+ def grid_definition_template_12 (section , metadata ):
682+ """
683+ Translate template representing transverse Mercator.
684+
685+ Updates the metadata in-place with the translations.
686+
687+ Args:
688+
689+ * section:
690+ Dictionary of coded key/value pairs from section 3 of the message.
691+
692+ * metadata:
693+ :class:`collections.OrderedDict` of metadata.
694+
695+ """
696+ major , minor , radius = ellipsoid_geometry (section )
697+ geog_cs = ellipsoid (section ['shapeOfTheEarth' ], major , minor , radius )
698+
699+ lat = section ['latitudeOfReferencePoint' ] * _GRID_ACCURACY_IN_DEGREES
700+ lon = section ['longitudeOfReferencePoint' ] * _GRID_ACCURACY_IN_DEGREES
701+ scale = section ['scaleFactorAtReferencePoint' ]
702+ # Catch bug in ECMWF GRIB API (present at 1.12.1) where the scale
703+ # is treated as a signed, 4-byte integer.
704+ if isinstance (scale , int ):
705+ scale = fixup_float32_from_int32 (scale )
706+ CM_TO_M = 0.01
707+ easting = section ['XR' ] * CM_TO_M
708+ northing = section ['YR' ] * CM_TO_M
709+ cs = icoord_systems .TransverseMercator (lat , lon , easting , northing ,
710+ scale , geog_cs )
711+
712+ # Deal with bug in ECMWF GRIB API (present at 1.12.1) where these
713+ # values are treated as unsigned, 4-byte integers.
714+ x1 = fixup_int32_from_uint32 (section ['x1' ])
715+ y1 = fixup_int32_from_uint32 (section ['y1' ])
716+ x2 = fixup_int32_from_uint32 (section ['x2' ])
717+ y2 = fixup_int32_from_uint32 (section ['y2' ])
718+
719+ # Rather unhelpfully this grid definition template seems to be
720+ # overspecified, and thus open to inconsistency.
721+ last_x = x1 + (section ['Ni' ] - 1 ) * section ['Di' ]
722+ last_y = y1 + (section ['Nj' ] - 1 ) * section ['Dj' ]
723+ if (last_x != x2 or last_y != y2 ):
724+ raise TranslationError ('Inconsistent grid definition' )
725+
726+ x1 = x1 * CM_TO_M
727+ dx = section ['Di' ] * CM_TO_M
728+ x_points = x1 + np .arange (section ['Ni' ]) * dx
729+ y1 = y1 * CM_TO_M
730+ dy = section ['Dj' ] * CM_TO_M
731+ y_points = y1 + np .arange (section ['Nj' ]) * dy
732+
733+ # This has only been tested with +x/+y scanning, so raise an error
734+ # for other permutations.
735+ scan = scanning_mode (section ['scanningMode' ])
736+ if scan .i_negative :
737+ raise TranslationError ('Unsupported -x scanning' )
738+ if not scan .j_positive :
739+ raise TranslationError ('Unsupported -y scanning' )
740+
741+ # Create the X and Y coordinates.
742+ y_coord = DimCoord (y_points , 'projection_y_coordinate' , units = 'm' ,
743+ coord_system = cs )
744+ x_coord = DimCoord (x_points , 'projection_x_coordinate' , units = 'm' ,
745+ coord_system = cs )
746+
747+ # Determine the lat/lon dimensions.
748+ y_dim , x_dim = 0 , 1
749+ scan = scanning_mode (section ['scanningMode' ])
750+ if scan .j_consecutive :
751+ y_dim , x_dim = 1 , 0
752+
753+ # Add the X and Y coordinates to the metadata dim coords.
754+ metadata ['dim_coords_and_dims' ].append ((y_coord , y_dim ))
755+ metadata ['dim_coords_and_dims' ].append ((x_coord , x_dim ))
756+
757+
647758def grid_definition_template_90 (section , metadata ):
648759 """
649760 Translate template representing space view.
@@ -792,6 +903,9 @@ def grid_definition_section(section, metadata):
792903 elif template == 5 :
793904 # Process variable resolution rotated latitude/longitude.
794905 grid_definition_template_5 (section , metadata )
906+ elif template == 12 :
907+ # Process transverse Mercator.
908+ grid_definition_template_12 (section , metadata )
795909 elif template == 90 :
796910 # Process space view.
797911 grid_definition_template_90 (section , metadata )
0 commit comments