weather_checker / app.py
Alex Stoken
remove auth again
f7e01dc
import requests
import pandas as pd
import datetime
import time
import gradio as gr
import os
###########
# other API's of interest: https://medium.com/@imdipto/best-free-alternatives-to-the-wunderground-weather-api-21acb22450e6
##########
OPENWEATHER_API_KEY = os.environ.get('OPENWEATHER_API_KEY')
WEATHERAPI_KEY = os.environ.get('WEATHERAPI_KEY')
def openweather_to_result(lat, lon, gmt_time):
"""
API docs: https://openweathermap.org/api/one-call-api#current
Parameters
------------
lat [float]: decimal valued latitude
lon [float]: decimal valued longitude
gmt_time [datetime object]: time of desired forecast, in gmt and as python datetime object
Returns
--------
cloud_pct Tuple(List, List): list of cloud percent and corresponding time for times within 1.5 hours of input GMT time
"""
exclude_parts = 'current,minutely,daily,alerts'
request_url = f'https://api.openweathermap.org/data/2.5/onecall?lat={lat}&lon={lon}&exclude={exclude_parts}&appid={OPENWEATHER_API_KEY}'
response = requests.get(request_url)
data = response.json()
cloud_pct = []
forecast_times = []
# timeframe around input time to check cloud % for
timeframe = datetime.timedelta(hours=1, minutes=30)
for hour in data['hourly']:
# dt property is unix utc time of forecasted data - convert this to python datetime object
forecast_time = datetime.datetime.fromtimestamp(
hour['dt'], tz=datetime.timezone.utc)
if abs(forecast_time - gmt_time) <= timeframe:
# cloud pct is stored in each hour at top level
cloud_pct.append(hour['clouds'])
forecast_times.append(forecast_time)
return cloud_pct, forecast_times
def weatherapi_to_result(lat, lon, gmt_time):
"""
API docs: https://www.weatherapi.com/docs/
TODO: implement wrapper instead https://github.com/weatherapicom/weatherapi-Python
Parameters
------------
lat [float]: decimal valued latitude
lon [float]: decimal values longitude
gmt_time [datetime object]: time of desired forecast, in gmt and as python datetime object
Returns
--------
cloud_pct Tuple(List, List): list of cloud percent and corresponding time for times within 1.5 hours of input GMT time
"""
request_url = f'http://api.weatherapi.com/v1/forecast.json?key={WEATHERAPI_KEY}&q={lat},{lon}&days=2&alerts=no'
response = requests.get(request_url)
data = response.json()
timezone = data['location']['tz_id']
cloud_pct = []
forecast_times = []
# quick error handling to make sure input time python object has "timezone" property attached
try:
gmt_time = gmt_time.astimezone(datetime.timezone.utc)
except:
gmt_time = gmt_time.tz_localize('utc')
# timeframe around input time to check cloud % for
timeframe = datetime.timedelta(hours=1, minutes=30)
# this api is first divided into days, then hours
for day in data['forecast']['forecastday']:
for hour in day['hour']:
# time_epoch contains unix epoch time in GMT/UTC
#forecast_time = datetime.datetime.fromtimestamp(hour['time_epoch'], ZoneInfo(timezone))
forecast_time = datetime.datetime.fromtimestamp(
hour['time_epoch'], datetime.timezone.utc)
if abs(forecast_time - gmt_time) <= timeframe:
cloud_pct.append(hour['cloud'])
forecast_times.append(
forecast_time.astimezone(datetime.timezone.utc))
return cloud_pct, forecast_times
def met_to_result(lat, lon, gmt_time):
"""
API doc: https://api.met.no/weatherapi/locationforecast/2.0/documentation
How to: https://api.met.no/doc/locationforecast/HowTO
Parameters
------------
lat [float]: decimal valued latitude
lon [float]: decimal values longitude
gmt_time [datetime object]: time of desired forecast, in gmt and as python datetime object
Returns
--------
cloud_pct Tuple(List, List): list of cloud percent and corresponding time for times within 1.5 hours of input GMT time
"""
# set user agent https://stackoverflow.com/questions/10606133/sending-user-agent-using-requests-library-in-python
# must be unique per API Terms of Service https://api.met.no/doc/TermsOfService
headers = {
'User-Agent': 'NASAEarthScienceRemoteSensingUnit alex.h.stoken@nasa.gov'}
request_url = f'https://api.met.no/weatherapi/locationforecast/2.0/compact?lat={lat}&lon={lon}'
response = requests.get(request_url, headers=headers)
data = response.json()
cloud_pct = []
forecast_times = []
# timeframe around input time to check cloud % for
timeframe = datetime.timedelta(hours=1, minutes=30)
# walk through json return
for hour in data['properties']['timeseries']:
# time is utc formatted time https://api.met.no/doc/locationforecast/FAQ
forecast_time = datetime.datetime.strptime(
hour['time'], '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=datetime.timezone.utc)
# check if time of forecast is withing "timeframe" of desired time
if abs(forecast_time - gmt_time) <= timeframe:
# grab cloud pct from location within the nested json, add to list
cloud_pct.append(hour['data']['instant']
['details']['cloud_area_fraction'])
# add time of forecast to list. Should be an "on the hour" time
forecast_times.append(forecast_time)
return cloud_pct, forecast_times
################
# generate text
################
def file_to_cloud_listing(input_file, services):
"""
Args:
input_file (Union[str, gradio FileType]): input csv file with LAT, LON, SITE, GMT cols
services (List): list of weather api servies to check
Returns:
str: formatted string with weather predictions for locations
"""
# this works if the input is from gradio. Then the file has an name property
try:
sites = pd.read_csv(input_file.name, parse_dates=['GMT'])
using_gradio = True
except:
# this is for input from a script or command line
sites = pd.read_csv(input_file, parse_dates=['GMT'])
using_gradio = False
start = time.perf_counter()
date_format = "%H:%M"
text = ''
# each row is a site. Get weather data and then print it for each service for each site.
for row_idx, row in sites.iterrows():
#time_of_interest = datetime.datetime.strptime(row.GMT, '%m/%d/%y %H:%M')
text += check_row(row, services, date_format)
text += f'{"="*60}\n'
return text
def check_row(row, services, date_format="%H:%M"):
"""Check a row of data (a pd.Series with LAT, LON, GMT, SITE cols)
Args:
row (pd.Series): pd.Series with LAT, LON, GMT, SITE cols)
services (List): List of weather services (['OpenWeather', 'MET (Norwegian)', 'WeatherAPI'] or subset)
date_format (str, optional): Format for printing time of site pass over. Defaults to "%H:%M".
Returns:
str: formatted str of text for weather vals
"""
text = ""
text += f'{"Location":13}:\t\t{row.SITE} @ {row["GMT"].strftime(date_format)} GMT\n'
if not isinstance(row.GMT, datetime.datetime):
GMT = row["GMT"].to_pydatetime()
else:
GMT = row["GMT"]
GMT = GMT.replace(tzinfo=datetime.timezone.utc)
if 'OpenWeather' in services:
try:
cldp, times = openweather_to_result(row.LAT, row.LON, GMT)
text += format_cldp_and_time("OpenWeather", cldp=cldp, times=times)
except Exception as e:
text += f'OpenWeather:\t\tError {e} in API processing\n'
if 'MET (Norwegian)' in services:
try:
cldp, times = met_to_result(row.LAT, row.LON, GMT)
text += format_cldp_and_time("Norwegian", cldp=cldp)
except Exception as e:
text += f'Norwegian:\t\tError {e} in API processing\n'
if 'WeatherAPI' in services:
try:
cldp, times = weatherapi_to_result(row.LAT, row.LON, GMT)
text += format_cldp_and_time("WeatherAPI", cldp=cldp)
except Exception as e:
text += f'WeatherAPI:\t\tError {e} in API processing\n'
return text
def format_cldp_and_time(api_name, cldp, times=None):
"""Formats output text for lists of cloud percents and forecast times
Args:
api_name ([type]): Name of weather source.
cldp (List): List of floating point cloud percentage values.
times (List, optional): List of forecast times, as datetime objects. Defaults to None.
Returns:
str: formatted text for printing
"""
text = ''
date_format = "%H:%M"
if times is not None:
text += f'{"Forecast Time:":13}\t\t' + ' '.join(time.strftime(date_format)
for time in times) + "\n"
text += f'{api_name:13}:\t\t{" ".join(f"{p:<6.0f}" for p in cldp)}\n'
return text
inputs = [gr.inputs.File(label='Site File with Lat/Lon and GMT Time'), gr.inputs.CheckboxGroup(label='Weather Services',
choices=['OpenWeather', 'MET (Norwegian)', 'WeatherAPI'], default=['OpenWeather', 'MET (Norwegian)'])]
outputs = gr.outputs.Textbox(label ='Cloud % for hour before, hour of, hour after')
css = """* {font-family: "Lucida Console", "Courier New", monospace !important;/* <-- fonts */
}"""
gr.Interface(fn=file_to_cloud_listing, inputs=inputs, css=css, outputs=outputs,
allow_screenshot=False).launch()