def _test_rotated( self, grid_north_pole_latitude=90, grid_north_pole_longitude=0, north_pole_grid_longitude=0, ): cs = ics.RotatedGeogCS( grid_north_pole_latitude, grid_north_pole_longitude, north_pole_grid_longitude, ) glon = coords.AuxCoord([359, 1], "grid_longitude", units="degrees", coord_system=cs) glat = coords.AuxCoord([0, 0], "grid_latitude", units="degrees", coord_system=cs) expected_path = Path([[-1, 0], [1, 0]], [Path.MOVETO, Path.LINETO]) plt.figure() lines = iplt.plot(glon, glat) # Matplotlib won't immediately set up the correct transform to allow us # to compare paths. Calling set_global(), which calls set_xlim() and # set_ylim(), will trigger Matplotlib to set up the transform. ax = plt.gca() ax.set_global() crs = cs.as_cartopy_crs() self.check_paths(expected_path, crs, lines, ax)
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 _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_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 _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")