def _fetch_forecast_data_for_month(variable,year,month, stream='enda'): # Need two sets of forecast data - from the runs at 6 and 18 for start_hour in (6,18): local_file=_hourly_get_file_name(variable,year,month, fc_init=start_hour,stream=stream) if os.path.isfile(local_file): # Got this data already continue if not os.path.exists(os.path.dirname(local_file)): os.makedirs(os.path.dirname(local_file)) if stream=='oper': server = ecmwfapi.ECMWFDataServer() server.retrieve({ 'dataset' : 'era5', 'stream' : 'oper', 'type' : 'fc', 'levtype' : 'sfc', 'param' : _translate_for_file_names(variable), 'grid' : '0.25/0.25', 'time' : "%02d" % start_hour, 'step' : '0/to/18/by/1', 'date' : "%04d-%02d-%02d/to/%04d-%02d-%02d" % (year,month,1, year,month, calendar.monthrange(year,month)[1]), 'format' : 'netcdf', 'target' : local_file }) elif stream=='enda': server = ecmwfapi.ECMWFDataServer() server.retrieve({ "class" : "ea", "dataset" : "era5", 'date' : "%04d-%02d-%02d/to/%04d-%02d-%02d" % (year,month,1, year,month, calendar.monthrange(year,month)[1]), "expver" : "1", "levtype" : "sfc", "number" : "0/1/2/3/4/5/6/7/8/9", "param" : _translate_for_file_names(variable), "stream" : "enda", 'time' : "%02d" % start_hour, 'step' : '0/to/18/by/3', "type" : "fc", 'grid' : '0.5/0.5', 'format' : 'netcdf', "target" : local_file }) else: raise Exception("Unsupported stream %s" % stream)
def _fetch_analysis_data_for_month(variable,year,month, stream='enda'): local_file=_hourly_get_file_name(variable,year,month, stream=stream) if os.path.isfile(local_file): # Got this data already return if not os.path.exists(os.path.dirname(local_file)): os.makedirs(os.path.dirname(local_file)) if stream=='oper': grid='0.25/0.25' server = ecmwfapi.ECMWFDataServer() server.retrieve({ 'dataset' : 'era5', 'stream' : 'oper', 'type' : 'an', 'levtype' : 'sfc', 'param' : _translate_for_file_names(variable), 'grid' : '0.25/0.25', 'time' : '0/to/23/by/1', 'date' : "%04d-%02d-%02d/to/%04d-%02d-%02d" % (year,month,1, year,month, calendar.monthrange(year,month)[1]), 'format' : 'netcdf', 'target' : local_file }) elif stream=='enda': server = ecmwfapi.ECMWFDataServer() server.retrieve({ "class" : "ea", "dataset" : "era5", 'date' : "%04d-%02d-%02d/to/%04d-%02d-%02d" % (year,month,1, year,month, calendar.monthrange(year,month)[1]), "expver" : "1", "levtype" : "sfc", "number" : "0/1/2/3/4/5/6/7/8/9", "param" : _translate_for_file_names(variable), "stream" : "enda", "time" : "0/to/21/by/3", "type" : "an", 'grid' : '0.5/0.5', 'format' : 'netcdf', "target" : local_file }) else: raise Exception("Unsupported stream %s" % stream)
def retrieve_prmon(mon,eifile,box = None): dx = 0.75 startyear,endyear = 1980,2015 dates = [] for y in range(startyear,endyear + 1): numdays = calendar.monthrange(y,mon)[1] dates.extend([datetime.date(y,mon,d) for d in range(1,numdays + 1)]) if(not os.path.isfile(eifile)): request = {"stream" : "oper", "levtype" : "sfc", "param" : "228.128", "dataset" : "interim", "step" : "3/6/9/12", "grid" : '/'.join([str(dx),str(dx)]), "time" : "00:00:00/12:00:00", "date" : '/'.join([d.strftime("%Y-%m-%d") for d in dates]), "type" : "fc", "class" : "ei", "format" : "netcdf", "target" : eifile } if(box): extent = [] if(len(box) == 2): extent = [box[0] + dx,box[1] - dx,box[0] - dx,box[1] + dx] else: extent = [box[1],box[2],box[0],box[3]] request["area"] = '/'.join([str(c) for c in extent]) print "The request is",request server = ecmwfapi.ECMWFDataServer() server.retrieve(request) return eifile
def click_ecmwfapi_request(b): with out: time_6 = "" time_12 = "" time_18 = "" if hour_06.value == True: time_6 = "/06:00:00" if hour_12.value == True: time_12 = "/12:00:00" if hour_18.value == True: time_18 = "/18:00:00" time_param = time_6 + time_12 + time_18 server = ecmwfapi.ECMWFDataServer() server.retrieve({ "class": "mc", "dataset": "cams_nrealtime", "date": str(start_date_widget.value) + "/to/" + str(current_date_widget.value), "expver": "0001", "levtype": "sfc", "param": parameter_value(data_parameter_widget.value), "step": "0", "stream": "oper", "time": "00:00:00" + time_param, "type": "an", "target": output_filename, })
def test_dataset(): while True: try: c = ecmwfapi.ECMWFDataServer("mars") c.retrieve({ "dataset": "tigge", "step": "24", "levtype": "sl", "date": "20071001", "time": "00", "origin": "ecmf", "type": "cf", "param": "2t", "target": "tigge.grib", }) assert os.path.getsize("tigge.grib") == 428963 return except ecmwfapi.api.APIException as e: print(e) msg = str(e) if "USER_QUEUED_LIMIT_EXCEEDED" not in msg: raise time.sleep(120)
def _get_from_ecmwf(self, lat_min, lat_max, lat_step, lon_min, lon_max, lon_step, time, out): import ecmwfapi server = ecmwfapi.ECMWFDataServer() corrected_date = util.round_date(time, datetime.timedelta(hours=6)) server.retrieve({ "class": self._classname, # ERA-Interim 'dataset': self._dataset, "expver": "1", # They warn me against all, but it works well "levelist": 'all', "levtype": "ml", # Model levels "param": "lnsp/q/z/t", # Necessary variables "stream": "oper", # date: Specify a single date as "2015-08-01" or a period as # "2015-08-01/to/2015-08-31". "date": datetime.datetime.strftime(corrected_date, "%Y-%m-%d"), # type: Use an (analysis) unless you have a particular reason to # use fc (forecast). "type": "an", # time: With type=an, time can be any of # "00:00:00/06:00:00/12:00:00/18:00:00". With type=fc, time can # be any of "00:00:00/12:00:00", "time": datetime.time.strftime(corrected_date.time(), "%H:%M:%S"), # step: With type=an, step is always "0". With type=fc, step can # be any of "3/6/9/12". "step": "0", # grid: Only regular lat/lon grids are supported. "grid": '{}/{}'.format(lat_step, lon_step), "area": '{}/{}/{}/{}'.format(lat_max, lon_min, lat_min, lon_max), # area: N/W/S/E "format": "netcdf", "resol": "av", "target": out, # target: the name of the output file. })
def retrieve_level(reqbase, reqext={}, append=False): # merge requests req = reqbase.copy() req.update(reqext) if append: # save real target name and d/l request to a tmp target file savetarget = req['target'] req['target'] = req['target'] + '.tmp' server = ecmwfapi.ECMWFDataServer() server.retrieve(req) if append: # append to real target by file objects if os.path.isfile(req['target']): targetobj = open(savetarget, 'ab') shutil.copyfileobj(open(req['target'], 'rb'), targetobj) targetobj.close() os.unlink(req['target']) # rm tmp target file req['target'] = savetarget # restore real target
def _fetch_analysis_data_for_month(variable, year, month): local_file = _hourly_get_file_name(variable, year, month) if os.path.isfile(local_file): # Got this data already return if not os.path.exists(os.path.dirname(local_file)): os.makedirs(os.path.dirname(local_file)) server = ecmwfapi.ECMWFDataServer() server.retrieve({ "dataset": "cera20c", "stream": "enda", "type": "an", "class": "ep", "expver": "1", "levtype": "sfc", "param": _translate_for_file_names(variable), "time": "00/03/06/09/12/15/18/21", "grid": "1.25/1.25", "number": "0/1/2/3/4/5/6/7/8/9", "date": "%04d-%02d-%02d/to/%04d-%02d-%02d" % (year, month, 1, year, month, calendar.monthrange(year, month)[1]), "format": "netcdf", "target": local_file, })
def _fetch_analysis_data_for_month(variable, year, month): local_file = _hourly_get_file_name(variable, year, month) if os.path.isfile(local_file): # Got this data already return if not os.path.exists(os.path.dirname(local_file)): os.makedirs(os.path.dirname(local_file)) server = ecmwfapi.ECMWFDataServer() server.retrieve({ 'dataset': 'cera20c', 'stream': 'enda', 'type': 'an', 'class': 'ep', 'expver': '1', 'levtype': 'sfc', 'param': _translate_for_file_names(variable), 'time': '00/03/06/09/12/15/18/21', 'grid': '1.25/1.25', 'number': '0/1/2/3/4/5/6/7/8/9', 'date': "%04d-%02d-%02d/to/%04d-%02d-%02d" % (year, month, 1, year, month, calendar.monthrange(year, month)[1]), 'format': 'netcdf', 'target': local_file })
def _fetch_forecast_data_for_month(variable, year, month): # Want 27 hours of forecast for each day # (so we can interpolate over the seam), # but the 27-hr forcast from one day has the same # validity time as the 3-hr forecast from the # next day - so need to download them separately # and store in different files. # First 24-hours of forecast in main file local_file = _hourly_get_file_name(variable, year, month, fc_init=None) if os.path.isfile(local_file): # Got this data already return if not os.path.exists(os.path.dirname(local_file)): os.makedirs(os.path.dirname(local_file)) server = ecmwfapi.ECMWFDataServer() server.retrieve({ "dataset": "cera20c", "stream": "enda", "type": "fc", "class": "ep", "expver": "1", "levtype": "sfc", "param": _translate_for_file_names(variable), "time": "18", "step": "3/6/9/12/15/18/21/24", "grid": "1.25/1.25", "number": "0/1/2/3/4/5/6/7/8/9", "date": "%04d-%02d-%02d/to/%04d-%02d-%02d" % (year, month, 1, year, month, calendar.monthrange(year, month)[1]), "format": "netcdf", "target": local_file, }) # 27-hour forecast in additional file local_file = _hourly_get_file_name(variable, year, month, fc_init="last") if os.path.isfile(local_file): # Got this data already return if not os.path.exists(os.path.dirname(local_file)): os.makedirs(os.path.dirname(local_file)) server = ecmwfapi.ECMWFDataServer() server.retrieve({ "dataset": "cera20c", "stream": "enda", "type": "fc", "class": "ep", "expver": "1", "levtype": "sfc", "param": _translate_for_file_names(variable), "time": "18", "step": "27", "grid": "1.25/1.25", "number": "0/1/2/3/4/5/6/7/8/9", "date": "%04d-%02d-%02d/to/%04d-%02d-%02d" % (year, month, 1, year, month, calendar.monthrange(year, month)[1]), "format": "netcdf", "target": local_file, })
#!/usr/bin/env python2 # -*- coding: utf-8 -*- ############################################ # # PyGdalSAR: An InSAR post-processing package # written in Python-Gdal # ############################################ # Author : Simon DAOUT (Oxford) ############################################ import ecmwfapi server = ecmwfapi.ECMWFDataServer() server.retrieve({ "class": "ea", "dataset": "era5", "date": "2007-01-01/to/2010-12-31", # Time period "expver": "1", "levtype": "pl", "levelist": "850/700/600/500", "param": "130", # Parameters. Here we use 2m Temperature (2t) and Surface Pressure (sp). See the ECMWF parameter database, at http://apps.ecmwf.int/codes/grib/param-db "stream": "oper", "type": "an", "time": "0/12", "step": "0", "area": "27.25/89.00/28.25/90.00", # Subset or clip to an area, here to Europe. Specify as North/West/South/East in Geographic lat/long degrees. Southern latitudes and Western longitudes must be given as negative numbers. "grid": "0.25/0.25", # Regrid from the default grid to a regular lat/lon with specified resolution. The first number is east-west resolution (longitude) and the second is north-south (latitude).
def era( params, stream, levtype, daterange=None, yearrange=None, monthrange=None, # dayrange=None, # not yet used years=None, months=None, format='netcdf', forecast=False, step=12, levrange=None, levs=None, grid=None, hours=(0, 6, 12, 18), hour=None, res=None, box=None, filename='era.nc', ): """ Retrieves ERA reanalysis data using the provided API. User must have, in home directory, a file named ``.ecmwfapirc``. See API documentation, but should look like: :: { "url" : "https://api.ecmwf.int/v1", "key" : "abcdefghijklmnopqrstuvwxyz", "email" : "*****@*****.**" } with the key found on your user/profile page on the ECMWF website. Parameters ---------- params : str or list of str Variable name. Gets translated to MARS id name by dictionary below. Add to this from the `online GRIB table <https://rda.ucar.edu/datasets/ds627.0/docs/era_interim_grib_table.html>`_. Pay attention to *available groups*. If not available for the group you selected (e.g. pressure levs, moda), get ``ERROR 6 (MARS_EXPECTED_FIELDS)``. For rates of change of *parameterized* processes (i.e. diabatic) see `this link <https://confluence.ecmwf.int/pages/viewpage.action?pageId=57448466>`_. stream : {'oper', 'moda', 'mofm', 'mdfa', 'mnth'} The data stream. levtype : {'ml', 'pl', 'sfc', 'pt', 'pv'} Level type: model, pressure, surface, potential temperature, and 2PVU surface, respectively. levrange : float or (float, float), optional Individual level or range of levels. levs : float or ndarray, optional Individual level or list of levels. yearrange : int or (int, int) Individual year or range of years. years : int or ndarray, optional Individual year or list of years. monthrange : int or (int, int), optional Individual month or range of months. months : int or ndarray, optional Individual month or list of months. daterange : (datetime.datetime, datetime.datetime), optional Range of dates. hours : {0, 6, 12, 18} or list thereof, optional Hour(s) (UTC) of observation. forecast : bool, optional Whether we want forecast `'fc'` or analysis `'an'` data. Note that some data is only available in `'fc'` mode, e.g. diabatic heating. grid : str, optional The grid type. Default is ``N32`` which returns data on 64 latitudes. res : float, optional Alternative to `grid` that specifies the desired output grid resolution in degrees. ERA-Interim has a few valid preset resolutions and will choose the resolution that most closely matches the input. box : str or length-4 list of float, optional String name for particular region, e.g. ``'europe'``, or the west, south, east, and north boundaries, respectively. format : {'grib1', 'grib2', 'netcdf'}, optional Output format. filename : str, optional Name of file output. Notes ----- Some fields (seems true for most model fields) are not archived as monthly means for some reason! Have no idea why because it would need almost zero storage requirements. """ # noqa: E501 # Data stream import ecmwfapi as ecmwf # only do so inside function # Variable id conversion (see: # https://rda.ucar.edu/datasets/ds627.0/docs/era_interim_grib_table.html) if isinstance(params, str) or not np.iterable(params): params = (params,) params = [ { 'tdt': '110.162', # model-level surface pressure; use this whenever getting tdt and stuff # (requires lev=1) 'msp': '152.128', 'sp': '134.128', # surface pressure 't2m': '167.128', # 2m temp 'd2m': '168.128', # 2m dew point 'sst': '34.128', # sst 'msl': '151.128', # sea level pressure 'slp': '151.128', # same 'z': '129.128', # geopotential 't': '130.128', # temp 'u': '131.128', # u wind 'v': '132.128', # v wind 'w': '135.128', # w wind 'q': '133.128', # specific humidity 'r': '157.128', # relative humidity 'vort': '138.128', # relative vorticity 'vo': '138.128', # same 'zeta': '138.128', # same 'pt': '3.128', # potential temp (available on 2pvu surf) 'theta': '3.128', # same 'p': '54.128', # pressure (availble on pt, 2pvu surfaces) 'pres': '54.128', # same # potential vorticity (available on p, pt surfaces) 'pv': '60.128', 'precip': '228.128', }.get(p) for p in params ] # returns generator object for each param if None in params: raise ValueError( 'MARS id for variable is unknown (might need to be added to this script).' ) params = '/'.join(params) # Time selection as various RANGES or LISTS # Priority; just use daterange as datetime or date objects if daterange is not None: if not np.iterable(daterange): daterange = (daterange,) # want a SINGLE DAY # options for monthly or daily data if stream != 'oper': y0, m0, y1, m1 = ( daterange[0].year, daterange[0].month, daterange[1].year, daterange[1].month, ) N = max(y1 - y0 - 1, 0) * 12 + (13 - m0) + m1 # number of months in range dates = '/'.join( '%04d%02d00' % (y0 + (m0 + n - 1) // 12, (m0 + n - 1) % 12 + 1) for n in range(N) ) else: # MARS will get calendar days in range dates = '/to/'.join(d.strftime('%Y%m%d') for d in daterange) # Alternative; list the years/months desired, and if synoptic, get all # calendar days within else: # First, years if years is not None: if not np.iterable(years): years = (years,) # single month elif yearrange is not None: if not np.iterable(yearrange): years = (yearrange,) else: years = tuple(range(yearrange[0], yearrange[1] + 1)) else: raise ValueError('You must use "years" or "yearrange" kwargs.') # Next, months (this way, can just download JJA data, for example) if months is not None: if not np.iterable(months): months = (months,) # single month elif monthrange is not None: if not np.iterable(monthrange): months = (monthrange, monthrange) else: months = tuple(range(monthrange[0], monthrange[1] + 1)) else: months = tuple(range(1, 13)) # And get dates; options for monthly means and daily stuff if stream != 'oper': dates = '/'.join( '/'.join('%04d%02d00' % (y, m) for m in months) for y in years ) else: dates = '/'.join( '/'.join( '/'.join( '%04d%02d%02d' % (y, m, i + 1) for i in range(calendar.monthrange(y, m)[1]) ) for m in months ) for y in years ) # Level selection as RANGE or LIST # Update this list if you modify script for ERA5, etc. levchoices = { 'sfc': None, 'pv': None, 'ml': np.arange(1, 137 + 1), 'pl': np.array( [ 1, 2, 3, 5, 7, 10, 20, 30, 50, 70, 100, 125, 150, 175, 200, 225, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 775, 800, 825, 850, 875, 900, 925, 950, 975, 1000, ] ), 'pt': np.array( [265, 270, 285, 300, 315, 330, 350, 370, 395, 430, 475, 530, 600, 700, 850] ), }.get(levtype, []) if levchoices == []: raise ValueError('Invalid level type. Choose from "pl", "pt", "pv", "sfc".') # More options # WARNING: Some vars are technically on "levels" like hybrid level surface # pressure, but we still need 60. if levtype not in ('sfc', 'pv'): # these have multiple options # Require input if levs is None and levrange is None: raise ValueError( 'Must specify list of levels to "levs" kwarg, range of levels ' 'to "levrange" kwarg, or single level to either one.' ) # Convert levels to mars request if levs is not None: if not np.iterable(levs): levs = (levs,) else: if not np.iterable(levrange): levs = (levrange,) else: levs = levchoices[ (levchoices >= levrange[0]) & (levchoices <= levrange[1]) ].flat levs = '/'.join(str(l) for l in levs) # Other parameters # Resolution # same in latitude/longitude required, for now if res is not None: grid = '%.5f/%.5f' % (res, res) elif grid is None: grid = 'N32' # Area - can be specified as pre-defined region (e.g. string 'europe') OR # n/s/w/e boundary if box is not None and not isinstance(box, str): box = '/'.join(str(b) for b in (box[3], box[0], box[2], box[1])) # Hour conversion if not np.iterable(hours): hours = (hours,) hours = '/'.join(str(h).zfill(2) for h in hours) # zfill padds 0s on left # Forecast type # TODO: Fix the 12 hour thing. Works for some params (e.g. diabatic # heating, has 3, 6, 9, 12) but others have 0, 6, 12, 18. if forecast: dtype, step = 'fc', str(step) else: dtype, step = 'an', '0' # Server instructions # Not really sure what happens in some situations: list so far: # 1. Evidently if you provide with variable string-name instead of numeric ID, # MARS will search for correct one; if there is name ambiguity/conflict will # throw error. # 2. On GUI framework, ECMWF only offers a few resolution options, but program # seems to run when requesting custom resolutions like 5deg/5deg # Can also spit raw output into GRIB; apparently ERA-Interim uses # bilinear interpolation to make grid of point obs, which makes sense, # because their reanalysis model just picks out point observations # from spherical harmonics; so maybe grid cell concept is dumb? Maybe need # to focus on just using cosine weightings, forget about rest? request = { # Important ones 'class': 'ei', # ecmwf classifiction; choose ERA-Interim 'expver': '1', 'dataset': 'interim', # thought we already did that; *shrug* 'type': dtype, # type of field; analysis 'an' or forecast 'fc' 'resol': 'av', # prevents truncation before transformation to geo grid 'gaussian': 'reduced', 'format': format, 'step': step, # NOTE: ignored for non-forecast type 'grid': grid, # 64 latitudes, i.e. T42 truncation 'stream': stream, # product monthly, raw, etc. 'date': dates, 'time': hours, 'levtype': levtype, 'param': params, 'target': filename, # save location } maxlen = max(map(len, request.keys())) if levs is not None: request.update(levelist=levs) if box is not None: request.update(area=box) if stream == 'oper': # TODO: change? request.update(hour=hour) print( 'REQUEST\n' + '\n'.join( f'"{key}": ' + ' ' * (maxlen - len(key)) + f'{value}' for key, value in request.items() ) ) # Retrieve DATA with settings server = ecmwf.ECMWFDataServer() server.retrieve(request) return
def _fetch_forecast_data_for_month(variable, year, month): # Want 27 hours of forecast for each day # (so we can interpolate over the seam), # but the 27-hr forcast from one day has the same # validity time as the 3-hr forecast from the # next day - so need to download them separately # and store in different files. # First 24-hours of forecast in main file local_file = _hourly_get_file_name(variable, year, month, fc_init=None) if os.path.isfile(local_file): # Got this data already return if not os.path.exists(os.path.dirname(local_file)): os.makedirs(os.path.dirname(local_file)) server = ecmwfapi.ECMWFDataServer() server.retrieve({ 'dataset': 'cera20c', 'stream': 'enda', 'type': 'fc', 'class': 'ep', 'expver': '1', 'levtype': 'sfc', 'param': _translate_for_file_names(variable), 'time': '18', 'step': '3/6/9/12/15/18/21/24', 'grid': '1.25/1.25', 'number': '0/1/2/3/4/5/6/7/8/9', 'date': "%04d-%02d-%02d/to/%04d-%02d-%02d" % (year, month, 1, year, month, calendar.monthrange(year, month)[1]), 'format': 'netcdf', 'target': local_file }) # 27-hour forecast in additional file local_file = _hourly_get_file_name(variable, year, month, fc_init='last') if os.path.isfile(local_file): # Got this data already return if not os.path.exists(os.path.dirname(local_file)): os.makedirs(os.path.dirname(local_file)) server = ecmwfapi.ECMWFDataServer() server.retrieve({ 'dataset': 'cera20c', 'stream': 'enda', 'type': 'fc', 'class': 'ep', 'expver': '1', 'levtype': 'sfc', 'param': _translate_for_file_names(variable), 'time': '18', 'step': '27', 'grid': '1.25/1.25', 'number': '0/1/2/3/4/5/6/7/8/9', 'date': "%04d-%02d-%02d/to/%04d-%02d-%02d" % (year, month, 1, year, month, calendar.monthrange(year, month)[1]), 'format': 'netcdf', 'target': local_file })
def _fetch_analysis_data_for_month(variable, year, month, stream="enda"): local_file = _hourly_get_file_name(variable, year, month, stream=stream) if os.path.isfile(local_file): # Got this data already return if not os.path.exists(os.path.dirname(local_file)): os.makedirs(os.path.dirname(local_file)) if stream == "oper": grid = "0.25/0.25" server = ecmwfapi.ECMWFDataServer() server.retrieve({ "dataset": "era5", "stream": "oper", "type": "an", "levtype": "sfc", "param": _translate_for_file_names(variable), "grid": "0.25/0.25", "time": "0/to/23/by/1", "date": "%04d-%02d-%02d/to/%04d-%02d-%02d" % (year, month, 1, year, month, calendar.monthrange(year, month)[1]), "format": "netcdf", "target": local_file, }) elif stream == "enda": server = ecmwfapi.ECMWFDataServer() server.retrieve({ "class": "ea", "dataset": "era5", "date": "%04d-%02d-%02d/to/%04d-%02d-%02d" % (year, month, 1, year, month, calendar.monthrange(year, month)[1]), "expver": "1", "levtype": "sfc", "number": "0/1/2/3/4/5/6/7/8/9", "param": _translate_for_file_names(variable), "stream": "enda", "time": "0/to/21/by/3", "type": "an", "grid": "0.5/0.5", "format": "netcdf", "target": local_file, }) else: raise Exception("Unsupported stream %s" % stream)