Solar Energy Score
Calculate DHI, GHI and DNI for an address
What's it do?

This app will accept any address and generate an irradiance prediction from the current weather forecast to calculate expected energy generation from PV cells. Three irradiance values are predicted, Direct Normal Irradiance (DNI), Diffuse Horizontal Irradiance (DHI) and Global Horizontal Irradiance (GHI), as defined here.

Example solar prediction for a location.
Ingredients

- A Google or Zillow API account
- Python, Flask, and a few other python libraries
- leaflet.js library for displaying a map
- Modzy's Python SDK
- The Solar Irradiance Prediction model running on Modzy
Instructions

- First, choose whether you will use Zillow or Google to geocode an address. Google is more complete and versatile, but Zillow is much faster. Here is a sample method for each:
def derive_latlon_zillow(address=None, zipcode=None):
''' derive latlng from Zillow's GeoCoding API by providing address and zipcode '''
from pyzillow.pyzillow import ZillowWrapper, GetDeepSearchResults
zillow_data = ZillowWrapper(ZWSID) #replace or load ZWSID with your Zillow API ID
deep_search_response = zillow_data.get_deep_search_results(address, zipcode)
result = GetDeepSearchResults(deep_search_response)
return({
'latitude': result.latitude,
'longitude': result.longitude,
'address':address,
'zipcode':zipcode
})
def derive_latlng_gcp(address=None):
''' derive latlng from Google's GeoCoding API by providing address '''
url = 'https://maps.googleapis.com/maps/api/geocode/json'
key = GCP_API_KEY #replace or load GCP_API_KEY with your Google API key
params = {'address': address, 'key': key}
result = requests.get(url=url, params=params)
json_result = result.json()
context = {}
if json_result.get('status') and json_result.get('status') == 'OK':
context.update({
'status': json_result.get('status'),
'message': 'Success',
'data': {
'latitude': json_result['results'][0]['geometry']['location']['lat'],
'longitude': json_result['results'][0]['geometry']['location']['lng'],
'address':address,
}
})
else:
context.update({
'status': json_result.get('status'),
'message': json_result.get('error_message'),
})
return context, json_result
We'll use a method to more easily grab a recent GRIB file. You can also download the files from their web archive here.
import urllib.request, os
def download(year, month, day, hour_out, location='./'):
print('Beginning file download')
day = str(day).zfill(2)
month = str(month).zfill(2)
hour_out = str(hour_out).zfill(3)
url = f'https://www.ncei.noaa.gov/data/global-forecast-system/access/grid-003-1.0-degree/forecast/{year}{month}/{year}{month}{day}/gfs_3_{year}{month}{day}_0000_{hour_out}.grb2'
fileName = f'./gfs_3_{year}{month}{day}_0000_{hour_out}.grb2'
try:
urllib.request.urlretrieve(url, os.path.join(location, f'gfs_3_{year}{month}{day}_0000_{hour_out}.grb2'))
except urllib.error.HTTPError:
print("Unable to download selected grib file. Please ensure that your date is in the past week. It may also take some time for more recent data to be uploaded so you may want to try an earlier date as your base value.")
return fileName
Next, let's prototype a function to send Modzy the location and GRIB data and retrieve a result.
from modzy import ApiClient
from modzy._util import file_to_bytes
client = ApiClient(base_url='https://app.modzy.com/api', api_key=API_KEY) #replace or load API_KEY with your Modzy API key
address = '4747 Bethesda Ave Suite 900, Bethesda, MD'
zipcode = '20814'
location = derive_latlng(address=address)
location_string = '{"latitude": '+str(location[0]['data']['latitude'])+',"longitude": '+str(location[0]['data']['longitude'])+'}'
sources = {}
sources["test-input"] = {
"location.json": location_string.encode('utf-8'),
"input.grb2": file_to_bytes('data/input.grb2'),
}
job = client.jobs.submit_embedded("clqv1dz0cq", "0.0.1", sources)
results = client.results.block_until_complete(job)
Which will result in a dictionary like:
output = {
"status": "SUCCESSFUL",
"engine": "model-batch-clqv1dz0cq-0-0-1-5f85cc5c64-bbng2",
"inputFetching": 7424,
"outputUploading": None,
"modelLatency": 8030,
"queueTime": 323066,
"startTime": "2021-09-28T18:56:50.660+0000",
"updateTime": "2021-09-28T18:57:15.565+0000",
"endTime": "2021-09-28T18:57:15.565+0000",
"results.json": {
"ghi": 84.04,
"dni": 97.72,
"dhi": 26.41
},
"voting": {
"up": 0,
"down": 0
}
}
A final step would be to take the GHI, DNI and DHI information and calculate a 'score' appropriate to your end use application, such as Watts generated.
Final Dish

When the prototype code is working to your liking, wrap it into a Flask App, as we have done in the Solar Score Flask Repo to run using a basic web interface. Start this app following the directions here using the Flask Repo as your starting point.

Basic web interface with Leaflet.js map
Models Used

Using a current "GRIB" file with forecast data including cloud cover, temperature, downward short wave radiation flux and wind data, and more, this trained random forest model then predicts irradiance labels of GHI, DHI and DNI used to calculate a total solar score.
Updated over 1 year ago