def read_pvgis_tmy(filename, pvgis_format=None): """ Read a file downloaded from PVGIS. Parameters ---------- filename : str, pathlib.Path, or file-like buffer Name, path, or buffer of file downloaded from PVGIS. pvgis_format : str, default None Format of PVGIS file or buffer. Equivalent to the ``outputformat`` parameter in the PVGIS TMY API. If `filename` is a file and `pvgis_format` is ``None`` then the file extension will be used to determine the PVGIS format to parse. For PVGIS files from the API with ``outputformat='basic'``, please set `pvgis_format` to ``'basic'``. If `filename` is a buffer, then `pvgis_format` is required and must be in ``['csv', 'epw', 'json', 'basic']``. Returns ------- data : pandas.DataFrame the weather data months_selected : list TMY year for each month, ``None`` for basic and EPW inputs : dict the inputs, ``None`` for basic and EPW meta : list or dict meta data, ``None`` for basic Raises ------ ValueError if `pvgis_format` is ``None`` and the file extension is neither ``.csv``, ``.json``, nor ``.epw``, or if `pvgis_format` is provided as input but isn't in ``['csv', 'epw', 'json', 'basic']`` TypeError if `pvgis_format` is ``None`` and `filename` is a buffer See also -------- get_pvgis_tmy """ # get the PVGIS outputformat if pvgis_format is None: # get the file extension from suffix, but remove the dot and make sure # it's lower case to compare with epw, csv, or json # NOTE: raises TypeError if filename is a buffer outputformat = Path(filename).suffix[1:].lower() else: outputformat = pvgis_format # parse the pvgis file based on the output format, either 'epw', 'json', # 'csv', or 'basic' # EPW: use the EPW parser from the pvlib.iotools epw.py module if outputformat == 'epw': try: data, meta = parse_epw(filename) except AttributeError: # str/path has no .read() attribute data, meta = read_epw(filename) return data, None, None, meta # NOTE: json, csv, and basic output formats have parsers defined as private # functions in this module # JSON: use Python built-in json module to convert file contents to a # Python dictionary, and pass the dictionary to the _parse_pvgis_tmy_json() # function from this module if outputformat == 'json': try: src = json.load(filename) except AttributeError: # str/path has no .read() attribute with open(str(filename), 'r') as fbuf: src = json.load(fbuf) return _parse_pvgis_tmy_json(src) # CSV or basic: use the correct parser from this module # eg: _parse_pvgis_tmy_csv() or _parse_pvgist_tmy_basic() if outputformat in ['csv', 'basic']: # get the correct parser function for this output format from globals() pvgis_parser = globals()['_parse_pvgis_tmy_{:s}'.format(outputformat)] # NOTE: pvgis_parse() is a pvgis parser function from this module, # either _parse_pvgis_tmy_csv() or _parse_pvgist_tmy_basic() try: pvgis_data = pvgis_parser(filename) except AttributeError: # str/path has no .read() attribute with open(str(filename), 'rb') as fbuf: pvgis_data = pvgis_parser(fbuf) return pvgis_data # raise exception if pvgis format isn't in ['csv', 'basic', 'epw', 'json'] err_msg = ( "pvgis format '{:s}' was unknown, must be either 'epw', 'json', 'csv'" ", or 'basic'").format(outputformat) raise ValueError(err_msg)
def get_pvgis_tmy(lat, lon, outputformat='json', usehorizon=True, userhorizon=None, startyear=None, endyear=None, url=URL, timeout=30, localtime=False): """ Get TMY data from PVGIS. For more information see the PVGIS [1]_ TMY tool documentation [2]_. Parameters ---------- lat : float Latitude in degrees north lon : float Longitude in dgrees east outputformat : str, default 'json' Must be in ``['csv', 'basic', 'epw', 'json']``. See PVGIS TMY tool documentation [2]_ for more info. usehorizon : bool, default True include effects of horizon userhorizon : list of float, default None optional user specified elevation of horizon in degrees, at equally spaced azimuth clockwise from north, only valid if `usehorizon` is true, if `usehorizon` is true but `userhorizon` is `None` then PVGIS will calculate the horizon [3]_ startyear : int, default None first year to calculate TMY endyear : int, default None last year to calculate TMY, must be at least 10 years from first year url : str, default :const:`pvlib.iotools.pvgis.URL` base url of PVGIS API, append ``tmy`` to get TMY endpoint timeout : int, default 30 time in seconds to wait for server response before timeout localtime: bool, default False if True, convert index to local time given by longitude and latitude, else tz=UTC Returns ------- data : pandas.DataFrame the weather data months_selected : list TMY year for each month, ``None`` for basic and EPW inputs : dict the inputs, ``None`` for basic and EPW meta : list or dict meta data, ``None`` for basic Raises ------ requests.HTTPError if the request response status is ``HTTP/1.1 400 BAD REQUEST``, then the error message in the response will be raised as an exception, otherwise raise whatever ``HTTP/1.1`` error occurred See also -------- read_pvgis_tmy References ---------- .. [1] `PVGIS <https://ec.europa.eu/jrc/en/pvgis>`_ .. [2] `PVGIS TMY tool <https://ec.europa.eu/jrc/en/PVGIS/tools/tmy>`_ .. [3] `PVGIS horizon profile tool <https://ec.europa.eu/jrc/en/PVGIS/tools/horizon>`_ """ # use requests to format the query string by passing params dictionary params = {'lat': lat, 'lon': lon, 'outputformat': outputformat} # pvgis only likes 0 for False, and 1 for True, not strings, also the # default for usehorizon is already 1 (ie: True), so only set if False if not usehorizon: params['usehorizon'] = 0 if userhorizon is not None: params['userhorizon'] = ','.join(str(x) for x in userhorizon) if startyear is not None: params['startyear'] = startyear if endyear is not None: params['endyear'] = endyear res = requests.get(url + 'tmy', params=params, timeout=timeout) # PVGIS returns really well formatted error messages in JSON for HTTP/1.1 # 400 BAD REQUEST so try to return that if possible, otherwise raise the # HTTP/1.1 error caught by requests if not res.ok: try: err_msg = res.json() except Exception: res.raise_for_status() else: raise requests.HTTPError(err_msg['message']) # initialize data to None in case API fails to respond to bad outputformat data = None, None, None, None # get time zone name if localtime tz = None if localtime: tf = TimezoneFinder() try: tz = tf.timezone_at(lng=lon, lat=lat) if tz is None: tz = tf.closest_timezone_at(lng=lon, lat=lat) except ValueError: # this line is never reached because if the lat, lon are # the response is HTTP/1.1 400 BAD REQUEST which is handle # by PVGIS in the requests pass if outputformat == 'json': src = res.json() return _parse_pvgis_tmy_json(src, tz) elif outputformat == 'csv': with io.BytesIO(res.content) as src: data = _parse_pvgis_tmy_csv(src, tz) elif outputformat == 'basic': with io.BytesIO(res.content) as src: data = _parse_pvgis_tmy_basic(src, tz) elif outputformat == 'epw': with io.StringIO(res.content.decode('utf-8')) as src: data, meta = parse_epw(src) if tz is not None: data.index = data.index.tz_convert(tz) data.index.name = f'time({tz})' data = (data, None, None, meta) else: # this line is never reached because if outputformat is not valid then # the response is HTTP/1.1 400 BAD REQUEST which is handled earlier pass return data
def get_pvgis_tmy(latitude, longitude, outputformat='json', usehorizon=True, userhorizon=None, startyear=None, endyear=None, url=URL, map_variables=None, timeout=30): """ Get TMY data from PVGIS. For more information see the PVGIS [1]_ TMY tool documentation [2]_. Parameters ---------- latitude : float Latitude in degrees north longitude : float Longitude in degrees east outputformat : str, default 'json' Must be in ``['csv', 'basic', 'epw', 'json']``. See PVGIS TMY tool documentation [2]_ for more info. usehorizon : bool, default True include effects of horizon userhorizon : list of float, default None optional user specified elevation of horizon in degrees, at equally spaced azimuth clockwise from north, only valid if ``usehorizon`` is true, if ``usehorizon`` is true but ``userhorizon`` is ``None`` then PVGIS will calculate the horizon [3]_ startyear : int, default None first year to calculate TMY endyear : int, default None last year to calculate TMY, must be at least 10 years from first year url : str, default: :const:`pvlib.iotools.pvgis.URL` base url of PVGIS API, append ``tmy`` to get TMY endpoint map_variables: bool When true, renames columns of the Dataframe to pvlib variable names where applicable. See variable PVGIS_VARIABLE_MAP. timeout : int, default 30 time in seconds to wait for server response before timeout Returns ------- data : pandas.DataFrame the weather data months_selected : list TMY year for each month, ``None`` for basic and EPW inputs : dict the inputs, ``None`` for basic and EPW metadata : list or dict file metadata, ``None`` for basic Note ---- The PVGIS website uses 10 years of data to generate the TMY, whereas the API accessed by this function defaults to using all available years. This means that the TMY returned by this function may not be identical to the one generated by the website. To replicate the website requests, specify the corresponding 10 year period using ``startyear`` and ``endyear``. Specifying ``endyear`` also avoids the TMY changing when new data becomes available. Raises ------ requests.HTTPError if the request response status is ``HTTP/1.1 400 BAD REQUEST``, then the error message in the response will be raised as an exception, otherwise raise whatever ``HTTP/1.1`` error occurred See also -------- read_pvgis_tmy References ---------- .. [1] `PVGIS <https://ec.europa.eu/jrc/en/pvgis>`_ .. [2] `PVGIS TMY tool <https://ec.europa.eu/jrc/en/PVGIS/tools/tmy>`_ .. [3] `PVGIS horizon profile tool <https://ec.europa.eu/jrc/en/PVGIS/tools/horizon>`_ """ # use requests to format the query string by passing params dictionary params = {'lat': latitude, 'lon': longitude, 'outputformat': outputformat} # pvgis only likes 0 for False, and 1 for True, not strings, also the # default for usehorizon is already 1 (ie: True), so only set if False if not usehorizon: params['usehorizon'] = 0 if userhorizon is not None: params['userhorizon'] = ','.join(str(x) for x in userhorizon) if startyear is not None: params['startyear'] = startyear if endyear is not None: params['endyear'] = endyear res = requests.get(url + 'tmy', params=params, timeout=timeout) # PVGIS returns really well formatted error messages in JSON for HTTP/1.1 # 400 BAD REQUEST so try to return that if possible, otherwise raise the # HTTP/1.1 error caught by requests if not res.ok: try: err_msg = res.json() except Exception: res.raise_for_status() else: raise requests.HTTPError(err_msg['message']) # initialize data to None in case API fails to respond to bad outputformat data = None, None, None, None if outputformat == 'json': src = res.json() data, months_selected, inputs, meta = _parse_pvgis_tmy_json(src) elif outputformat == 'csv': with io.BytesIO(res.content) as src: data, months_selected, inputs, meta = _parse_pvgis_tmy_csv(src) elif outputformat == 'basic': with io.BytesIO(res.content) as src: data, months_selected, inputs, meta = _parse_pvgis_tmy_basic(src) elif outputformat == 'epw': with io.StringIO(res.content.decode('utf-8')) as src: data, meta = parse_epw(src) months_selected, inputs = None, None else: # this line is never reached because if outputformat is not valid then # the response is HTTP/1.1 400 BAD REQUEST which is handled earlier pass if map_variables is None: warnings.warn( 'PVGIS variable names will be renamed to pvlib conventions by ' 'default starting in pvlib 0.10.0. Specify map_variables=True ' 'to enable that behavior now, or specify map_variables=False ' 'to hide this warning.', pvlibDeprecationWarning ) map_variables = False if map_variables: data = data.rename(columns=PVGIS_VARIABLE_MAP) return data, months_selected, inputs, meta