Solar Energy Score

Calculate DHI, GHI and DNI for an address

:rocket: 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.

695

Example solar prediction for a location.

:fork-and-knife: Ingredients

:books: 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.

:cake: 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.

695

Basic web interface with Leaflet.js map

:8ball: 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.