Example #1
0
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)
Example #2
0
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
Example #3
0
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