8000 sam iotools #1371 by shirubana · Pull Request #1556 · pvlib/pvlib-python · GitHub
[go: up one dir, main page]

Skip to content

sam iotools #1371 #1556

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

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
stickler
  • Loading branch information
shirubana committed Sep 22, 2022
commit 2de5a3716272da5b4e8c7ae20dbb762767f2b093
102 changes: 57 additions & 45 deletions pvlib/iotools/sam.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,61 @@

import pandas as pd

def saveSAM_WeatherFile(data, metadata, savefile='SAM_WeatherFile.csv', standardSAM = True, includeminute=False):
def saveSAM_WeatherFile(data, metadata, savefile='SAM_WeatherFile.csv',
standardSAM=True, includeminute=False):
"""
Saves a dataframe with weather data from pvlib format on SAM-friendly format.
Saves dataframe with weather data from pvlib format on SAM-friendly format.

Parameters
-----------
data : pandas.DataFrame
timeseries data in PVLib format. Should be TZ converted (not UTC). Ideally it is one sequential year data; if not suggested to use standardSAM = False.
timeseries data in PVLib format. Should be TZ converted (not UTC).
Ideally it is one sequential year data; if not suggested to use
standardSAM = False.
metdata : dictionary
Dictionary with at least 'latitude', 'longitude', 'elevation', 'source', and 'TZ' for timezone.
Dictionary with 'latitude', 'longitude', 'elevation', 'source',
and 'TZ' for timezone.
savefile : str
Name of file to save output as.
Name of file to save output as.
standardSAM : boolean
This checks the dataframe to avoid having a leap day, then averages it to SAM style (closed to the right),
and fills the years so it starst on YEAR/1/1 0:0 and ends on YEAR/12/31 23:00.
This checks the dataframe to avoid having a leap day, then averages it
to SAM style (closed to the right),
and fills the years so it starst on YEAR/1/1 0:0 and ends on
YEAR/12/31 23:00.
includeminute ; Bool
For hourly data, if SAM input does not have Minutes, it calculates the sun position 30 minutes
prior to the hour (i.e. 12 timestamp means sun position at 11:30)
If minutes are included, it will calculate the sun position at the time of the timestamp (12:00 at 12:00)
For hourly data, if SAM input does not have Minutes, it calculates the
sun position 30 minutes prior to the hour (i.e. 12 timestamp means sun
position at 11:30).
If minutes are included, it will calculate the sun position at the time
of the timestamp (12:00 at 12:00)
Set to true if resolution of data is sub-hourly.

Returns
-------
Nothing, it just writes the file.

"""

def _is_leap_and_29Feb(s):
''' Creates a mask to help remove Leap Years. Obtained from:
https://stackoverflow.com/questions/34966422/remove-leap-year-day-from-pandas-dataframe/34966636
''' Creates a mask to help remove Leap Years. Obtained from:
https://stackoverflow.com/questions/34966422/remove-leap-year-day-
from-pandas-dataframe/34966636
'''
return (s.index.year % 4 == 0) & \
((s.index.year % 100 != 0) | (s.index.year % 400 == 0)) & \
(s.index.month == 2) & (s.index.day == 29)

def _averageSAMStyle(df, interval='60T', closed='right', label='right'):
''' Averages subhourly data into hourly data in SAM's expected format.
''' Averages subhourly data into hourly data in SAM's expected format.
'''
try:
df = df.resample(interval, closed=closed, label=label).mean() #
df = df.resample(interval, closed=closed, label=label).mean()
except:
print('Warning - unable to average')
return df

def _fillYearSAMStyle(df, freq='60T'):
''' Fills year
''' Fills year
'''
# add zeros for the rest of the year
if freq is None:
Expand All @@ -59,38 +68,41 @@ def _fillYearSAMStyle(df, freq='60T'):
# idx = df.index
# apply correct TZ info (if applicable)
tzinfo = df.index.tzinfo
starttime = pd.to_datetime('%s-%s-%s %s:%s' % (df.index.year[0],1,1,0,0 ) ).tz_localize(tzinfo)
endtime = pd.to_datetime('%s-%s-%s %s:%s' % (df.index.year[-1],12,31,23,60-int(freq[:-1])) ).tz_localize(tzinfo)
starttime = pd.to_datetime('%s-%s-%s %s:%s' % (df.index.year[0], 1, 1,
0, 0)).tz_localize(tzinfo)
endtime = pd.to_datetime('%s-%s-%s %s:%s' % (df.index.year[-1], 12, 31,
23, 60-int(freq[:-1]))
).tz_localize(tzinfo)

df2 = _averageSAMStyle(df, freq)
df2.iloc[0] = 0 # set first datapt to zero to forward fill w zeros
df2.iloc[-1] = 0 # set last datapt to zero to forward fill w zeros
df2.loc[starttime] = 0
df2.loc[endtime] = 0
df2 = df2.resample(freq).ffill()
df2 = df2.resample(freq).ffill()
return df2


# Modify this to cut into different years. Right now handles partial year and sub-hourly interval.
# Modify this to cut into different years. Right now handles partial year
# and sub-hourly interval.
if standardSAM:
filterdatesLeapYear = ~(_is_leap_and_29Feb(data))
filterdatesLeapYear = ~(_is_leap_and_29Feb(data))
data = data[filterdatesLeapYear]
data = _fillYearSAMStyle(data)


# metadata
latitude=metadata['latitude']
longitude=metadata['longitude']
elevation=metadata['elevation']
latitude = metadata['latitude']
longitude = metadata['longitude']
elevation = metadata['elevation']
timezone_offset = metadata['TZ']
source = metadata['source']

# make a header
header = '\n'.join(
[ 'Source,Latitude,Longitude,Time Zone,Elevation',
source + ',' + str(latitude) + ',' + str(longitude) + ',' + str(timezone_offset) + ',' + str(elevation)]) + '\n'

savedata = pd.DataFrame({'Year':data.index.year, 'Month':data.index.month, 'Day':data.index.day,
# make a header
header = '\n'.join([ 'Source,Latitude,Longitude,Time Zone,Elevation',
source + ',' + str(latitude) + ',' + str(longitude)
+ ',' + str(timezone_offset) + ',' + str(elevation)])+'\n'

savedata = pd.DataFrame({'Year':data.index.year, 'Month':data.index.month,
'Day':data.index.day,
'Hour':data.index.hour})

if includeminute:
Expand All @@ -100,24 +112,24 @@ def _fillYearSAMStyle(df, freq='60T'):
temp_amb = list(data.temp_air)
savedata['Wspd'] = windspeed
savedata['Tdry'] = temp_amb

if 'dni' in data:
dni = list(data.dni)
savedata['DHI'] = dni

if 'dhi' in data:
dhi = list(data.dhi)
savedata['DNI'] = dhi

if 'ghi' in data:
ghi = list(data.ghi)
savedata['GHI'] = ghi
if 'poa' in data: # This is a nifty function of SAM for using field measured POA irradiance!

if 'poa' in data:
poa = list(data.poa)
savedata['POA'] = poa
if 'albedo' in data:

if 'albedo' in data:
albedo = list(data.albedo)
savedata['Albedo'] = albedo

Expand All @@ -133,18 +145,18 @@ def _fillYearSAMStyle(df, freq='60T'):

def tz_convert(df, tz_convert_val, metadata=None):
"""
Support function to convert metdata E78F to a different local timezone. Particularly for
GIS weather files which are returned in UTC by default.
Support function to convert metdata to a different local timezone.
Particularly for GIS weather files which are returned in UTC by default.

Parameters
----------
df : DataFrame
A dataframe in UTC timezone
tz_convert_val : int
Convert timezone to this fixed value, following ISO standard
Convert timezone to this fixed value, following ISO standard
(negative values indicating West of UTC.)
Returns: metdata, metadata
Returns: metdata, metadata


Returns
-------
Expand Down
14 changes: 9 additions & 5 deletions pvlib/tests/iotools/test_sam.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@
testfile_radiation_csv = r'C:\Users\sayala\Documents\GitHub\pvlib-python\pvlib\data\pvgis_hourly_Timeseries_45.000_8.000_SA_30deg_0deg_2016_2016.csv'

def test_saveSAM_WeatherFile():
data, inputs, metadata = read_pvgis_hourly(testfile_radiation_csv, map_variables=True)#, pvgis_format=pvgis_format)
metadata = {'latitude': inputs['latitude'],
'longitude': inputs['longitude'],
'elevation': inputs['elevation'],
data, months_selected, inputs, metadata = get_pvgis_tmy(latitude=33, longitude=-110, map_variables=True)
# Reading local file doesn't work read_pvgis_hourly returns different
# keys than get_pvgis_tmy when map=variables = True. Opened issue for that
# data, inputs, metadata = read_pvgis_hourly(testfile_radiation_csv,
# map_variables=True)
metadata = {'latitude': inputs['location']['latitude'],
'longitude': inputs['location']['longitude'],
'elevation': inputs['location']['elevation'],
'source': 'User-generated'}
metadata['TZ'] = -7
metadata['TZ'] = -7
data = tz_convert(data, tz_convert_val=metadata['TZ'])
coerce_year=2021 # read_pvgis_hourly does not coerce_year, so doing it here.
data.index = data.index.map(lambda dt: dt.replace(year=coerce_year))
Expand Down
0