File size: 9,769 Bytes
ca487d2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
038883f
ca487d2
038883f
 
83e504d
bb3e606
038883f
f7e01dc
ca487d2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
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 [email protected]'}

    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()