def test_spherical_default_supported(self): cs_by_shape = { 0: icoord_systems.GeogCS(6367470), 6: icoord_systems.GeogCS(6371229) } for shape, expected in cs_by_shape.items(): result = ellipsoid(shape, MDI, MDI, MDI) self.assertEqual(result, expected)
def test_netcdf_save_gridmapping(self): # Test saving CF-netCDF from a cubelist with various grid mappings. c1 = self.cubell c2 = self.cubell.copy() c3 = self.cubell.copy() coord_system = icoord_systems.GeogCS(6371229) coord_system2 = icoord_systems.GeogCS(6371228) coord_system3 = icoord_systems.RotatedGeogCS(30, 30) c1.add_dim_coord( iris.coords.DimCoord(np.arange(1, 3), 'latitude', long_name='1', units='degrees', coord_system=coord_system), 1) c1.add_dim_coord( iris.coords.DimCoord(np.arange(1, 3), 'longitude', long_name='1', units='degrees', coord_system=coord_system), 0) c2.add_dim_coord( iris.coords.DimCoord(np.arange(1, 3), 'latitude', long_name='2', units='degrees', coord_system=coord_system2), 1) c2.add_dim_coord( iris.coords.DimCoord(np.arange(1, 3), 'longitude', long_name='2', units='degrees', coord_system=coord_system2), 0) c3.add_dim_coord( iris.coords.DimCoord(np.arange(1, 3), 'grid_latitude', long_name='3', units='degrees', coord_system=coord_system3), 1) c3.add_dim_coord( iris.coords.DimCoord(np.arange(1, 3), 'grid_longitude', long_name='3', units='degrees', coord_system=coord_system3), 0) cubes = iris.cube.CubeList([c1, c2, c3]) with self.temp_filename(suffix='.nc') as file_out: iris.save(cubes, file_out) # Check the netCDF file against CDL expected output. self.assertCDL(file_out, ('netcdf', 'netcdf_save_gridmapmulti.cdl'))
def test_lat_lon_major_minor(self): major = 63781370 minor = 63567523 self.grid.semi_major_axis = major self.grid.semi_minor_axis = minor crs = pyke_rules.build_coordinate_system(self.grid) self.assertEqual(crs, icoord_systems.GeogCS(major, minor))
def _cube_data(data): """Returns a cube given a list of lat lon information.""" cube = iris.cube.Cube(data) lat_lon_coord_system = icoord_systems.GeogCS(6371229) step = 4.0 start = step / 2 count = 90 pts = start + numpy.arange(count, dtype=numpy.float32) * step lon_coord = icoords.DimCoord(pts, standard_name='longitude', units='degrees', coord_system=lat_lon_coord_system, circular=True) lon_coord.guess_bounds() start = -90 step = 4.0 count = 45 pts = start + numpy.arange(count, dtype=numpy.float32) * step lat_coord = icoords.DimCoord(pts, standard_name='latitude', units='degrees', coord_system=lat_lon_coord_system) lat_coord.guess_bounds() cube.add_dim_coord(lat_coord, 0) cube.add_dim_coord(lon_coord, 1) return cube
def test_load_tmerc_grid_with_projection_origin(self): # Test loading a single CF-netCDF file with a transverse Mercator # grid_mapping that uses longitude_of_projection_origin and # scale_factor_at_projection_origin instead of # longitude_of_central_meridian and scale_factor_at_central_meridian. cube = iris.load_cube( tests.get_data_path(( "NetCDF", "transverse_mercator", "projection_origin_attributes.nc", ))) expected = icoord_systems.TransverseMercator( latitude_of_projection_origin=49.0, longitude_of_central_meridian=-2.0, false_easting=400000.0, false_northing=-100000.0, scale_factor_at_central_meridian=0.9996012717, ellipsoid=icoord_systems.GeogCS(semi_major_axis=6377563.396, semi_minor_axis=6356256.91), ) self.assertEqual( cube.coord("projection_x_coordinate").coord_system, expected) self.assertEqual( cube.coord("projection_y_coordinate").coord_system, expected)
def NAME_to_cube(filenames, callback): """Returns a generator of cubes given a list of filenames and a callback.""" for filename in filenames: header, column_headings, data_arrays = load_NAME_III(filename) for i, data_array in enumerate(data_arrays): # turn the dictionary of column headers with a list of header information for each field into a dictionary of # headers for just this field. Ignore the first 4 columns of grid position (data was located with the data array). field_headings = dict([(k, v[i + 4]) for k, v in column_headings.iteritems()]) # make an cube cube = iris.cube.Cube(data_array) # define the name and unit name = ('%s %s' % (field_headings['species'], field_headings['quantity'])).upper().replace(' ', '_') cube.rename(name) # Some units are badly encoded in the file, fix this by putting a space in between. (if gs is not found, then the # string will be returned unchanged) cube.units = field_headings['unit'].replace('gs', 'g s') # define and add the singular coordinates of the field (flight level, time etc.) cube.add_aux_coord(icoords.AuxCoord(field_headings['z_level'], long_name='flight_level', units='1')) # define the time unit and use it to serialise the datetime for the time coordinate time_unit = iris.unit.Unit('hours since epoch', calendar=iris.unit.CALENDAR_GREGORIAN) time_coord = icoords.AuxCoord(time_unit.date2num(field_headings['time']), standard_name='time', units=time_unit) cube.add_aux_coord(time_coord) # build a coordinate system which can be referenced by latitude and longitude coordinates lat_lon_coord_system = icoord_systems.GeogCS(6371229) # build regular latitude and longitude coordinates which have bounds start = header['X grid origin'] + header['X grid resolution'] step = header['X grid resolution'] count = header['X grid size'] pts = start + np.arange(count, dtype=np.float32) * step lon_coord = icoords.DimCoord(pts, standard_name='longitude', units='degrees', coord_system=lat_lon_coord_system) lon_coord.guess_bounds() start = header['Y grid origin'] + header['Y grid resolution'] step = header['Y grid resolution'] count = header['Y grid size'] pts = start + np.arange(count, dtype=np.float32) * step lat_coord = icoords.DimCoord(pts, standard_name='latitude', units='degrees', coord_system=lat_lon_coord_system) lat_coord.guess_bounds() # add the latitude and longitude coordinates to the cube, with mappings to data dimensions cube.add_dim_coord(lat_coord, 0) cube.add_dim_coord(lon_coord, 1) # implement standard iris callback capability. Although callbacks are not used in this example, the standard # mechanism for a custom loader to implement a callback is shown: cube = iris.io.run_callback(callback, cube, [header, field_headings, data_array], filename) # yield the cube created (the loop will continue when the next() element is requested) yield cube
def test_oblate_shape_3_7(self): for shape in [3, 7]: major, minor = 1, 10 scale = 1 result = ellipsoid(shape, major, minor, MDI) if shape == 3: # Convert km to m. scale = 1000 expected = icoord_systems.GeogCS(major * scale, minor * scale) self.assertEqual(result, expected)
def _compute_extra_keys(self): """Compute our extra keys.""" global unknown_string self.extra_keys = {} forecastTime = self.startStep # regular or rotated grid? try: longitudeOfSouthernPoleInDegrees = \ self.longitudeOfSouthernPoleInDegrees latitudeOfSouthernPoleInDegrees = \ self.latitudeOfSouthernPoleInDegrees except AttributeError: longitudeOfSouthernPoleInDegrees = 0.0 latitudeOfSouthernPoleInDegrees = 90.0 centre = gribapi.grib_get_string(self.grib_message, "centre") # default values self.extra_keys = { '_referenceDateTime': -1.0, '_phenomenonDateTime': -1.0, '_periodStartDateTime': -1.0, '_periodEndDateTime': -1.0, '_levelTypeName': unknown_string, '_levelTypeUnits': unknown_string, '_firstLevelTypeName': unknown_string, '_firstLevelTypeUnits': unknown_string, '_firstLevel': -1.0, '_secondLevelTypeName': unknown_string, '_secondLevel': -1.0, '_originatingCentre': unknown_string, '_forecastTime': None, '_forecastTimeUnit': unknown_string, '_coord_system': None, '_x_circular': False, '_x_coord_name': unknown_string, '_y_coord_name': unknown_string, # These are here to avoid repetition in the rules # files, and reduce the very long line lengths. '_x_points': None, '_y_points': None, '_cf_data': None } # cf phenomenon translation # Get centre code (N.B. self.centre has default type = string) centre_number = gribapi.grib_get_long(self.grib_message, "centre") # Look for a known grib1-to-cf translation (or None). cf_data = gptx.grib1_phenom_to_cf_info( table2_version=self.table2Version, centre_number=centre_number, param_number=self.indicatorOfParameter) self.extra_keys['_cf_data'] = cf_data # reference date self.extra_keys['_referenceDateTime'] = \ datetime.datetime(int(self.year), int(self.month), int(self.day), int(self.hour), int(self.minute)) # forecast time with workarounds self.extra_keys['_forecastTime'] = forecastTime # verification date processingDone = self._get_processing_done() # time processed? if processingDone.startswith("time"): validityDate = str(self.validityDate) validityTime = "{:04}".format(int(self.validityTime)) endYear = int(validityDate[:4]) endMonth = int(validityDate[4:6]) endDay = int(validityDate[6:8]) endHour = int(validityTime[:2]) endMinute = int(validityTime[2:4]) # fixed forecastTime in hours self.extra_keys['_periodStartDateTime'] = \ (self.extra_keys['_referenceDateTime'] + datetime.timedelta(hours=int(forecastTime))) self.extra_keys['_periodEndDateTime'] = \ datetime.datetime(endYear, endMonth, endDay, endHour, endMinute) else: self.extra_keys['_phenomenonDateTime'] = \ self._get_verification_date() # originating centre # TODO #574 Expand to include sub-centre self.extra_keys['_originatingCentre'] = CENTRE_TITLES.get( centre, "unknown centre %s" % centre) # forecast time unit as a cm string # TODO #575 Do we want PP or GRIB style forecast delta? self.extra_keys['_forecastTimeUnit'] = self._timeunit_string() # shape of the earth # pre-defined sphere if self.shapeOfTheEarth == 0: geoid = coord_systems.GeogCS(semi_major_axis=6367470) # custom sphere elif self.shapeOfTheEarth == 1: geoid = coord_systems.GeogCS( self.scaledValueOfRadiusOfSphericalEarth * 10**-self.scaleFactorOfRadiusOfSphericalEarth) # IAU65 oblate sphere elif self.shapeOfTheEarth == 2: geoid = coord_systems.GeogCS(6378160, inverse_flattening=297.0) # custom oblate spheroid (km) elif self.shapeOfTheEarth == 3: geoid = coord_systems.GeogCS( semi_major_axis=self.scaledValueOfEarthMajorAxis * 10**-self.scaleFactorOfEarthMajorAxis * 1000., semi_minor_axis=self.scaledValueOfEarthMinorAxis * 10**-self.scaleFactorOfEarthMinorAxis * 1000.) # IAG-GRS80 oblate spheroid elif self.shapeOfTheEarth == 4: geoid = coord_systems.GeogCS(6378137, None, 298.257222101) # WGS84 elif self.shapeOfTheEarth == 5: geoid = \ coord_systems.GeogCS(6378137, inverse_flattening=298.257223563) # pre-defined sphere elif self.shapeOfTheEarth == 6: geoid = coord_systems.GeogCS(6371229) # custom oblate spheroid (m) elif self.shapeOfTheEarth == 7: geoid = coord_systems.GeogCS( semi_major_axis=self.scaledValueOfEarthMajorAxis * 10**-self.scaleFactorOfEarthMajorAxis, semi_minor_axis=self.scaledValueOfEarthMinorAxis * 10**-self.scaleFactorOfEarthMinorAxis) elif self.shapeOfTheEarth == 8: raise ValueError("unhandled shape of earth : grib earth shape = 8") else: raise ValueError("undefined shape of earth") gridType = gribapi.grib_get_string(self.grib_message, "gridType") if gridType in [ "regular_ll", "regular_gg", "reduced_ll", "reduced_gg" ]: self.extra_keys['_x_coord_name'] = "longitude" self.extra_keys['_y_coord_name'] = "latitude" self.extra_keys['_coord_system'] = geoid elif gridType == 'rotated_ll': # TODO: Confirm the translation from angleOfRotation to # north_pole_lon (usually 0 for both) self.extra_keys['_x_coord_name'] = "grid_longitude" self.extra_keys['_y_coord_name'] = "grid_latitude" southPoleLon = longitudeOfSouthernPoleInDegrees southPoleLat = latitudeOfSouthernPoleInDegrees self.extra_keys['_coord_system'] = \ coord_systems.RotatedGeogCS( -southPoleLat, math.fmod(southPoleLon + 180.0, 360.0), self.angleOfRotation, geoid) elif gridType == 'polar_stereographic': self.extra_keys['_x_coord_name'] = "projection_x_coordinate" self.extra_keys['_y_coord_name'] = "projection_y_coordinate" if self.projectionCentreFlag == 0: pole_lat = 90 elif self.projectionCentreFlag == 1: pole_lat = -90 else: raise TranslationError("Unhandled projectionCentreFlag") # Note: I think the grib api defaults LaDInDegrees to 60 for grib1. self.extra_keys['_coord_system'] = \ coord_systems.Stereographic( pole_lat, self.orientationOfTheGridInDegrees, 0, 0, self.LaDInDegrees, ellipsoid=geoid) elif gridType == 'lambert': self.extra_keys['_x_coord_name'] = "projection_x_coordinate" self.extra_keys['_y_coord_name'] = "projection_y_coordinate" flag_name = "projectionCenterFlag" if getattr(self, flag_name) == 0: pole_lat = 90 elif getattr(self, flag_name) == 1: pole_lat = -90 else: raise TranslationError("Unhandled projectionCentreFlag") LambertConformal = coord_systems.LambertConformal self.extra_keys['_coord_system'] = LambertConformal( self.LaDInDegrees, self.LoVInDegrees, 0, 0, secant_latitudes=(self.Latin1InDegrees, self.Latin2InDegrees), ellipsoid=geoid) else: raise TranslationError("unhandled grid type: {}".format(gridType)) if gridType in ["regular_ll", "rotated_ll"]: self._regular_longitude_common() j_step = self.jDirectionIncrementInDegrees if not self.jScansPositively: j_step = -j_step self._y_points = (np.arange(self.Nj, dtype=np.float64) * j_step + self.latitudeOfFirstGridPointInDegrees) elif gridType in ['regular_gg']: # longitude coordinate is straight-forward self._regular_longitude_common() # get the distinct latitudes, and make sure they are sorted # (south-to-north) and then put them in the right direction # depending on the scan direction latitude_points = gribapi.grib_get_double_array( self.grib_message, 'distinctLatitudes').astype(np.float64) latitude_points.sort() if not self.jScansPositively: # we require latitudes north-to-south self._y_points = latitude_points[::-1] else: self._y_points = latitude_points elif gridType in ["polar_stereographic", "lambert"]: # convert the starting latlon into meters cartopy_crs = self.extra_keys['_coord_system'].as_cartopy_crs() x1, y1 = cartopy_crs.transform_point( self.longitudeOfFirstGridPointInDegrees, self.latitudeOfFirstGridPointInDegrees, ccrs.Geodetic()) if not np.all(np.isfinite([x1, y1])): raise TranslationError("Could not determine the first latitude" " and/or longitude grid point.") self._x_points = x1 + self.DxInMetres * np.arange(self.Nx, dtype=np.float64) self._y_points = y1 + self.DyInMetres * np.arange(self.Ny, dtype=np.float64) elif gridType in ["reduced_ll", "reduced_gg"]: self._x_points = self.longitudes self._y_points = self.latitudes else: raise TranslationError("unhandled grid type")
def test_lat_lon_earth_radius(self): earth_radius = 63700000 self.grid.earth_radius = earth_radius crs = pyke_rules.build_coordinate_system(self.grid) self.assertEqual(crs, icoord_systems.GeogCS(earth_radius))
def NAME_to_cube(filenames, callback): """ Returns a generator of cubes given a list of filenames and a callback. """ for filename in filenames: header, column_headings, data_arrays = load_NAME_III(filename) for i, data_array in enumerate(data_arrays): # turn the dictionary of column headers with a list of header # information for each field into a dictionary of headers for just # this field. Ignore the first 4 columns of grid position (data was # located with the data array). field_headings = dict( (k, v[i + 4]) for k, v in column_headings.items()) # make an cube cube = iris.cube.Cube(data_array) # define the name and unit name = "%s %s" % ( field_headings["species"], field_headings["quantity"], ) name = name.upper().replace(" ", "_") cube.rename(name) # Some units are badly encoded in the file, fix this by putting a # space in between. (if gs is not found, then the string will be # returned unchanged) cube.units = field_headings["unit"].replace("gs", "g s") # define and add the singular coordinates of the field (flight # level, time etc.) cube.add_aux_coord( icoords.AuxCoord( field_headings["z_level"], long_name="flight_level", units="1", )) # define the time unit and use it to serialise the datetime for the # time coordinate time_unit = Unit("hours since epoch", calendar=CALENDAR_GREGORIAN) time_coord = icoords.AuxCoord( time_unit.date2num(field_headings["time"]), standard_name="time", units=time_unit, ) cube.add_aux_coord(time_coord) # build a coordinate system which can be referenced by latitude and # longitude coordinates lat_lon_coord_system = icoord_systems.GeogCS(6371229) # build regular latitude and longitude coordinates which have # bounds start = header["X grid origin"] + header["X grid resolution"] step = header["X grid resolution"] count = header["X grid size"] pts = start + np.arange(count, dtype=np.float32) * step lon_coord = icoords.DimCoord( pts, standard_name="longitude", units="degrees", coord_system=lat_lon_coord_system, ) lon_coord.guess_bounds() start = header["Y grid origin"] + header["Y grid resolution"] step = header["Y grid resolution"] count = header["Y grid size"] pts = start + np.arange(count, dtype=np.float32) * step lat_coord = icoords.DimCoord( pts, standard_name="latitude", units="degrees", coord_system=lat_lon_coord_system, ) lat_coord.guess_bounds() # add the latitude and longitude coordinates to the cube, with # mappings to data dimensions cube.add_dim_coord(lat_coord, 0) cube.add_dim_coord(lon_coord, 1) # implement standard iris callback capability. Although callbacks # are not used in this example, the standard mechanism for a custom # loader to implement a callback is shown: cube = iris.io.run_callback(callback, cube, [header, field_headings, data_array], filename) # yield the cube created (the loop will continue when the next() # element is requested) yield cube
def setUp(self): plt.figure() self.geog_cs = ics.GeogCS(6371229.0) self.plate_carree = self.geog_cs.as_cartopy_projection()
def test_netcdf_save_gridmapping(self): # Test saving CF-netCDF from a cubelist with various grid mappings. c1 = self.cubell c2 = self.cubell.copy() c3 = self.cubell.copy() coord_system = icoord_systems.GeogCS(6371229) coord_system2 = icoord_systems.GeogCS(6371228) coord_system3 = icoord_systems.RotatedGeogCS(30, 30) c1.add_dim_coord( iris.coords.DimCoord( np.arange(1, 3), "latitude", long_name="1", units="degrees", coord_system=coord_system, ), 1, ) c1.add_dim_coord( iris.coords.DimCoord( np.arange(1, 3), "longitude", long_name="1", units="degrees", coord_system=coord_system, ), 0, ) c2.add_dim_coord( iris.coords.DimCoord( np.arange(1, 3), "latitude", long_name="2", units="degrees", coord_system=coord_system2, ), 1, ) c2.add_dim_coord( iris.coords.DimCoord( np.arange(1, 3), "longitude", long_name="2", units="degrees", coord_system=coord_system2, ), 0, ) c3.add_dim_coord( iris.coords.DimCoord( np.arange(1, 3), "grid_latitude", long_name="3", units="degrees", coord_system=coord_system3, ), 1, ) c3.add_dim_coord( iris.coords.DimCoord( np.arange(1, 3), "grid_longitude", long_name="3", units="degrees", coord_system=coord_system3, ), 0, ) cubes = iris.cube.CubeList([c1, c2, c3]) with self.temp_filename(suffix=".nc") as file_out: iris.save(cubes, file_out) # Check the netCDF file against CDL expected output. self.assertCDL(file_out, ("netcdf", "netcdf_save_gridmapmulti.cdl"))
def test_spherical_shape_1(self): shape = 1 radius = 10 result = ellipsoid(shape, MDI, MDI, radius) expected = icoord_systems.GeogCS(radius) self.assertEqual(result, expected)
if short_name == "2t": color_plot_dist = values_range_for_plot / 20 bounds = np.arange(total_min, total_max + color_plot_dist, color_plot_dist) color_bar_dist = values_range_for_plot / 10 cmap = plt.get_cmap(colormap) time_after_init = start_time_since_init + i * plot_interval if surface_bool == 0: print("plotting " + short_name + " at level " + str(level) + " for t - t_init = " + str(time_after_init) + " s ...") if surface_bool == 1: print("plotting " + short_name + " for t - t_init = " + str(time_after_init) + " s ...") if (projection == "Orthographic"): fig = plt.figure(figsize=(fig_size, fig_size)) coord_sys = cs.GeogCS(6371229) if (projection == "Mollweide"): fig = plt.figure(figsize=(fig_size, 0.5 * fig_size)) coord_sys = cs.GeogCS(6371229) if (projection == "EckertIII"): fig = plt.figure(figsize=(fig_size, 0.5 * fig_size)) coord_sys = cs.GeogCS(6371229) if (projection == "Orthographic"): fig = plt.figure(figsize=(fig_size, fig_size)) ax = plt.axes(projection=ccrs.Orthographic(central_latitude=0, central_longitude=0)) coord_sys = cs.GeogCS(6371229) if (projection == "Mollweide"): fig = plt.figure(figsize=(fig_size, fig_size)) ax = plt.axes(projection=ccrs.Mollweide()) coord_sys = cs.GeogCS(6371229)
def _compute_extra_keys(self): """Compute our extra keys.""" global unknown_string self.extra_keys = {} # work out stuff based on these values from the message edition = self.edition # time-processed forcast time is from reference time to start of period if edition == 2: forecastTime = self.forecastTime uft = np.uint32(forecastTime) BILL = 2**30 # Workaround grib api's assumption that forecast time is positive. # Handles correctly encoded -ve forecast times up to one -1 billion. if hindcast_workaround: if 2 * BILL < uft < 3 * BILL: msg = "Re-interpreting negative forecastTime from " \ + str(forecastTime) forecastTime = -(uft - 2 * BILL) msg += " to " + str(forecastTime) warnings.warn(msg) else: forecastTime = self.startStep #regular or rotated grid? try: longitudeOfSouthernPoleInDegrees = self.longitudeOfSouthernPoleInDegrees latitudeOfSouthernPoleInDegrees = self.latitudeOfSouthernPoleInDegrees except AttributeError: longitudeOfSouthernPoleInDegrees = 0.0 latitudeOfSouthernPoleInDegrees = 90.0 centre = gribapi.grib_get_string(self.grib_message, "centre") #default values self.extra_keys = { '_referenceDateTime': -1.0, '_phenomenonDateTime': -1.0, '_periodStartDateTime': -1.0, '_periodEndDateTime': -1.0, '_levelTypeName': unknown_string, '_levelTypeUnits': unknown_string, '_firstLevelTypeName': unknown_string, '_firstLevelTypeUnits': unknown_string, '_firstLevel': -1.0, '_secondLevelTypeName': unknown_string, '_secondLevel': -1.0, '_originatingCentre': unknown_string, '_forecastTime': None, '_forecastTimeUnit': unknown_string, '_coord_system': None, '_x_circular': False, '_x_coord_name': unknown_string, '_y_coord_name': unknown_string, # These are here to avoid repetition in the rules files, # and reduce the very long line lengths. '_x_points': None, '_y_points': None, '_cf_data': None } # cf phenomenon translation if edition == 1: # Get centre code (N.B. self.centre has default type = string) centre_number = gribapi.grib_get_long(self.grib_message, "centre") # Look for a known grib1-to-cf translation (or None). cf_data = gptx.grib1_phenom_to_cf_info( table2_version=self.table2Version, centre_number=centre_number, param_number=self.indicatorOfParameter) self.extra_keys['_cf_data'] = cf_data elif edition == 2: # Don't attempt to interpret params if 'master tables version' is # 255, as local params may then have same codes as standard ones. if self.tablesVersion != 255: # Look for a known grib2-to-cf translation (or None). cf_data = gptx.grib2_phenom_to_cf_info( param_discipline=self.discipline, param_category=self.parameterCategory, param_number=self.parameterNumber) self.extra_keys['_cf_data'] = cf_data #reference date self.extra_keys['_referenceDateTime'] = \ datetime.datetime(int(self.year), int(self.month), int(self.day), int(self.hour), int(self.minute)) # forecast time with workarounds self.extra_keys['_forecastTime'] = forecastTime #verification date processingDone = self._get_processing_done() #time processed? if processingDone.startswith("time"): if self.edition == 1: validityDate = str(self.validityDate) validityTime = "{:04}".format(int(self.validityTime)) endYear = int(validityDate[:4]) endMonth = int(validityDate[4:6]) endDay = int(validityDate[6:8]) endHour = int(validityTime[:2]) endMinute = int(validityTime[2:4]) elif self.edition == 2: endYear = self.yearOfEndOfOverallTimeInterval endMonth = self.monthOfEndOfOverallTimeInterval endDay = self.dayOfEndOfOverallTimeInterval endHour = self.hourOfEndOfOverallTimeInterval endMinute = self.minuteOfEndOfOverallTimeInterval # fixed forecastTime in hours self.extra_keys['_periodStartDateTime'] = \ (self.extra_keys['_referenceDateTime'] + datetime.timedelta(hours=int(forecastTime))) self.extra_keys['_periodEndDateTime'] = \ datetime.datetime(endYear, endMonth, endDay, endHour, endMinute) else: self.extra_keys[ '_phenomenonDateTime'] = self._get_verification_date() #originating centre #TODO #574 Expand to include sub-centre self.extra_keys['_originatingCentre'] = CENTRE_TITLES.get( centre, "unknown centre %s" % centre) #forecast time unit as a cm string #TODO #575 Do we want PP or GRIB style forecast delta? self.extra_keys['_forecastTimeUnit'] = self._timeunit_string() #shape of the earth #pre-defined sphere if self.shapeOfTheEarth == 0: geoid = coord_systems.GeogCS(semi_major_axis=6367470) #custom sphere elif self.shapeOfTheEarth == 1: geoid = \ coord_systems.GeogCS(self.scaledValueOfRadiusOfSphericalEarth * \ self.scaleFactorOfRadiusOfSphericalEarth) #IAU65 oblate sphere elif self.shapeOfTheEarth == 2: geoid = coord_systems.GeogCS(6378160, inverse_flattening=297.0) #custom oblate spheroid (km) elif self.shapeOfTheEarth == 3: geoid = coord_systems.GeogCS( semi_major_axis=self.scaledValueOfEarthMajorAxis * self.scaleFactorOfEarthMajorAxis * 1000.0, semi_minor_axis=self.scaledValueOfEarthMinorAxis * self.scaleFactorOfEarthMinorAxis * 1000.0) #IAG-GRS80 oblate spheroid elif self.shapeOfTheEarth == 4: geoid = coord_systems.GeogCS(6378137, None, 298.257222101) #WGS84 elif self.shapeOfTheEarth == 5: geoid = \ coord_systems.GeogCS(6378137, inverse_flattening=298.257223563) #pre-defined sphere elif self.shapeOfTheEarth == 6: geoid = coord_systems.GeogCS(6371229) #custom oblate spheroid (m) elif self.shapeOfTheEarth == 7: geoid = coord_systems.GeogCS( semi_major_axis=self.scaledValueOfEarthMajorAxis * self.scaleFactorOfEarthMajorAxis, semi_minor_axis=self.scaledValueOfEarthMinorAxis * self.scaleFactorOfEarthMinorAxis) elif self.shapeOfTheEarth == 8: raise ValueError("unhandled shape of earth : grib earth shape = 8") else: raise ValueError("undefined shape of earth") gridType = gribapi.grib_get_string(self.grib_message, "gridType") if gridType == "regular_ll": self.extra_keys['_x_coord_name'] = "longitude" self.extra_keys['_y_coord_name'] = "latitude" self.extra_keys['_coord_system'] = geoid elif gridType == 'rotated_ll': # TODO: Confirm the translation from angleOfRotation to # north_pole_lon (usually 0 for both) self.extra_keys['_x_coord_name'] = "grid_longitude" self.extra_keys['_y_coord_name'] = "grid_latitude" southPoleLon = longitudeOfSouthernPoleInDegrees southPoleLat = latitudeOfSouthernPoleInDegrees self.extra_keys['_coord_system'] = \ iris.coord_systems.RotatedGeogCS( -southPoleLat, math.fmod(southPoleLon + 180.0, 360.0), self.angleOfRotation, geoid) elif gridType == 'polar_stereographic': self.extra_keys['_x_coord_name'] = "projection_x_coordinate" self.extra_keys['_y_coord_name'] = "projection_y_coordinate" if self.projectionCentreFlag == 0: pole_lat = 90 elif self.projectionCentreFlag == 1: pole_lat = -90 else: raise TranslationError("Unhandled projectionCentreFlag") # Note: I think the grib api defaults LaDInDegrees to 60 for grib1. self.extra_keys['_coord_system'] = \ iris.coord_systems.Stereographic( pole_lat, self.orientationOfTheGridInDegrees, 0, 0, self.LaDInDegrees, ellipsoid=geoid) else: raise TranslationError("unhandled grid type") # x and y points, and circularity if gridType in ["regular_ll", "rotated_ll"]: i_step = self.iDirectionIncrementInDegrees j_step = self.jDirectionIncrementInDegrees if self.iScansNegatively: i_step = -i_step if not self.jScansPositively: j_step = -j_step self._x_points = (np.arange(self.Ni, dtype=np.float64) * i_step + self.longitudeOfFirstGridPointInDegrees) self._y_points = (np.arange(self.Nj, dtype=np.float64) * j_step + self.latitudeOfFirstGridPointInDegrees) # circular x coord? if "longitude" in self.extra_keys['_x_coord_name'] and self.Ni > 1: # Is the gap from end to start smaller, # or about equal to the max step? points = self._x_points gap = 360.0 - abs(points[-1] - points[0]) max_step = abs(np.diff(points)).max() if gap <= max_step: self.extra_keys['_x_circular'] = True else: delta = 0.001 if abs(1.0 - gap / max_step) < delta: self.extra_keys['_x_circular'] = True elif gridType == "polar_stereographic": # convert the starting latlon into meters cartopy_crs = self.extra_keys['_coord_system'].as_cartopy_crs() x1, y1 = cartopy_crs.transform_point( self.longitudeOfFirstGridPointInDegrees, self.latitudeOfFirstGridPointInDegrees, cartopy.crs.Geodetic()) self._x_points = x1 + self.DxInMetres * np.arange(self.Nx, dtype=np.float64) self._y_points = y1 + self.DyInMetres * np.arange(self.Ny, dtype=np.float64) else: raise TranslationError("unhandled grid type")
def _compute_extra_keys(self): """Compute our extra keys.""" global unknown_string self.extra_keys = {} forecastTime = self.startStep # regular or rotated grid? try: longitudeOfSouthernPoleInDegrees = \ self.longitudeOfSouthernPoleInDegrees latitudeOfSouthernPoleInDegrees = \ self.latitudeOfSouthernPoleInDegrees except AttributeError: longitudeOfSouthernPoleInDegrees = 0.0 latitudeOfSouthernPoleInDegrees = 90.0 centre = gribapi.grib_get_string(self.grib_message, "centre") # default values self.extra_keys = {'_referenceDateTime': -1.0, '_phenomenonDateTime': -1.0, '_periodStartDateTime': -1.0, '_periodEndDateTime': -1.0, '_levelTypeName': unknown_string, '_levelTypeUnits': unknown_string, '_firstLevelTypeName': unknown_string, '_firstLevelTypeUnits': unknown_string, '_firstLevel': -1.0, '_secondLevelTypeName': unknown_string, '_secondLevel': -1.0, '_originatingCentre': unknown_string, '_forecastTime': None, '_forecastTimeUnit': unknown_string, '_coord_system': None, '_x_circular': False, '_x_coord_name': unknown_string, '_y_coord_name': unknown_string, # These are here to avoid repetition in the rules # files, and reduce the very long line lengths. '_x_points': None, '_y_points': None, '_cf_data': None} # cf phenomenon translation # Get centre code (N.B. self.centre has default type = string) centre_number = gribapi.grib_get_long(self.grib_message, "centre") # Look for a known grib1-to-cf translation (or None). cf_data = gptx.grib1_phenom_to_cf_info( table2_version=self.table2Version, centre_number=centre_number, param_number=self.indicatorOfParameter) self.extra_keys['_cf_data'] = cf_data # reference date self.extra_keys['_referenceDateTime'] = \ datetime.datetime(int(self.year), int(self.month), int(self.day), int(self.hour), int(self.minute)) # forecast time with workarounds self.extra_keys['_forecastTime'] = forecastTime # verification date processingDone = self._get_processing_done() # time processed? if processingDone.startswith("time"): validityDate = str(self.validityDate) validityTime = "{:04}".format(int(self.validityTime)) endYear = int(validityDate[:4]) endMonth = int(validityDate[4:6]) endDay = int(validityDate[6:8]) endHour = int(validityTime[:2]) endMinute = int(validityTime[2:4]) # fixed forecastTime in hours self.extra_keys['_periodStartDateTime'] = \ (self.extra_keys['_referenceDateTime'] + datetime.timedelta(hours=int(forecastTime))) self.extra_keys['_periodEndDateTime'] = \ datetime.datetime(endYear, endMonth, endDay, endHour, endMinute) else: self.extra_keys['_phenomenonDateTime'] = \ self._get_verification_date() # originating centre # TODO #574 Expand to include sub-centre self.extra_keys['_originatingCentre'] = CENTRE_TITLES.get( centre, "unknown centre %s" % centre) # forecast time unit as a cm string # TODO #575 Do we want PP or GRIB style forecast delta? self.extra_keys['_forecastTimeUnit'] = self._timeunit_string() # shape of the earth soe_code = self.shapeOfTheEarth # As this class is now *only* for GRIB1, 'shapeOfTheEarth' is not a # value read from the actual file : It is really a GRIB2 param, and # the value is merely what eccodes (gribapi) gives as the default. # This was always = 6, until eccodes 0.19, when it changed to 0. # See https://jira.ecmwf.int/browse/ECC-811 # The two represent different sized spherical earths. if soe_code not in (6, 0): raise ValueError('Unexpected shapeOfTheEarth value =', soe_code) soe_code = 6 # *FOR NOW* maintain the old behaviour (radius=6371229) in all cases, # for backwards compatibility. # However, this does not match the 'radiusOfTheEarth' default from the # gribapi so is probably incorrect (see above issue ECC-811). # So we may change this in future. if soe_code == 0: # New supposedly-correct default value, matches 'radiusOfTheEarth'. geoid = coord_systems.GeogCS(semi_major_axis=6367470) elif soe_code == 6: # Old value, does *not* match the 'radiusOfTheEarth' parameter. geoid = coord_systems.GeogCS(6371229) gridType = gribapi.grib_get_string(self.grib_message, "gridType") if gridType in ["regular_ll", "regular_gg", "reduced_ll", "reduced_gg"]: self.extra_keys['_x_coord_name'] = "longitude" self.extra_keys['_y_coord_name'] = "latitude" self.extra_keys['_coord_system'] = geoid elif gridType == 'rotated_ll': # TODO: Confirm the translation from angleOfRotation to # north_pole_lon (usually 0 for both) self.extra_keys['_x_coord_name'] = "grid_longitude" self.extra_keys['_y_coord_name'] = "grid_latitude" southPoleLon = longitudeOfSouthernPoleInDegrees southPoleLat = latitudeOfSouthernPoleInDegrees self.extra_keys['_coord_system'] = \ coord_systems.RotatedGeogCS( -southPoleLat, math.fmod(southPoleLon + 180.0, 360.0), self.angleOfRotation, geoid) elif gridType == 'polar_stereographic': self.extra_keys['_x_coord_name'] = "projection_x_coordinate" self.extra_keys['_y_coord_name'] = "projection_y_coordinate" if self.projectionCentreFlag == 0: pole_lat = 90 elif self.projectionCentreFlag == 1: pole_lat = -90 else: raise TranslationError("Unhandled projectionCentreFlag") # Note: I think the grib api defaults LaDInDegrees to 60 for grib1. self.extra_keys['_coord_system'] = \ coord_systems.Stereographic( pole_lat, self.orientationOfTheGridInDegrees, 0, 0, self.LaDInDegrees, ellipsoid=geoid) elif gridType == 'lambert': self.extra_keys['_x_coord_name'] = "projection_x_coordinate" self.extra_keys['_y_coord_name'] = "projection_y_coordinate" flag_name = "projectionCenterFlag" if getattr(self, flag_name) == 0: pole_lat = 90 elif getattr(self, flag_name) == 1: pole_lat = -90 else: raise TranslationError("Unhandled projectionCentreFlag") LambertConformal = coord_systems.LambertConformal self.extra_keys['_coord_system'] = LambertConformal( self.LaDInDegrees, self.LoVInDegrees, 0, 0, secant_latitudes=(self.Latin1InDegrees, self.Latin2InDegrees), ellipsoid=geoid) else: raise TranslationError("unhandled grid type: {}".format(gridType)) if gridType in ["regular_ll", "rotated_ll"]: self._regular_longitude_common() j_step = self.jDirectionIncrementInDegrees if not self.jScansPositively: j_step = -j_step self._y_points = (np.arange(self.Nj, dtype=np.float64) * j_step + self.latitudeOfFirstGridPointInDegrees) elif gridType in ['regular_gg']: # longitude coordinate is straight-forward self._regular_longitude_common() # get the distinct latitudes, and make sure they are sorted # (south-to-north) and then put them in the right direction # depending on the scan direction latitude_points = gribapi.grib_get_double_array( self.grib_message, 'distinctLatitudes').astype(np.float64) latitude_points.sort() if not self.jScansPositively: # we require latitudes north-to-south self._y_points = latitude_points[::-1] else: self._y_points = latitude_points elif gridType in ["polar_stereographic", "lambert"]: # convert the starting latlon into meters cartopy_crs = self.extra_keys['_coord_system'].as_cartopy_crs() x1, y1 = cartopy_crs.transform_point( self.longitudeOfFirstGridPointInDegrees, self.latitudeOfFirstGridPointInDegrees, ccrs.Geodetic()) if not np.all(np.isfinite([x1, y1])): raise TranslationError("Could not determine the first latitude" " and/or longitude grid point.") self._x_points = x1 + self.DxInMetres * np.arange(self.Nx, dtype=np.float64) self._y_points = y1 + self.DyInMetres * np.arange(self.Ny, dtype=np.float64) elif gridType in ["reduced_ll", "reduced_gg"]: self._x_points = self.longitudes self._y_points = self.latitudes else: raise TranslationError("unhandled grid type")