-
Notifications
You must be signed in to change notification settings - Fork 1.1k
add pvgis to iotools #845
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
add pvgis to iotools #845
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
230f4f2
add get_pvgis_tmy to iotools
mikofski 362dac6
stickler fixes maybe?
mikofski 263fc5c
add pvgis tests
mikofski 5314c60
fix stickler
mikofski 868fc33
Merge branch 'master' into pvgis
mikofski 3cfb0a2
STY: fix indents and long lines in test meta JSON
mikofski ef8594b
check each column of output separately
mikofski d471da9
add test for basic output format
mikofski 6b3a501
add test for epw output format
mikofski ec2a193
add parse_epw to use buffer v. filepath
mikofski 6232cb4
fix use lowercase url in request
mikofski df3a2df
use assert cond is False instead of ==, thx stickler
mikofski 3fa3c81
update api docs and what's new
mikofski f93cec3
fix typo in Parameters
mikofski 1b30bda
use with context manager for IO
mikofski 2d3335c
use fixtures for test_pvgis
mikofski 5bea928
fix reference links
mikofski 7c37f66
fix open for py35
mikofski 83db8df
remove whitespace on blank line
mikofski 67eb6af
initialize data to None in case API fails to respond to bad outputformat
mikofski File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
{ | ||
"inputs": { | ||
"location": { | ||
"description": "Selected location", | ||
"variables": { | ||
"latitude": { | ||
"description": "Latitude", | ||
"units": "decimal degree" | ||
}, | ||
"longitude": { | ||
"description": "Longitude", | ||
"units": "decimal degree" | ||
}, | ||
"elevation": { | ||
"description": "Elevation", | ||
"units": "m" | ||
} | ||
} | ||
}, | ||
"meteo_data": { | ||
"description": "Sources of meteorological data", | ||
"variables": { | ||
"radiation_db": { | ||
"description": "Solar radiation database" | ||
}, | ||
"meteo_db": { | ||
"description": "Database used for meteorological variables other than solar radiation" | ||
}, | ||
"year_min": { | ||
"description": "First year of the calculations" | ||
}, | ||
"year_max": { | ||
"description": "Last year of the calculations" | ||
}, | ||
"use_horizon": { | ||
"description": "Include horizon shadows" | ||
}, | ||
"horizon_db": { | ||
"description": "Source of horizon data" | ||
} | ||
} | ||
} | ||
}, | ||
"outputs": { | ||
"months_selected": { | ||
"type": "time series", | ||
"timestamp": "monthly", | ||
"description": "months selected for the TMY" | ||
}, | ||
"tmy_hourly": { | ||
"type": "time series", | ||
"timestamp": "hourly", | ||
"variables": { | ||
"T2m": { | ||
"description": "2-m air temperature", | ||
"units": "degree Celsius" | ||
}, | ||
"RH": { | ||
"description": "relative humidity", | ||
"units": "%" | ||
}, | ||
"G(h)": { | ||
"description": "Global irradiance on the horizontal plane", | ||
"units": "W/m2" | ||
}, | ||
"Gb(n)": { | ||
"description": "Beam/direct irradiance on a plane always normal to sun rays", | ||
"units": "W/m2" | ||
}, | ||
"Gd(h)": { | ||
"description": "Diffuse irradiance on the horizontal plane", | ||
"units": "W/m2" | ||
}, | ||
"IR(h)": { | ||
"description": "Surface infrared (thermal) irradiance on a horizontal plane", | ||
"units": "W/m2" | ||
}, | ||
"WS10m": { | ||
"description": "10-m total wind speed", | ||
"units": "m/s" | ||
}, | ||
"WD10m": { | ||
"description": "10-m wind direction (0 = N, 90 = E)", | ||
"units": "degree" | ||
}, | ||
"SP": { | ||
"description": "Surface (air) pressure", | ||
"units": "Pa" | ||
} | ||
} | ||
} | ||
} | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
""" | ||
Get, read, and parse data from `PVGIS <https://ec.europa.eu/jrc/en/pvgis>`_. | ||
|
||
For more information, see the following links: | ||
* `Interactive Tools <https://re.jrc.ec.europa.eu/pvg_tools/en/tools.html>`_ | ||
* `Data downloads <https://ec.europa.eu/jrc/en/PVGIS/downloads/data>`_ | ||
* `User manual docs <https://ec.europa.eu/jrc/en/PVGIS/docs/usermanual>`_ | ||
|
||
More detailed information about the API for TMY and hourly radiation are here: | ||
* `TMY <https://ec.europa.eu/jrc/en/PVGIS/tools/tmy>`_ | ||
* `hourly radiation | ||
<https://ec.europa.eu/jrc/en/PVGIS/tools/hourly-radiation>`_ | ||
* `daily radiation <https://ec.europa.eu/jrc/en/PVGIS/tools/daily-radiation>`_ | ||
* `monthly radiation | ||
<https://ec.europa.eu/jrc/en/PVGIS/tools/monthly-radiation>`_ | ||
""" | ||
import io | ||
import requests | ||
import pandas as pd | ||
from pvlib.iotools import parse_epw | ||
|
||
URL = 'https://re.jrc.ec.europa.eu/api/' | ||
|
||
|
||
def get_pvgis_tmy(lat, lon, outputformat='json', usehorizon=True, | ||
mikofski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
userhorizon=None, startyear=None, endyear=None, url=URL, | ||
timeout=30): | ||
""" | ||
Get TMY data from PVGIS [1]_. For more information see the PVGIS TMY tool | ||
documentation [2]_. | ||
|
||
Parameters | ||
---------- | ||
mikofski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 | ||
|
||
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 | ||
mikofski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 | ||
|
||
References | ||
---------- | ||
|
||
.. [1] `PVGIS <https://ec.europa.eu/jrc/en/pvgis>`_ | ||
.. [2] `PVGIS TMY tool <https://ec.europa.eu/jrc/en/PVGIS/tools/tmy>`_ | ||
mikofski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.. [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 | ||
if outputformat == 'json': | ||
src = res.json() | ||
return _parse_pvgis_tmy_json(src) | ||
elif outputformat == 'csv': | ||
with io.BytesIO(res.content) as src: | ||
data = _parse_pvgis_tmy_csv(src) | ||
elif outputformat == 'basic': | ||
with io.BytesIO(res.content) as src: | ||
data = _parse_pvgis_tmy_basic(src) | ||
elif outputformat == 'epw': | ||
with io.StringIO(res.content.decode('utf-8')) as src: | ||
data, meta = parse_epw(src) | ||
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 | ||
mikofski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return data | ||
|
||
|
||
def _parse_pvgis_tmy_json(src): | ||
inputs = src['inputs'] | ||
meta = src['meta'] | ||
months_selected = src['outputs']['months_selected'] | ||
data = pd.DataFrame(src['outputs']['tmy_hourly']) | ||
data.index = pd.to_datetime( | ||
data['time(UTC)'], format='%Y%m%d:%H%M', utc=True) | ||
data = data.drop('time(UTC)', axis=1) | ||
return data, months_selected, inputs, meta | ||
|
||
|
||
def _parse_pvgis_tmy_csv(src): | ||
# the first 3 rows are latitude, longitude, elevation | ||
inputs = {} | ||
# 'Latitude (decimal degrees): 45.000\r\n' | ||
inputs['latitude'] = float(src.readline().split(b':')[1]) | ||
# 'Longitude (decimal degrees): 8.000\r\n' | ||
inputs['longitude'] = float(src.readline().split(b':')[1]) | ||
# Elevation (m): 1389.0\r\n | ||
inputs['elevation'] = float(src.readline().split(b':')[1]) | ||
# then there's a 13 row comma separated table with two columns: month, year | ||
# which contains the year used for that month in the | ||
src.readline() # get "month,year\r\n" | ||
months_selected = [] | ||
for month in range(12): | ||
months_selected.append( | ||
{'month': month+1, 'year': int(src.readline().split(b',')[1])}) | ||
# then there's the TMY (typical meteorological year) data | ||
# first there's a header row: | ||
# time(UTC),T2m,RH,G(h),Gb(n),Gd(h),IR(h),WS10m,WD10m,SP | ||
headers = [h.decode('utf-8').strip() for h in src.readline().split(b',')] | ||
data = pd.DataFrame( | ||
[src.readline().split(b',') for _ in range(8760)], columns=headers) | ||
dtidx = data['time(UTC)'].apply(lambda dt: dt.decode('utf-8')) | ||
dtidx = pd.to_datetime(dtidx, format='%Y%m%d:%H%M', utc=True) | ||
data = data.drop('time(UTC)', axis=1) | ||
data = pd.DataFrame(data, dtype=float) | ||
data.index = dtidx | ||
# finally there's some meta data | ||
meta = [line.decode('utf-8').strip() for line in src.readlines()] | ||
return data, months_selected, inputs, meta | ||
|
||
|
||
def _parse_pvgis_tmy_basic(src): | ||
data = pd.read_csv(src) | ||
data.index = pd.to_datetime( | ||
data['time(UTC)'], format='%Y%m%d:%H%M', utc=True) | ||
data = data.drop('time(UTC)', axis=1) | ||
return data, None, None, None |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.