diff --git a/weather.noaa/addon.xml b/weather.noaa/addon.xml index 9deda6e87..411faf580 100644 --- a/weather.noaa/addon.xml +++ b/weather.noaa/addon.xml @@ -1,5 +1,5 @@ - + diff --git a/weather.noaa/changelog.txt b/weather.noaa/changelog.txt index 9aa3f519f..60561bb43 100644 --- a/weather.noaa/changelog.txt +++ b/weather.noaa/changelog.txt @@ -1,3 +1,7 @@ +v3.0.1 +- refactor to move all logic out of default.py +- fixes to wind chill + v2.0.13 - enable location lookup by address via census.gov diff --git a/weather.noaa/default.py b/weather.noaa/default.py index ab1db1af6..c5faaaaae 100644 --- a/weather.noaa/default.py +++ b/weather.noaa/default.py @@ -1,1204 +1,19 @@ # -*- coding: utf-8 -*- -#from __future__ import unicode_literals -#from future import standard_library -#standard_library.install_aliases() - -import os, glob, sys, time, re -import xbmc, xbmcgui, xbmcvfs, xbmcaddon -import datetime - -from resources.lib.utils import FtoC, CtoF, log, ADDON, LANGUAGE, MAPSECTORS, LOOPSECTORS, MAPTYPES -from resources.lib.utils import WEATHER_CODES, FORECAST, WIND_DIR, SPEEDUNIT, zip_x -from resources.lib.utils import FEELS_LIKE_F_MPH, FEELS_LIKE_C_KPH, WIND_CHILL_F_MPH, WIND_CHILL_C_KPH, HEAT_INDEX_F, HEAT_INDEX_C -from resources.lib.utils import get_url_JSON, get_url_image -from resources.lib.utils import get_month, get_timestamp, get_weekday, get_time -from dateutil.parser import parse - -WEATHER_WINDOW = xbmcgui.Window(12600) -WEATHER_ICON = xbmcvfs.translatePath('%s.png') -DATEFORMAT = xbmc.getRegion('dateshort') -TIMEFORMAT = xbmc.getRegion('meridiem') -MAXDAYS = 10 -TEMPUNIT = xbmc.getRegion('tempunit') -SOURCEPREF = ADDON.getSetting("DataSourcePreference") - -def set_property(name, value): - WEATHER_WINDOW.setProperty(name, value) - -def clear_property(name): - WEATHER_WINDOW.clearProperty(name) - -def clear(): - set_property('Current.Condition' , 'N/A') - set_property('Current.Temperature' , '0') - set_property('Current.Wind' , '0') - set_property('Current.WindDirection' , 'N/A') - set_property('Current.Humidity' , '0') - set_property('Current.FeelsLike' , '0') - set_property('Current.UVIndex' , '0') - set_property('Current.DewPoint' , '0') - set_property('Current.OutlookIcon' , 'na.png') - set_property('Current.FanartCode' , 'na') - for count in range (0, MAXDAYS+1): - set_property('Day%i.Title' % count, 'N/A') - set_property('Day%i.HighTemp' % count, '0') - set_property('Day%i.LowTemp' % count, '0') - set_property('Day%i.Outlook' % count, 'N/A') - set_property('Day%i.OutlookIcon' % count, 'na.png') - set_property('Day%i.FanartCode' % count, 'na') - -def refresh_locations(): - locations = 0 - for count in range(1, 6): - LatLong = ADDON.getSetting('Location%sLatLong' % count) - loc_name = ADDON.getSetting('Location%s' % count) - if LatLong: - locations += 1 - if not loc_name: - loc_name = 'Location %s' % count - set_property('Location%s' % count, loc_name) - - else: - set_property('Location%s' % count, '') - - #set_property('Location%s' % count, loc_name) - - set_property('Locations', str(locations)) - log('available locations: %s' % str(locations)) - - -def code_from_icon(icon): - if icon: - #xbmc.log('icon: %s' % (icon) ,level=xbmc.LOGDEBUG) - - - daynight="day" - - #special handling of forecast.weather.gov "dualimage" icon generator urls - #https://forecast.weather.gov/DualImage.php?i=bkn&j=shra&jp=30 - #https://forecast.weather.gov/DualImage.php?i=shra&j=bkn&ip=30 - if 'DualImage' in icon: -# xbmc.log('icon: %s' % icon,level=xbmc.LOGERROR) - - params = icon.split("?")[1].split("&") -# xbmc.log('params: %s' % params,level=xbmc.LOGERROR) - - code="day" - rain=None - for param in params: -# xbmc.log('param: %s' % param,level=xbmc.LOGERROR) - - thing=param.split("=") - p=thing[0] - v=thing[1] -# xbmc.log('p: %s' % p,level=xbmc.LOGERROR) -# xbmc.log('v: %s' % v,level=xbmc.LOGERROR) - if p == "i": - code="%s/%s" % ("day",v) - if p == "ip" or p == "jp": - if rain is None or v > rain: - rain=v -# xbmc.log('code: %s' % code,level=xbmc.LOGERROR) -# xbmc.log('rain: %s' % rain,level=xbmc.LOGERROR) - - return code, rain - - - if '?' in icon: - icon=icon.rsplit('?', 1)[0] - - # strip off file extension if we have one - icon=icon.replace(".png","") - icon=icon.replace(".jpg","") - - if "/day/" in icon: - daynight="day" - elif "/night/" in icon: - daynight="night" - - rain = None - code = None - # loop though our "split" icon paths, and get max rain percent - # take last icon code in the process - for checkcode in icon.rsplit('/'): - thing=checkcode.split(",") - code="%s/%s" % (daynight,thing[0]) - - if len(thing) > 1: - train=thing[1] - if rain is None or train > rain: - rain=train - - # forcast.gov urls may have codes like sct30, which means "scattered clouds 30% chance of rain" ,so regex for it - cresult = re.search(r"([a-z]+)(\d*)", thing[0]) - if cresult and cresult.group(1): - code="%s/%s" % (daynight,cresult.group(1)) - if cresult and cresult.group(2): - train=cresult.group(2) - if rain is None or train > rain: - rain=train - -# xbmc.log('code: %s' % code,level=xbmc.LOGERROR) -# xbmc.log('rain: %s' % rain,level=xbmc.LOGERROR) - - return code, rain - - - -def get_lat_long_by_address(num): - - dialog = xbmcgui.Dialog() - saddress=dialog.input(heading=LANGUAGE(32345),defaultt='',type=xbmcgui.INPUT_ALPHANUM) - saddress=saddress.replace(" ", "+") - url="https://geocoding.geo.census.gov/geocoder/locations/onelineaddress?address=%s&benchmark=4&format=json" % (saddress) - - data=get_url_JSON(url) - - ##xbmc.log('DEBUG data== %s' % data,level=xbmc.LOGERROR) - - - if data and 'result' in data and 'addressMatches' in data['result'] and len(data['result']['addressMatches']) > 0 : - addresslist=[] - addresses={} - for count,item in enumerate(data['result']['addressMatches']): - locx=round(item['coordinates']['x'],4) - locy=round(item['coordinates']['y'],4) - locfull=str(locy) + ',' + str(locx) - address=item['matchedAddress'] - addresslist.append(address) - addresses[address]=locfull - - dialog = xbmcgui.Dialog() - i=dialog.select(LANGUAGE(32348),addresslist) - # clean up reference to dialog object - del dialog - if i >= 0: - LatLong=addresses[addresslist[i]] - ADDON.setSetting("Location"+num+"Address",addresslist[i]) - ADDON.setSetting("Location"+num+"LatLong",LatLong) - get_Stations(num,LatLong,True) - else: - dialog = xbmcgui.Dialog() - dialog.ok(heading=LANGUAGE(32346),message=LANGUAGE(32347)) - del dialog - return - - - -######################################################################################## -## Dialog for getting Latitude and Longitude -######################################################################################## -def enterLocation(num): -## log("argument: %s" % (sys.argv[1])) - - text = ADDON.getSetting("Location"+num+"LatLong") - Latitude="" - Longitude="" - if text and "," in text: - thing=text.split(",") - Latitude=thing[0] - Longitude=thing[1] - - dialog = xbmcgui.Dialog() - - Latitude=dialog.input(LANGUAGE(32341),defaultt=Latitude,type=xbmcgui.INPUT_ALPHANUM) - - if not Latitude: - ADDON.setSetting("Location"+num+"LatLong","") - return False - - Longitude=dialog.input(heading=LANGUAGE(32342),defaultt=Longitude,type=xbmcgui.INPUT_ALPHANUM) - - if not Longitude: - ADDON.setSetting("Location"+num+"LatLong","") - return False - LatLong=Latitude+","+Longitude - ADDON.setSetting("Location"+num+"LatLong",LatLong) - get_Stations(num,LatLong,True) - return +import sys +from lib import noaa ######################################################################################## -## fetches location data (weather grid point, station, etc, for lattitude,logngitude -## returns url for fetching local weather stations +## Main Kodi entry point ######################################################################################## -def get_Points(num,LatLong,resetName=False): - - prefix="Location"+num - log('searching for location: %s' % LatLong) - url = 'https://api.weather.gov/points/%s' % LatLong - log("url:"+url) - data=get_url_JSON(url) - log('location data: %s' % data) - if not data: - log('failed to retrieve location data') - return None - if data and 'properties' in data: - - if resetName: - city = data['properties']['relativeLocation']['properties']['city'] - state = data['properties']['relativeLocation']['properties']['state'] - locationName= city+", "+state - ADDON.setSetting(prefix, locationName) - - gridX=data['properties']['gridX'] - ADDON.setSetting(prefix+'gridX',str(gridX)) - - gridY=data['properties']['gridY'] - ADDON.setSetting(prefix+'gridY',str(gridY)) - - cwa=data['properties']['cwa'] - ADDON.setSetting(prefix+'cwa', cwa) - - forecastZone=data['properties']['forecastZone'] - zone=forecastZone.rsplit('/',1)[1] - ADDON.setSetting(prefix+'Zone', zone) - - forecastCounty=data['properties']['county'] - county=forecastCounty.rsplit('/',1)[1] - ADDON.setSetting(prefix+'County', county) - - forecastGridData_url = data['properties']['forecastGridData'] - ADDON.setSetting(prefix+'forecastGrid_url', forecastGridData_url) - - forecastHourly_url = data['properties']['forecastHourly'] - ADDON.setSetting(prefix+'forecastHourly_url', forecastHourly_url) - - forecast_url = data['properties']['forecast'] - ADDON.setSetting(prefix+'forecast_url', forecast_url) - - radarStation = data['properties']['radarStation'] - ADDON.setSetting(prefix+'radarStation', radarStation) - - - #current_datetime = parse("now") - current_datetime = datetime.datetime.now() - ADDON.setSetting(prefix+'lastPointsCheck', str(current_datetime)) - - - stations_url = data['properties']['observationStations'] - return stations_url - -######################################################################################## -## fetches location data (weather grid point, station, etc, for lattitude,logngitude -######################################################################################## - -def get_Stations(num,LatLong,resetName=False): - - prefix="Location"+num - odata=None - stations_url=get_Points(num,LatLong,resetName) - if stations_url: - odata = get_url_JSON(stations_url) - - if odata and 'features' in odata: - stations={} - stationlist=[] - - for count,item in enumerate(odata['features']): - stationId=item['properties']['stationIdentifier'] - stationName=item['properties']['name'] - stationlist.append(stationName) - stations[stationName]=stationId - - dialog = xbmcgui.Dialog() - i=dialog.select(LANGUAGE(32331),stationlist) - # clean up reference to dialog object - del dialog - - ADDON.setSetting(prefix+'Station',stations[stationlist[i]]) - ADDON.setSetting(prefix+'StationName',stationlist[i]) - - - - -######################################################################################## -## fetches daily weather data -######################################################################################## - -def fetchDaily(num): - - log("SOURCEPREF: %s" % SOURCEPREF) - url=ADDON.getSetting('Location'+str(num)+'forecast_url') - if "preview-api.weather.gov" == SOURCEPREF: - url=url.replace("https://api.weather.gov","https://preview-api.weather.gov") - - if 'F' in TEMPUNIT: - url="%s?units=us" % url - elif 'C' in TEMPUNIT: - url="%s?units=si" % url - - log('forecast url: %s' % url) - - daily_weather = get_url_JSON(url) - - if daily_weather and 'properties' in daily_weather: - data=daily_weather['properties'] - else: - #api.weather.gov is acting up, so fall back to alternate api - xbmc.log('failed to find weather data from : %s' % url,level=xbmc.LOGERROR) - xbmc.log('%s' % daily_weather,level=xbmc.LOGERROR) - return fetchAltDaily(num) - - for count, item in enumerate(data['periods'], start=0): - icon = item['icon'] - #https://api.weather.gov/icons/land/night/ovc?size=small - if icon and '?' in icon: - icon=icon.rsplit('?', 1)[0] - code, rain=code_from_icon(icon) - - weathercode = WEATHER_CODES.get(code) - starttime=item['startTime'] - startstamp=get_timestamp(starttime) - set_property('Day%i.isDaytime' % (count),str(item['isDaytime'])) - set_property('Day%i.Title' % (count), item['name']) - - if item['isDaytime'] == True: - ##Since we passed units into api, we may need to convert to C, or may not - if 'F' in TEMPUNIT: - set_property('Day%i.HighTemp' % (count), str(int(round(FtoC(item['temperature']))))) - set_property('Day%i.LowTemp' % (count), str(int(round(FtoC(item['temperature']))))) - elif 'C' in TEMPUNIT: - set_property('Day%i.HighTemp' % (count), str(int(round(item['temperature'])))) - set_property('Day%i.LowTemp' % (count), str(int(round(item['temperature'])))) - if item['isDaytime'] == False: - if 'F' in TEMPUNIT: - set_property('Day%i.HighTemp' % (count), str(int(round(FtoC(item['temperature']))))) - set_property('Day%i.LowTemp' % (count), str(int(round(FtoC(item['temperature']))))) - elif 'C' in TEMPUNIT: - set_property('Day%i.HighTemp' % (count), str(int(round(item['temperature'])))) - set_property('Day%i.LowTemp' % (count), str(int(round(item['temperature'])))) - set_property('Day%i.Outlook' % (count), item['shortForecast']) - set_property('Day%i.FanartCode' % (count), weathercode) - set_property('Day%i.OutlookIcon'% (count), WEATHER_ICON % weathercode) - set_property('Day%i.RemoteIcon' % (count), icon) - - # NOTE: Day props are 0 based, but Daily/Hourly are 1 based - set_property('Daily.%i.isDaytime' % (count+1),str(item['isDaytime'])) - set_property('Daily.%i.Outlook' % (count+1), item['shortForecast']) - set_property('Daily.%i.ShortOutlook' % (count+1), item['shortForecast']) - set_property('Daily.%i.DetailedOutlook' % (count+1), item['detailedForecast']) - - set_property('Daily.%i.RemoteIcon' % (count+1), icon) - set_property('Daily.%i.OutlookIcon' % (count+1), WEATHER_ICON % weathercode) - set_property('Daily.%i.FanartCode' % (count+1), weathercode) - set_property('Daily.%i.WindDirection' % (count+1), item['windDirection']) - set_property('Daily.%i.WindSpeed' % (count+1), item['windSpeed']) - - if item['isDaytime'] == True: - set_property('Daily.%i.LongDay' % (count+1), item['name']) - set_property('Daily.%i.ShortDay' % (count+1), get_weekday(startstamp,'s')+" (d)") - #set_property('Daily.%i.TempDay' % (count+1), u'%i\N{DEGREE SIGN}%s' % (item['temperature'], item['temperatureUnit'])) - #set_property('Daily.%i.HighTemperature' % (count+1), u'%i\N{DEGREE SIGN}%s' % (item['temperature'], item['temperatureUnit'])) - - ## we passed units to api, so we got back C or F, so don't need to convert - set_property('Daily.%i.TempDay' % (count+1), u'%s%s' % (item['temperature'], TEMPUNIT)) - set_property('Daily.%i.HighTemperature' % (count+1), u'%s%s' % (item['temperature'], TEMPUNIT)) - set_property('Daily.%i.TempNight' % (count+1), '') - set_property('Daily.%i.LowTemperature' % (count+1), '') - - if item['isDaytime'] == False: - set_property('Daily.%i.LongDay' % (count+1), item['name']) - set_property('Daily.%i.ShortDay' % (count+1), get_weekday(startstamp,'s')+" (n)") - - set_property('Daily.%i.TempDay' % (count+1), '') - set_property('Daily.%i.HighTemperature' % (count+1), '') - ## we passed units to api, so we got back C or F, so don't need to convert - set_property('Daily.%i.TempNight' % (count+1), u'%s%s' % (item['temperature'], TEMPUNIT)) - set_property('Daily.%i.LowTemperature' % (count+1), u'%s%s' % (item['temperature'], TEMPUNIT)) - - if DATEFORMAT[1] == 'd' or DATEFORMAT[0] == 'D': - set_property('Daily.%i.LongDate' % (count+1), get_month(startstamp, 'dl')) - set_property('Daily.%i.ShortDate' % (count+1), get_month(startstamp, 'ds')) - else: - set_property('Daily.%i.LongDate' % (count+1), get_month(startstamp, 'ml')) - set_property('Daily.%i.ShortDate' % (count+1), get_month(startstamp, 'ms')) - - rain=0 - if item['probabilityOfPrecipitation'] and item['probabilityOfPrecipitation']['value'] : - rain=item['probabilityOfPrecipitation']['value'] - - if rain and str(rain) and not "0" == str(rain): - set_property('Daily.%i.ChancePrecipitation' % (count+1), str(rain) + '%') - else: - ##set_property('Daily.%i.ChancePrecipitation' % (count+1), '') - clear_property('Daily.%i.ChancePrecipitation' % (count+1)) - - - -######################################################################################## -## fetches daily weather data using alternative api endpoint -######################################################################################## - - -def fetchAltDaily(num): - - latlong=ADDON.getSetting('Location'+str(num)+"LatLong") - latitude =latlong.rsplit(',',1)[0] - longitude=latlong.rsplit(',',1)[1] - - url="https://forecast.weather.gov/MapClick.php?lon="+longitude+"&lat="+latitude+"&FcstType=json" - log('forecast url: %s' % url) - - daily_weather = get_url_JSON(url) - - if daily_weather and 'data' in daily_weather: - - dailydata=[ - {"startPeriodName": a, - "startValidTime": b, - "tempLabel": c, - "temperature": d, - "pop": e, - "weather": f, - "iconLink": g, - "hazard": h, - "hazardUrl": i, - "text": j - } - for a,b,c,d,e,f,g,h,i,j in zip_x(None, - daily_weather['time']['startPeriodName'], - daily_weather['time']['startValidTime'], - daily_weather['time']['tempLabel'], - daily_weather['data']['temperature'], - daily_weather['data']['pop'], - daily_weather['data']['weather'], - daily_weather['data']['iconLink'], - daily_weather['data']['hazard'], - daily_weather['data']['hazardUrl'], - daily_weather['data']['text'] - )] - - else: - xbmc.log('failed to retrieve weather data from : %s' % url,level=xbmc.LOGERROR) - xbmc.log('%s' % daily_weather,level=xbmc.LOGERROR) - return None - - for count, item in enumerate(dailydata, start=0): - icon = item['iconLink'] - - #https://api.weather.gov/icons/land/night/ovc?size=small - code, ignoreme = code_from_icon(icon) - weathercode = WEATHER_CODES.get(code) - - starttime=item['startValidTime'] - startstamp=get_timestamp(starttime) - set_property('Day%i.Title' % (count), item['startPeriodName']) - - set_property('Day%i.Outlook' % (count), item['weather']) - set_property('Day%i.Details' % (count), item['text']) - - set_property('Day%i.OutlookIcon' % (count), WEATHER_ICON % weathercode) - set_property('Day%i.RemoteIcon' % (count), icon) - set_property('Day%i.FanartCode' % (count), weathercode) - - # NOTE: Day props are 0 based, but Daily/Hourly are 1 based - set_property('Daily.%i.DetailedOutlook' % (count+1), item['text']) - set_property('Daily.%i.Outlook' % (count+1), item['weather']) - set_property('Daily.%i.ShortOutlook' % (count+1), item['weather']) - - set_property('Daily.%i.OutlookIcon' % (count+1), WEATHER_ICON % weathercode) - set_property('Daily.%i.RemoteIcon' % (count+1), icon) - set_property('Daily.%i.FanartCode' % (count+1), weathercode) - - if item['tempLabel'] == 'High': - set_property('Daily.%i.LongDay' % (count+1), item['startPeriodName']) - set_property('Daily.%i.ShortDay' % (count+1), get_weekday(startstamp,'s')+" (d)") - - set_property('Daily.%i.TempNight' % (count+1), '') - set_property('Daily.%i.LowTemperature' % (count+1), '') - if 'F' in TEMPUNIT: - set_property('Daily.%i.TempDay' % (count+1), u'%s%s' % (int(round(item['temperature'])), TEMPUNIT)) - set_property('Daily.%i.HighTemperature' % (count+1), u'%s%s' % (int(round(item['temperature'])), TEMPUNIT)) - elif 'C' in TEMPUNIT: - set_property('Daily.%i.TempDay' % (count+1), u'%s%s' % (int(round(FtoC(item['temperature']))), TEMPUNIT)) - set_property('Daily.%i.HighTemperature' % (count+1), u'%s%s' % (int(round(FtoC(item['temperature']))), TEMPUNIT)) - - if item['tempLabel'] == 'Low': - set_property('Daily.%i.LongDay' % (count+1), item['startPeriodName']) - set_property('Daily.%i.ShortDay' % (count+1), get_weekday(startstamp,'s')+" (n)") - - set_property('Daily.%i.TempDay' % (count+1), '') - set_property('Daily.%i.HighTemperature' % (count+1), '') - if 'F' in TEMPUNIT: - set_property('Daily.%i.TempNight' % (count+1), u'%s%s' % (int(round(item['temperature'])), TEMPUNIT)) - set_property('Daily.%i.LowTemperature' % (count+1), u'%s%s' % (int(round(item['temperature'])), TEMPUNIT)) - elif 'C' in TEMPUNIT: - set_property('Daily.%i.TempNight' % (count+1), u'%s%s' % (int(round(FtoC(item['temperature']))), TEMPUNIT)) - set_property('Daily.%i.LowTemperature' % (count+1), u'%s%s' % (int(round(FtoC(item['temperature']))), TEMPUNIT)) - - if DATEFORMAT[1] == 'd' or DATEFORMAT[0] == 'D': - set_property('Daily.%i.LongDate' % (count+1), get_month(startstamp, 'dl')) - set_property('Daily.%i.ShortDate' % (count+1), get_month(startstamp, 'ds')) - else: - set_property('Daily.%i.LongDate' % (count+1), get_month(startstamp, 'ml')) - set_property('Daily.%i.ShortDate' % (count+1), get_month(startstamp, 'ms')) - - rain = item['pop'] - if rain and str(rain) and not "0" == str(rain): - set_property('Daily.%i.ChancePrecipitation' % (count+1), str(rain) + '%') - else: - ##set_property('Daily.%i.ChancePrecipitation' % (count+1), '') - clear_property('Daily.%i.ChancePrecipitation' % (count+1)) - - - - - if daily_weather and 'currentobservation' in daily_weather: - data=daily_weather['currentobservation'] - icon = "http://forecast.weather.gov/newimages/large/%s" % data.get('Weatherimage') - code, rain = code_from_icon(icon) - weathercode = WEATHER_CODES.get(code) - - set_property('Current.Location', data.get('name')) - set_property('Current.RemoteIcon',icon) - set_property('Current.OutlookIcon', '%s.png' % weathercode) # xbmc translates it to Current.ConditionIcon - set_property('Current.FanartCode', weathercode) - set_property('Current.Condition', FORECAST.get(data.get('Weather'), data.get('Weather'))) - set_property('Current.Humidity' , str(data.get('Relh'))) - set_property('Current.DewPoint', str(int(round(FtoC(data.get('Dewp')))))) - - try: - temp=data.get('Temp') - set_property('Current.Temperature',str(int(round(FtoC(temp))))) - except: - #set_property('Current.Temperature','') - clear_property('Current.Temperature') - - try: - set_property('Current.Wind', str(round(float(data.get('Winds'))*1.609298167))) - except: - #set_property('Current.Wind','') - clear_property('Current.Wind') - - try: - set_property('Current.WindDirection', xbmc.getLocalizedString(WIND_DIR(int(data.get('Windd'))))) - except: - #set_property('Current.WindDirection', '') - clear_property('Current.WindDirection') - -# try: -# set_property('Current.WindGust' , str(SPEED(float(data.get('Gust'))/2.237)) + SPEEDUNIT) -# except: -# clear_property('Current.WindGust') -# ##set_property('Current.WindGust' , '') - - if rain and str(rain) and not "0" == str(rain): - set_property('Current.ChancePrecipitation', str(rain)+'%') - else : - clear_property('Current.ChancePrecipitation') - - # calculate feels like - clear_property('Current.FeelsLike') - try: - wind=data.get('Winds') - if not wind: - wind=0 - feelslike = FEELS_LIKE_C_KPH( FtoC(data.get('Temp')), float(wind)/2.237, int(data.get('Relh'))) - if feelslike: - set_property('Current.FeelsLike', str(int(round(feelslike)))) - else: - clear_property('Current.FeelsLike') - except: - clear_property('Current.FeelsLike') - #set_property('Current.FeelsLike', '') - -# # if we have windchill or heatindex directly, then use that instead -# if data.get('WindChill') and not "NA" == data.get('WindChill'): -# set_property('Current.FeelsLike', str(FtoC(data.get('WindChill'))) ) -# if data.get('HeatIndex') and not "NA" == data.get('HeatIndex'): -# set_property('Current.FeelsLike', str(FtoC(data.get('HeatIndex'))) ) - - - - -######################################################################################## -## fetches current weather info for location -######################################################################################## - -def fetchCurrent(num): - station=ADDON.getSetting('Location'+str(num)+'Station') - url="https://api.weather.gov/stations/%s/observations/latest" %station - current=get_url_JSON(url) - if current and 'properties' in current: - data=current['properties'] - else: - xbmc.log('failed to find weather data from : %s' % url,level=xbmc.LOGERROR) - xbmc.log('%s' % current,level=xbmc.LOGERROR) - return - - icon = data['icon'] - #https://api.weather.gov/icons/land/night/ovc?size=small - code = None - rain = None - if icon: - if '?' in icon: - icon=icon.rsplit('?', 1)[0] - code, rain = code_from_icon(icon) - weathercode = WEATHER_CODES.get(code) - set_property('Current.RemoteIcon',icon) - set_property('Current.OutlookIcon', '%s.png' % weathercode) # xbmc translates it to Current.ConditionIcon - set_property('Current.FanartCode', weathercode) - - set_property('Current.Condition', FORECAST.get(data.get('textDescription'), data.get('textDescription'))) - try: - set_property('Current.Humidity' , str(round(data.get('relativeHumidity').get('value')))) - except: - ##set_property('Current.Humidity' , '') - clear_property('Current.Humidity') - - try: - temp=int(round(data.get('temperature').get('value'))) - set_property('Current.Temperature',str(temp)) # api values are in C - except: - ##set_property('Current.Temperature','') - clear_property('Current.Temperature') - try: - set_property('Current.Wind', str(int(round(data.get('windSpeed').get('value'))))) - except: - ##set_property('Current.Wind','') - clear_property('Current.Wind') - - try: - set_property('Current.WindDirection', xbmc.getLocalizedString(WIND_DIR(int(round(data.get('windDirection').get('value')))))) - except: - #set_property('Current.WindDirection', '') - clear_property('Current.WindDirection') - - if rain and str(rain) and not "0" == str(rain): - set_property('Current.ChancePrecipitation', str(rain)+'%') - else : - #set_property('Current.ChancePrecipitation', '') - clear_property('Current.ChancePrecipitation') - - clear_property('Current.FeelsLike') - #calculate feels like - windspeed=data.get('windSpeed').get('value') - if not windspeed: - windspeed=0 - - try: - feelslike=FEELS_LIKE_C_KPH(data.get('temperature').get('value'), float(windspeed), data.get('relativeHumidity').get('value')) - if feelslike: - set_property('Current.FeelsLike', int(round(feelslike))) - else: - clear_property('Current.FeelsLike') - except: - clear_property('Current.FeelsLike') - - # if we have windchill or heat index directly, then use that instead - if data.get('windChill').get('value'): - set_property('Current.FeelsLike', str(int(round(data.get('windChill').get('value')))) ) - if data.get('heatIndex').get('value'): - set_property('Current.FeelsLike', str(int(round(data.get('heatIndex').get('value')))) ) - - try: - temp=int(round(data.get('dewpoint').get('value',0))) - set_property('Current.DewPoint', str(temp)) # api values are in C - except: - set_property('Current.DewPoint', '') - - - -## extended properties - -# try: -# set_property('Current.WindGust' , SPEED(float(data.get('windGust').get('value',0))/3.6) + SPEEDUNIT) -# except: -# set_property('Current.WindGust' , '') - - try: - set_property('Current.SeaLevel' , str(data.get('seaLevelPressure').get('value',0))) - except: - set_property('Current.SeaLevel' , '') - - try: - set_property('Current.GroundLevel' ,str(data.get('barometricPressure').get('value',0))) - except: - set_property('Current.GroundLevel' , '') - - - - - -######################################################################################## -## fetches any weather alerts for location -######################################################################################## - - -def fetchWeatherAlerts(num): - - ### we could fetch alerts for either 'County', or 'Zone' - #https://api.weather.gov/alerts/active/zone/CTZ006 - #https://api.weather.gov/alerts/active/zone/CTC009 - ##https://api.weather.gov/alerts/active?status=actual&point=%7Blat%7D,%7Blong%7D - - #for now, lets use the point alert lookup, as suggested by the weather api team - - ##a_zone=ADDON.getSetting('Location'+str(num)+'County') - ##url="https://api.weather.gov/alerts/active/zone/%s" %a_zone - - # we are storing lat,long as comma separated already, so that is convienent for us and we can just drop it into the url - latlong=ADDON.getSetting('Location'+str(num)+'LatLong') - url="https://api.weather.gov/alerts/active?status=actual&point=%s" % (latlong) - - alerts=get_url_JSON(url) - # if we have a valid response then clear our current alerts - if alerts and 'features' in alerts: - for count in range (1, 10): - clear_property('Alerts.%i.event' % (count)) - else: - xbmc.log('failed to get proper alert response %s' % url,level=xbmc.LOGERROR) - xbmc.log('%s' % alerts,level=xbmc.LOGDEBUG) - return - - if 'features' in alerts and alerts['features']: - data=alerts['features'] - set_property('Alerts.IsFetched' , 'true') - else: - clear_property('Alerts.IsFetched') - xbmc.log('No current weather alerts from %s' % url,level=xbmc.LOGDEBUG) - return - - for count, item in enumerate(data, start=1): - - thisdata=item['properties'] - set_property('Alerts.%i.status' % (count), str(thisdata['status'])) - set_property('Alerts.%i.messageType' % (count), str(thisdata['messageType'])) - set_property('Alerts.%i.category' % (count), str(thisdata['category'])) - set_property('Alerts.%i.severity' % (count), str(thisdata['severity'])) - set_property('Alerts.%i.certainty' % (count), str(thisdata['certainty'])) - set_property('Alerts.%i.urgency' % (count), str(thisdata['urgency'])) - set_property('Alerts.%i.event' % (count), str(thisdata['event'])) - set_property('Alerts.%i.headline' % (count), str(thisdata['headline'])) - set_property('Alerts.%i.description' % (count), str(thisdata['description'])) - set_property('Alerts.%i.instruction' % (count), str(thisdata['instruction'])) - set_property('Alerts.%i.response' % (count), str(thisdata['response'])) - - - -######################################################################################## -## fetches hourly weather data -######################################################################################## - -def fetchHourly(num): - - log("SOURCEPREF: %s" % SOURCEPREF) - - url=ADDON.getSetting('Location'+str(num)+'forecastHourly_url') - if "preview-api.weather.gov" == SOURCEPREF: - url=url.replace("https://api.weather.gov","https://preview-api.weather.gov") - log("url-x: %s" % url) - - if 'F' in TEMPUNIT: - url="%s?units=us" % url - elif 'C' in TEMPUNIT: - url="%s?units=si" % url - - - - hourly_weather = get_url_JSON(url) - if hourly_weather and 'properties' in hourly_weather: - data=hourly_weather['properties'] - else: - xbmc.log('failed to find proper hourly weather from %s' % url,level=xbmc.LOGERROR) - return - #api is currently returning a 0 % rain icon url, which is not valid, so need to clean it - iconreplacepattern1 = re.compile(r"[,]0$") - -# extended properties - for count, item in enumerate(data['periods'], start = 0): - - icon=item['icon'] - #https://api.weather.gov/icons/land/night/ovc?size=small - if icon: - if '?' in icon: - icon=icon.rsplit('?', 1)[0] - code, rain=code_from_icon(icon) - icon=iconreplacepattern1.sub("",icon) - set_property('Hourly.%i.RemoteIcon' % (count+1), icon) - - weathercode = WEATHER_CODES.get(code) - starttime=item['startTime'] - startstamp=get_timestamp(starttime) - if DATEFORMAT[1] == 'd' or DATEFORMAT[0] == 'D': - set_property('Hourly.%i.LongDate' % (count+1), get_month(startstamp, 'dl')) - set_property('Hourly.%i.ShortDate' % (count+1), get_month(startstamp, 'ds')) - else: - set_property('Hourly.%i.LongDate' % (count+1), get_month(startstamp, 'ml')) - set_property('Hourly.%i.ShortDate' % (count+1), get_month(startstamp, 'ms')) - - set_property('Hourly.%i.Time' % (count+1), get_time(startstamp)) - if DATEFORMAT[1] == 'd' or DATEFORMAT[0] == 'D': - set_property('Hourly.%i.LongDate' % (count+1), get_month(startstamp, 'dl')) - set_property('Hourly.%i.ShortDate' % (count+1), get_month(startstamp, 'ds')) - else: - set_property('Hourly.%i.LongDate' % (count+1), get_month(startstamp, 'ml')) - set_property('Hourly.%i.ShortDate' % (count+1), get_month(startstamp, 'ms')) - - set_property('Hourly.%i.Outlook' % (count+1), FORECAST.get(item['shortForecast'], item['shortForecast'])) - set_property('Hourly.%i.ShortOutlook' % (count+1), FORECAST.get(item['shortForecast'], item['shortForecast'])) - set_property('Hourly.%i.OutlookIcon' % (count+1), WEATHER_ICON % weathercode) - set_property('Hourly.%i.FanartCode' % (count+1), weathercode) - windspeed=item['windSpeed'] - - if windspeed and (windspeed == "0 mph" or windspeed == "0 km/h"): - windspeed="" - - if windspeed and item['windDirection']: - set_property('Hourly.%i.WindDirection' % (count+1), item['windDirection']) - set_property('Hourly.%i.WindSpeed' % (count+1), windspeed) - else: - clear_property('Hourly.%i.WindDirection' % (count+1)) - clear_property('Hourly.%i.WindSpeed' % (count+1)) - - #set_property('Hourly.%i.Temperature' % (count+1), str(item['temperature'])+u'\N{DEGREE SIGN}'+item['temperatureUnit']) - - ## we passed units to api, so we got back C or F, so don't need to convert - set_property('Hourly.%i.Temperature' % (count+1), u'%s%s' % (int(round(item['temperature'])), TEMPUNIT)) - ##if 'F' in TEMPUNIT: - ## set_property('Hourly.%i.Temperature' % (count+1), u'%s%s' % (item['temperature'], TEMPUNIT)) - ##elif 'C' in TEMPUNIT: - ## set_property('Hourly.%i.Temperature' % (count+1), u'%s%s' % (FtoC(item['temperature']), TEMPUNIT)) - - - rain=0 - if item['probabilityOfPrecipitation'] and item['probabilityOfPrecipitation']['value'] : - rain=item['probabilityOfPrecipitation']['value'] - - if rain and str(rain) and not "0" == str(rain): - set_property('Hourly.%i.ChancePrecipitation' % (count+1), str(rain) + '%') - else: - clear_property('Hourly.%i.ChancePrecipitation' % (count+1)) - - humid=0 - if item['relativeHumidity'] and item['relativeHumidity']['value'] : - humid=item['relativeHumidity']['value'] - - if humid and str(humid) and not "0" == str(humid): - set_property('Hourly.%i.Humidity' % (count+1), str(humid) + '%') - else: - clear_property('Hourly.%i.Humidity' % (count+1)) - - dewpoint=0 - if item['dewpoint'] and item['dewpoint']['value'] : - dewpoint=item['dewpoint']['value'] - - if dewpoint and str(dewpoint) and not "0" == str(dewpoint): - ## API is always returning dewpoint in C rather then obeying our prefered units, so convert - if 'F' in TEMPUNIT: - set_property('Hourly.%i.DewPoint' % (count+1), u'%s%s' % (int(round(CtoF(dewpoint))), TEMPUNIT)) - elif 'C' in TEMPUNIT: - set_property('Hourly.%i.DewPoint' % (count+1), u'%s%s' % (int(round(dewpoint)), TEMPUNIT)) - else: - clear_property('Hourly.%i.DewPoint' % (count+1)) - - if 'F' in TEMPUNIT: - try: - windspeed=0 - if item['windSpeed'] and item['windSpeed'].endswith(" mph"): - windspeed=item['windSpeed'].rstrip(" mph") - feelslike=FEELS_LIKE_F_MPH(item['temperature'], windspeed, humid) - if feelslike: - set_property('Hourly.%i.FeelsLike' % (count+1), u'%s%s' % (int(round(feelslike)), TEMPUNIT)) - else: - clear_property('Hourly.%i.FeelsLike' % (count+1)) - except: - ##xbmc.log('Error Loading Feels-like %s %s %s' % (item['temperature'], windspeed, humid),level=xbmc.LOGERROR) - clear_property('Hourly.%i.FeelsLike' % (count+1)) - try: - windspeed=0 - if item['windSpeed'] and item['windSpeed'].endswith(" mph"): - windspeed=item['windSpeed'].rstrip(" mph") - windchill=WIND_CHILL_F_MPH(item['temperature'], windspeed) - if windchill: - set_property('Hourly.%i.WindChill' % (count+1), u'%s%s' % (int(round(windchill)), TEMPUNIT)) - else: - clear_property('Hourly.%i.WindChill' % (count+1)) - except: - ##xbmc.log('Error Loading %s %s %s' % (item['temperature'], windspeed, humid),level=xbmc.LOGERROR) - clear_property('Hourly.%i.WindChill' % (count+1)) - try: - heatindex=HEAT_INDEX_F(item['temperature'], humid) - if heatindex: - set_property('Hourly.%i.HeatIndex' % (count+1), u'%s%s' % (int(round(heatindex)), TEMPUNIT)) - else: - clear_property('Hourly.%i.HeatIndex' % (count+1)) - except: - ##xbmc.log('Error Loading %s %s %s' % (item['temperature'], windspeed, humid),level=xbmc.LOGERROR) - clear_property('Hourly.%i.HeatIndex' % (count+1)) - elif 'C' in TEMPUNIT: - try: - windspeed=0 - if item['windSpeed'] and item['windSpeed'].endswith(" km/h"): - windspeed=item['windSpeed'].rstrip(" km/h") - feelslike=FEELS_LIKE_C_KPH(item['temperature'], windspeed, humid) - if feelslike: - set_property('Hourly.%i.FeelsLike' % (count+1), u'%s%s' % (int(round(feelslike)), TEMPUNIT)) - else: - clear_property('Hourly.%i.FeelsLike' % (count+1)) - except: - clear_property('Hourly.%i.FeelsLike' % (count+1)) - try: - windspeed=0 - if item['windSpeed'] and item['windSpeed'].endswith(" km/h"): - windspeed=item['windSpeed'].rstrip(" km/h") - windchill=WIND_CHILL_C_KPH(item['temperature'], windspeed) - if windchill: - set_property('Hourly.%i.WindChill' % (count+1), u'%s%s' % (int(round(windchill)), TEMPUNIT)) - else: - clear_property('Hourly.%i.WindChill' % (count+1)) - except: - #xbmc.log('Error Loading %s %s %s' % (item['temperature'], windspeed, humid),level=xbmc.LOGERROR) - clear_property('Hourly.%i.WindChill' % (count+1)) - try: - heatindex=HEAT_INDEX_C(item['temperature'], humid) - if heatindex: - set_property('Hourly.%i.HeatIndex' % (count+1), u'%s%s' % (int(round(heatindex)), TEMPUNIT)) - else: - clear_property('Hourly.%i.HeatIndex' % (count+1)) - except: - #xbmc.log('Error Loading %s %s %s' % (item['temperature'], windspeed, humid),level=xbmc.LOGERROR) - clear_property('Hourly.%i.HeatIndex' % (count+1)) - - count = 1 - - -######################################################################################## -## Grabs map selection from user in settings -######################################################################################## - -def mapSettings(mapid): - s_sel = ADDON.getSetting(mapid+"Sector") - t_sel = ADDON.getSetting(mapid+"Type") - - t_keys = [] - t_values= [] - - #1st option is blank for removing map - t_keys.append("") - t_values.append("") - - - for key,value in MAPTYPES.items(): - t_keys.append(key) - t_values.append(value) - - - dialog = xbmcgui.Dialog() - - ti=0 - try: - ti=t_keys.index(t_sel) - except: - ti=0 - ti=dialog.select(LANGUAGE(32350), t_values,0,ti) - t_sel=t_keys[ti] - ADDON.setSetting(mapid+"Type",t_keys[ti]) - - - - - - if ti > 0: - - if ("LOOP" == t_sel): - Sectors=LOOPSECTORS - else: - Sectors=MAPSECTORS - - # convert our map data into matching arrays to pass into dialog - s_keys = [] - s_values= [] - - for key,value in Sectors.items(): - s_keys.append(key) - s_values.append(value['name']) - - # grab index of current region, and pass in as default to dialog - si=0 - try: - si=s_keys.index(s_sel.lower()) - except: - #ignore if we did not find - si=0 - si=dialog.select(LANGUAGE(32349),s_values,0,si) - s_sel=s_keys[si] - ADDON.setSetting(mapid+"Sector",s_sel) - ADDON.setSetting(mapid+"Label",Sectors[s_sel]['name']+":"+MAPTYPES[t_sel]) - ADDON.setSetting(mapid+"Select",Sectors[s_sel]['name']+":"+MAPTYPES[t_sel]) - else: - ADDON.setSetting(mapid+"Label","") - ADDON.setSetting(mapid+"Select","") - - - # clean up referenced dialog object - del dialog - - - -######################################################################################## -## Main Kodi entry point -######################################################################################## - -class MyMonitor(xbmc.Monitor): - def __init__(self, *args, **kwargs): - xbmc.Monitor.__init__(self) - -log('version %s started with argv: %s' % (ADDON.getAddonInfo('version'), sys.argv[1])) - -MONITOR = MyMonitor() -set_property('Forecast.IsFetched' , 'true') -set_property('Current.IsFetched' , 'true') -set_property('Today.IsFetched' , '') -set_property('Daily.IsFetched' , 'true') -set_property('Detailed.IsFetched' , 'true') -set_property('Weekend.IsFetched' , '') -set_property('36Hour.IsFetched' , '') -set_property('Hourly.IsFetched' , 'true') -set_property('NOAA.IsFetched' , 'true') -set_property('WeatherProvider' , 'NOAA') -set_property('WeatherProviderLogo', xbmcvfs.translatePath(os.path.join(ADDON.getAddonInfo('path'), 'resources', 'media', 'skin-banner.png'))) - - -if sys.argv[1].startswith('EnterLocation'): - num=sys.argv[2] - enterLocation(num) - -if sys.argv[1].startswith('EnterAddress'): - num=sys.argv[2] - get_lat_long_by_address(num) - - -if sys.argv[1].startswith('FetchLocation'): - num=sys.argv[2] - LatLong = ADDON.getSetting("Location"+num+"LatLong") - if not LatLong: - enterLocation(num) - elif LatLong: - get_Stations(num,LatLong) - -elif sys.argv[1].startswith('Map'): - - mapSettings(sys.argv[1]) - -else: - - num=sys.argv[1] - LatLong = ADDON.getSetting('Location%sLatLong' % num) - - station=ADDON.getSetting('Location'+str(num)+'Station') - if station == '' : - log("calling location with %s" % (LatLong)) - get_Stations(str(num),LatLong) - - try: - lastPointsCheck=ADDON.getSetting('Location'+str(num)+'lastPointsCheck') - last_check=parse(lastPointsCheck) - current_datetime = datetime.datetime.now() - next_check=last_check+datetime.timedelta(days=2) - if (next_check < current_datetime): - get_Points(str(num),LatLong) - except: - get_Points(str(num),LatLong) - - refresh_locations() - - LatLong = ADDON.getSetting('Location%s' % num) - - if LatLong: - fetchWeatherAlerts(num) - if "forecast.weather.gov" == SOURCEPREF: - fetchAltDaily(num) - else: - fetchCurrent(num) - fetchDaily(num) - fetchHourly(num) - Station=ADDON.getSetting('Location%sradarStation' % num) - - set_property('Map.IsFetched', 'true') - #KODI will cache and not re-fetch the weather image, so inject a dummy time-stamp into the url to trick kodi because we want the new image - nowtime=str(time.time()) - #Radar - radarLoop=ADDON.getSetting('RadarLoop') - - #clean up previously fetched radar loop images - imagepath=xbmcvfs.translatePath(xbmcaddon.Addon().getAddonInfo('profile')) - for f in glob.glob(imagepath+"radar*.gif"): - os.remove(f) - - if ("true" == radarLoop): - #kodi will not loop gifs from a url, we have to actually - #download to a local file to get it to loop - - #xbmc.log('Option To Loop Radar Selected',level=xbmc.LOGDEBUG) - xbmc.log('Option To Loop Radar Selected',level=xbmc.LOGDEBUG) - url="https://radar.weather.gov/ridge/standard/%s_loop.gif" % (Station) - radarfilename="radar_%s_%s.gif" % (Station,nowtime) - dest=imagepath+radarfilename - loop_image=get_url_image(url, dest) - set_property('Map.%i.Area' % 1, loop_image) - else: - url="https://radar.weather.gov/ridge/standard/%s_0.gif?%s" % (Station,nowtime) - set_property('Map.%i.Area' % 1, url) - #clear_property('Map.%i.Area' % 1) - #set_property('Map.%i.Layer' % 1, url) - - clear_property('Map.%i.Layer' % 1) - set_property('Map.%i.Heading' % 1, LANGUAGE(32334)) - - - # add satellite maps if we configured any - for count in range (1, 5): - mcount=count+1 - mapsector = ADDON.getSetting('Map%iSector' % (mcount)) - maptype = ADDON.getSetting('Map%iType' % (mcount)) - maplabel = ADDON.getSetting('Map%iLabel' % (mcount)) - - if (mapsector and maptype): - - if ("LOOP" == maptype): - # want looping radar gifs - path=LOOPSECTORS.get(mapsector)['path'] - imagepath=xbmcvfs.translatePath(xbmcaddon.Addon().getAddonInfo('profile')) - url="https://radar.weather.gov/%s" % (path) - radarfilename="radar_%s_%s.gif" % (mapsector,nowtime) - dest=imagepath+radarfilename - loop_image=get_url_image(url, dest) +if (__name__ == '__main__'): + noaa.noaa(sys) - set_property('Map.%i.Area' % (mcount), loop_image) - set_property('Map.%i.Heading' % (mcount), "%s" % (maplabel) ) - clear_property('Map.%i.Layer' % (mcount)) - else: - # want normal satellite images - path=MAPSECTORS.get(mapsector)['path'] - if mapsector != 'glm-e' and mapsector != 'glm-w': - path=path.replace("%s",maptype) - url="https://cdn.star.nesdis.noaa.gov/%s?%s" % (path,nowtime) - - set_property('Map.%i.Area' % (mcount), url) - set_property('Map.%i.Heading' % (mcount), "%s" % (maplabel) ) - clear_property('Map.%i.Layer' % (mcount)) - else: - clear_property('Map.%i.Area' % (mcount)) - clear_property('Map.%i.Heading' % (mcount)) - clear_property('Map.%i.Layer' % (mcount)) - else: - log('no location provided') - clear() -# clean up references to classes that we used -del MONITOR, xbmc, xbmcgui, xbmcvfs, xbmcaddon, WEATHER_WINDOW -# clean up everything we referenced from the utils to prevent any dangling classes hanging around -del FtoC, CtoF, log, ADDON, LANGUAGE, MAPSECTORS, LOOPSECTORS, MAPTYPES -del WEATHER_CODES, FORECAST, WIND_DIR, SPEEDUNIT, zip_x -del FEELS_LIKE_F_MPH, FEELS_LIKE_C_KPH, WIND_CHILL_F_MPH, WIND_CHILL_C_KPH, HEAT_INDEX_F, HEAT_INDEX_C -del get_url_JSON, get_url_image -del get_month, get_timestamp, get_weekday, get_time diff --git a/weather.noaa/lib/noaa.py b/weather.noaa/lib/noaa.py new file mode 100644 index 000000000..54277f401 --- /dev/null +++ b/weather.noaa/lib/noaa.py @@ -0,0 +1,1206 @@ +# -*- coding: utf-8 -*- +#from __future__ import unicode_literals + +#from future import standard_library +#standard_library.install_aliases() + +import os, glob, sys, time, re +import xbmc, xbmcgui, xbmcvfs, xbmcaddon +import datetime + +from .utils import * +#from lib.utils import FtoC, CtoF, log, ADDON, LANGUAGE, MAPSECTORS, LOOPSECTORS, MAPTYPES +#from lib.utils import WEATHER_CODES, FORECAST, WIND_DIR, SPEEDUNIT, zip_x +#from lib.utils import FEELS_LIKE_F_MPH, FEELS_LIKE_C_KPH, WIND_CHILL_F_MPH, WIND_CHILL_C_KPH, HEAT_INDEX_F, HEAT_INDEX_C +#from lib.utils import get_url_JSON, get_url_image +#from lib.utils import get_datestr, get_timestamp, get_weekday, get_time +from dateutil.parser import parse + + + +# WEATHER_WINDOW = xbmcgui.Window(12600) +WEATHER_ICON = xbmcvfs.translatePath('%s.png') +DATEFORMAT = xbmc.getRegion('dateshort') +TIMEFORMAT = xbmc.getRegion('meridiem') +MAXDAYS = 14 +TEMPUNIT = xbmc.getRegion('tempunit') +SOURCEPREF = ADDON.getSetting("DataSourcePreference") + + +def set_property(name, value): + xbmcgui.Window(12600).setProperty(name, value) + +def clear_property(name): + xbmcgui.Window(12600).clearProperty(name) + +def code_from_icon(icon): + if icon: + #xbmc.log('icon: %s' % (icon) ,level=xbmc.LOGDEBUG) + + + daynight="day" + + #special handling of forecast.weather.gov "dualimage" icon generator urls + #https://forecast.weather.gov/DualImage.php?i=bkn&j=shra&jp=30 + #https://forecast.weather.gov/DualImage.php?i=shra&j=bkn&ip=30 + if 'DualImage' in icon: +# xbmc.log('icon: %s' % icon,level=xbmc.LOGERROR) + + params = icon.split("?")[1].split("&") +# xbmc.log('params: %s' % params,level=xbmc.LOGERROR) + + code="day" + rain=None + for param in params: +# xbmc.log('param: %s' % param,level=xbmc.LOGERROR) + + thing=param.split("=") + p=thing[0] + v=thing[1] +# xbmc.log('p: %s' % p,level=xbmc.LOGERROR) +# xbmc.log('v: %s' % v,level=xbmc.LOGERROR) + if p == "i": + code="%s/%s" % ("day",v) + if p == "ip" or p == "jp": + if rain is None or v > rain: + rain=v +# xbmc.log('code: %s' % code,level=xbmc.LOGERROR) +# xbmc.log('rain: %s' % rain,level=xbmc.LOGERROR) + + return code, rain + + + if '?' in icon: + icon=icon.rsplit('?', 1)[0] + + # strip off file extension if we have one + icon=icon.replace(".png","") + icon=icon.replace(".jpg","") + + if "/day/" in icon: + daynight="day" + elif "/night/" in icon: + daynight="night" + + rain = None + code = None + # loop though our "split" icon paths, and get max rain percent + # take last icon code in the process + for checkcode in icon.rsplit('/'): + thing=checkcode.split(",") + code="%s/%s" % (daynight,thing[0]) + + if len(thing) > 1: + train=thing[1] + if rain is None or train > rain: + rain=train + + # forcast.gov urls may have codes like sct30, which means "scattered clouds 30% chance of rain" ,so regex for it + cresult = re.search(r"([a-z]+)(\d*)", thing[0]) + if cresult and cresult.group(1): + code="%s/%s" % (daynight,cresult.group(1)) + if cresult and cresult.group(2): + train=cresult.group(2) + if rain is None or train > rain: + rain=train + +# xbmc.log('code: %s' % code,level=xbmc.LOGERROR) +# xbmc.log('rain: %s' % rain,level=xbmc.LOGERROR) + + return code, rain + + + + +class noaa: + + def clear(self): + set_property('Current.Condition' , 'N/A') + set_property('Current.Temperature' , '0') + set_property('Current.Wind' , '0') + set_property('Current.WindDirection' , 'N/A') + set_property('Current.Humidity' , '0') + set_property('Current.FeelsLike' , '0') + set_property('Current.UVIndex' , '0') + set_property('Current.DewPoint' , '0') + set_property('Current.OutlookIcon' , 'na.png') + set_property('Current.FanartCode' , 'na') + for count in range (0, MAXDAYS+1): + set_property('Day%i.Title' % count, 'N/A') + set_property('Day%i.HighTemp' % count, '0') + set_property('Day%i.LowTemp' % count, '0') + set_property('Day%i.Outlook' % count, 'N/A') + set_property('Day%i.OutlookIcon' % count, 'na.png') + set_property('Day%i.FanartCode' % count, 'na') + + def refresh_locations(self): + locations = 0 + for count in range(1, 6): + LatLong = ADDON.getSetting('Location%sLatLong' % count) + loc_name = ADDON.getSetting('Location%s' % count) + if LatLong: + locations += 1 + if not loc_name: + loc_name = 'Location %s' % count + set_property('Location%s' % count, loc_name) + + else: + set_property('Location%s' % count, '') + + #set_property('Location%s' % count, loc_name) + + set_property('Locations', str(locations)) + log('available locations: %s' % str(locations)) + + + + + + def get_lat_long_by_address(self,num): + + dialog = xbmcgui.Dialog() + saddress=dialog.input(heading=LANGUAGE(32345),defaultt='',type=xbmcgui.INPUT_ALPHANUM) + saddress=saddress.replace(" ", "+") + url="https://geocoding.geo.census.gov/geocoder/locations/onelineaddress?address=%s&benchmark=4&format=json" % (saddress) + + data=get_url_JSON(url) + + ##xbmc.log('DEBUG data== %s' % data,level=xbmc.LOGERROR) + + + if data and 'result' in data and 'addressMatches' in data['result'] and len(data['result']['addressMatches']) > 0 : + addresslist=[] + addresses={} + for count,item in enumerate(data['result']['addressMatches']): + locx=round(item['coordinates']['x'],4) + locy=round(item['coordinates']['y'],4) + locfull=str(locy) + ',' + str(locx) + address=item['matchedAddress'] + addresslist.append(address) + addresses[address]=locfull + + dialog = xbmcgui.Dialog() + i=dialog.select(LANGUAGE(32348),addresslist) + # clean up reference to dialog object + del dialog + if i >= 0: + LatLong=addresses[addresslist[i]] + ADDON.setSetting("Location"+num+"Address",addresslist[i]) + ADDON.setSetting("Location"+num+"LatLong",LatLong) + self.get_Stations(num,LatLong,True) + else: + dialog = xbmcgui.Dialog() + dialog.ok(heading=LANGUAGE(32346),message=LANGUAGE(32347)) + del dialog + return + + + + ######################################################################################## + ## Dialog for getting Latitude and Longitude + ######################################################################################## + def enterLocation(self,num): + ## log("argument: %s" % (sys.argv[1])) + + text = ADDON.getSetting("Location"+num+"LatLong") + Latitude="" + Longitude="" + if text and "," in text: + thing=text.split(",") + Latitude=thing[0] + Longitude=thing[1] + + dialog = xbmcgui.Dialog() + + Latitude=dialog.input(LANGUAGE(32341),defaultt=Latitude,type=xbmcgui.INPUT_ALPHANUM) + + if not Latitude: + ADDON.setSetting("Location"+num+"LatLong","") + return False + + Longitude=dialog.input(heading=LANGUAGE(32342),defaultt=Longitude,type=xbmcgui.INPUT_ALPHANUM) + + if not Longitude: + ADDON.setSetting("Location"+num+"LatLong","") + return False + LatLong=Latitude+","+Longitude + ADDON.setSetting("Location"+num+"LatLong",LatLong) + self.get_Stations(num,LatLong,True) + return + + + ######################################################################################## + ## fetches location data (weather grid point, station, etc, for lattitude,logngitude + ## returns url for fetching local weather stations + ######################################################################################## + + + + def get_Points(self,num,LatLong,resetName=False): + + prefix="Location"+num + log('searching for location: %s' % LatLong) + url = 'https://api.weather.gov/points/%s' % LatLong + log("url:"+url) + data=get_url_JSON(url) + log('location data: %s' % data) + if not data: + log('failed to retrieve location data') + return None + if data and 'properties' in data: + + if resetName: + city = data['properties']['relativeLocation']['properties']['city'] + state = data['properties']['relativeLocation']['properties']['state'] + locationName= city+", "+state + ADDON.setSetting(prefix, locationName) + + gridX=data['properties']['gridX'] + ADDON.setSetting(prefix+'gridX',str(gridX)) + + gridY=data['properties']['gridY'] + ADDON.setSetting(prefix+'gridY',str(gridY)) + + cwa=data['properties']['cwa'] + ADDON.setSetting(prefix+'cwa', cwa) + + forecastZone=data['properties']['forecastZone'] + zone=forecastZone.rsplit('/',1)[1] + ADDON.setSetting(prefix+'Zone', zone) + + forecastCounty=data['properties']['county'] + county=forecastCounty.rsplit('/',1)[1] + ADDON.setSetting(prefix+'County', county) + + forecastGridData_url = data['properties']['forecastGridData'] + ADDON.setSetting(prefix+'forecastGrid_url', forecastGridData_url) + + forecastHourly_url = data['properties']['forecastHourly'] + ADDON.setSetting(prefix+'forecastHourly_url', forecastHourly_url) + + forecast_url = data['properties']['forecast'] + ADDON.setSetting(prefix+'forecast_url', forecast_url) + + radarStation = data['properties']['radarStation'] + ADDON.setSetting(prefix+'radarStation', radarStation) + + + #current_datetime = parse("now") + current_datetime = datetime.datetime.now() + ADDON.setSetting(prefix+'lastPointsCheck', str(current_datetime)) + + + stations_url = data['properties']['observationStations'] + return stations_url + + ######################################################################################## + ## fetches location data (weather grid point, station, etc, for lattitude,logngitude + ######################################################################################## + + def get_Stations(self,num,LatLong,resetName=False): + + prefix="Location"+num + odata=None + stations_url=self.get_Points(num,LatLong,resetName) + if stations_url: + odata = get_url_JSON(stations_url) + + if odata and 'features' in odata: + stations={} + stationlist=[] + + for count,item in enumerate(odata['features']): + stationId=item['properties']['stationIdentifier'] + stationName=item['properties']['name'] + stationlist.append(stationName) + stations[stationName]=stationId + + dialog = xbmcgui.Dialog() + i=dialog.select(LANGUAGE(32331),stationlist) + # clean up reference to dialog object + del dialog + + ADDON.setSetting(prefix+'Station',stations[stationlist[i]]) + ADDON.setSetting(prefix+'StationName',stationlist[i]) + + + + + ######################################################################################## + ## fetches daily weather data + ######################################################################################## + + def fetchDaily(self,num): + + log("SOURCEPREF: %s" % SOURCEPREF) + url=ADDON.getSetting('Location'+str(num)+'forecast_url') + if "preview-api.weather.gov" == SOURCEPREF: + url=url.replace("https://api.weather.gov","https://preview-api.weather.gov") + + if 'F' in TEMPUNIT: + url="%s?units=us" % url + elif 'C' in TEMPUNIT: + url="%s?units=si" % url + + log('forecast url: %s' % url) + + daily_weather = get_url_JSON(url) + + if daily_weather and 'properties' in daily_weather: + data=daily_weather['properties'] + else: + #api.weather.gov is acting up, so fall back to alternate api + xbmc.log('failed to find weather data from : %s' % url,level=xbmc.LOGERROR) + xbmc.log('%s' % daily_weather,level=xbmc.LOGERROR) + return self.fetchAltDaily(num) + + for count, item in enumerate(data['periods'], start=0): + icon = item['icon'] + #https://api.weather.gov/icons/land/night/ovc?size=small + if icon and '?' in icon: + icon=icon.rsplit('?', 1)[0] + code, rain=code_from_icon(icon) + + weathercode = WEATHER_CODES.get(code) + starttime=item['startTime'] + startstamp=get_timestamp(starttime) + set_property('Day%i.isDaytime' % (count),str(item['isDaytime'])) + set_property('Day%i.Title' % (count), item['name']) + + if item['isDaytime'] == True: + ##Since we passed units into api, we may need to convert to C, or may not + if 'F' in TEMPUNIT: + set_property('Day%i.HighTemp' % (count), str(int(round(FtoC(item['temperature']))))) + set_property('Day%i.LowTemp' % (count), str(int(round(FtoC(item['temperature']))))) + elif 'C' in TEMPUNIT: + set_property('Day%i.HighTemp' % (count), str(int(round(item['temperature'])))) + set_property('Day%i.LowTemp' % (count), str(int(round(item['temperature'])))) + if item['isDaytime'] == False: + if 'F' in TEMPUNIT: + set_property('Day%i.HighTemp' % (count), str(int(round(FtoC(item['temperature']))))) + set_property('Day%i.LowTemp' % (count), str(int(round(FtoC(item['temperature']))))) + elif 'C' in TEMPUNIT: + set_property('Day%i.HighTemp' % (count), str(int(round(item['temperature'])))) + set_property('Day%i.LowTemp' % (count), str(int(round(item['temperature'])))) + set_property('Day%i.Outlook' % (count), item['shortForecast']) + set_property('Day%i.FanartCode' % (count), weathercode) + set_property('Day%i.OutlookIcon'% (count), WEATHER_ICON % weathercode) + set_property('Day%i.RemoteIcon' % (count), icon) + + # NOTE: Day props are 0 based, but Daily/Hourly are 1 based + set_property('Daily.%i.isDaytime' % (count+1),str(item['isDaytime'])) + set_property('Daily.%i.Outlook' % (count+1), item['shortForecast']) + set_property('Daily.%i.ShortOutlook' % (count+1), item['shortForecast']) + set_property('Daily.%i.DetailedOutlook' % (count+1), item['detailedForecast']) + + set_property('Daily.%i.RemoteIcon' % (count+1), icon) + set_property('Daily.%i.OutlookIcon' % (count+1), WEATHER_ICON % weathercode) + set_property('Daily.%i.FanartCode' % (count+1), weathercode) + set_property('Daily.%i.WindDirection' % (count+1), item['windDirection']) + set_property('Daily.%i.WindSpeed' % (count+1), item['windSpeed']) + + if item['isDaytime'] == True: + set_property('Daily.%i.LongDay' % (count+1), item['name']) + set_property('Daily.%i.ShortDay' % (count+1), get_weekday(startstamp,'s')+" (d)") + #set_property('Daily.%i.TempDay' % (count+1), u'%i\N{DEGREE SIGN}%s' % (item['temperature'], item['temperatureUnit'])) + #set_property('Daily.%i.HighTemperature' % (count+1), u'%i\N{DEGREE SIGN}%s' % (item['temperature'], item['temperatureUnit'])) + + ## we passed units to api, so we got back C or F, so don't need to convert + set_property('Daily.%i.TempDay' % (count+1), u'%s%s' % (item['temperature'], TEMPUNIT)) + set_property('Daily.%i.HighTemperature' % (count+1), u'%s%s' % (item['temperature'], TEMPUNIT)) + set_property('Daily.%i.TempNight' % (count+1), '') + set_property('Daily.%i.LowTemperature' % (count+1), '') + + if item['isDaytime'] == False: + set_property('Daily.%i.LongDay' % (count+1), item['name']) + set_property('Daily.%i.ShortDay' % (count+1), get_weekday(startstamp,'s')+" (n)") + + set_property('Daily.%i.TempDay' % (count+1), '') + set_property('Daily.%i.HighTemperature' % (count+1), '') + ## we passed units to api, so we got back C or F, so don't need to convert + set_property('Daily.%i.TempNight' % (count+1), u'%s%s' % (item['temperature'], TEMPUNIT)) + set_property('Daily.%i.LowTemperature' % (count+1), u'%s%s' % (item['temperature'], TEMPUNIT)) + + if [1] == 'd' or DATEFORMAT[0] == 'D': + set_property('Daily.%i.LongDate' % (count+1), get_datestr(startstamp, 'dl')) + set_property('Daily.%i.ShortDate' % (count+1), get_datestr(startstamp, 'ds')) + else: + set_property('Daily.%i.LongDate' % (count+1), get_datestr(startstamp, 'ml')) + set_property('Daily.%i.ShortDate' % (count+1), get_datestr(startstamp, 'ms')) + + rain=0 + if item['probabilityOfPrecipitation'] and item['probabilityOfPrecipitation']['value'] : + rain=item['probabilityOfPrecipitation']['value'] + + if rain and str(rain) and not "0" == str(rain): + set_property('Daily.%i.ChancePrecipitation' % (count+1), str(rain) + '%') + else: + ##set_property('Daily.%i.ChancePrecipitation' % (count+1), '') + clear_property('Daily.%i.ChancePrecipitation' % (count+1)) + + + + ######################################################################################## + ## fetches daily weather data using alternative api endpoint + ######################################################################################## + + + def fetchAltDaily(self,num): + + latlong=ADDON.getSetting('Location'+str(num)+"LatLong") + latitude =latlong.rsplit(',',1)[0] + longitude=latlong.rsplit(',',1)[1] + + url="https://forecast.weather.gov/MapClick.php?lon="+longitude+"&lat="+latitude+"&FcstType=json" + log('forecast url: %s' % url) + + daily_weather = get_url_JSON(url) + + if daily_weather and 'data' in daily_weather: + + dailydata=[ + {"startPeriodName": a, + "startValidTime": b, + "tempLabel": c, + "temperature": d, + "pop": e, + "weather": f, + "iconLink": g, + "hazard": h, + "hazardUrl": i, + "text": j + } + for a,b,c,d,e,f,g,h,i,j in zip_x(None, + daily_weather['time']['startPeriodName'], + daily_weather['time']['startValidTime'], + daily_weather['time']['tempLabel'], + daily_weather['data']['temperature'], + daily_weather['data']['pop'], + daily_weather['data']['weather'], + daily_weather['data']['iconLink'], + daily_weather['data']['hazard'], + daily_weather['data']['hazardUrl'], + daily_weather['data']['text'] + )] + + else: + xbmc.log('failed to retrieve weather data from : %s' % url,level=xbmc.LOGERROR) + xbmc.log('%s' % daily_weather,level=xbmc.LOGERROR) + return None + + for count, item in enumerate(dailydata, start=0): + icon = item['iconLink'] + + #https://api.weather.gov/icons/land/night/ovc?size=small + code, ignoreme = code_from_icon(icon) + weathercode = WEATHER_CODES.get(code) + + starttime=item['startValidTime'] + startstamp=get_timestamp(starttime) + set_property('Day%i.Title' % (count), item['startPeriodName']) + + set_property('Day%i.Outlook' % (count), item['weather']) + set_property('Day%i.Details' % (count), item['text']) + + set_property('Day%i.OutlookIcon' % (count), WEATHER_ICON % weathercode) + set_property('Day%i.RemoteIcon' % (count), icon) + set_property('Day%i.FanartCode' % (count), weathercode) + + # NOTE: Day props are 0 based, but Daily/Hourly are 1 based + set_property('Daily.%i.DetailedOutlook' % (count+1), item['text']) + set_property('Daily.%i.Outlook' % (count+1), item['weather']) + set_property('Daily.%i.ShortOutlook' % (count+1), item['weather']) + + set_property('Daily.%i.OutlookIcon' % (count+1), WEATHER_ICON % weathercode) + set_property('Daily.%i.RemoteIcon' % (count+1), icon) + set_property('Daily.%i.FanartCode' % (count+1), weathercode) + + if item['tempLabel'] == 'High': + set_property('Daily.%i.LongDay' % (count+1), item['startPeriodName']) + set_property('Daily.%i.ShortDay' % (count+1), get_weekday(startstamp,'s')+" (d)") + + set_property('Daily.%i.TempNight' % (count+1), '') + set_property('Daily.%i.LowTemperature' % (count+1), '') + if 'F' in TEMPUNIT: + set_property('Daily.%i.TempDay' % (count+1), u'%s%s' % (int(round(float(item['temperature']))), TEMPUNIT)) + set_property('Daily.%i.HighTemperature' % (count+1), u'%s%s' % (int(round(float(item['temperature']))), TEMPUNIT)) + elif 'C' in TEMPUNIT: + set_property('Daily.%i.TempDay' % (count+1), u'%s%s' % (int(round(FtoC(float(item['temperature'])))), TEMPUNIT)) + set_property('Daily.%i.HighTemperature' % (count+1), u'%s%s' % (int(round(FtoC(float(item['temperature'])))), TEMPUNIT)) + + if item['tempLabel'] == 'Low': + set_property('Daily.%i.LongDay' % (count+1), item['startPeriodName']) + set_property('Daily.%i.ShortDay' % (count+1), get_weekday(startstamp,'s')+" (n)") + + set_property('Daily.%i.TempDay' % (count+1), '') + set_property('Daily.%i.HighTemperature' % (count+1), '') + if 'F' in TEMPUNIT: + set_property('Daily.%i.TempNight' % (count+1), u'%s%s' % (int(round(float(item['temperature']))), TEMPUNIT)) + set_property('Daily.%i.LowTemperature' % (count+1), u'%s%s' % (int(round(float(item['temperature']))), TEMPUNIT)) + elif 'C' in TEMPUNIT: + set_property('Daily.%i.TempNight' % (count+1), u'%s%s' % (int(round(FtoC(float(item['temperature'])))), TEMPUNIT)) + set_property('Daily.%i.LowTemperature' % (count+1), u'%s%s' % (int(round(FtoC(float(item['temperature'])))), TEMPUNIT)) + + if DATEFORMAT[1] == 'd' or DATEFORMAT[0] == 'D': + set_property('Daily.%i.LongDate' % (count+1), get_datestr(startstamp, 'dl')) + set_property('Daily.%i.ShortDate' % (count+1), get_datestr(startstamp, 'ds')) + else: + set_property('Daily.%i.LongDate' % (count+1), get_datestr(startstamp, 'ml')) + set_property('Daily.%i.ShortDate' % (count+1), get_datestr(startstamp, 'ms')) + + rain = item['pop'] + if rain and str(rain) and not "0" == str(rain): + set_property('Daily.%i.ChancePrecipitation' % (count+1), str(rain) + '%') + else: + ##set_property('Daily.%i.ChancePrecipitation' % (count+1), '') + clear_property('Daily.%i.ChancePrecipitation' % (count+1)) + + + + + if daily_weather and 'currentobservation' in daily_weather: + data=daily_weather['currentobservation'] + icon = "http://forecast.weather.gov/newimages/large/%s" % data.get('Weatherimage') + code, rain = code_from_icon(icon) + weathercode = WEATHER_CODES.get(code) + + set_property('Current.Location', data.get('name')) + set_property('Current.RemoteIcon',icon) + set_property('Current.OutlookIcon', '%s.png' % weathercode) # xbmc translates it to Current.ConditionIcon + set_property('Current.FanartCode', weathercode) + set_property('Current.Condition', FORECAST.get(data.get('Weather'), data.get('Weather'))) + set_property('Current.Humidity' , str(data.get('Relh'))) + set_property('Current.DewPoint', str(int(round(FtoC(data.get('Dewp')))))) + + try: + temp=data.get('Temp') + set_property('Current.Temperature',str(int(round(FtoC(temp))))) + except: + #set_property('Current.Temperature','') + clear_property('Current.Temperature') + + try: + set_property('Current.Wind', str(round(float(data.get('Winds'))*1.609298167))) + except: + #set_property('Current.Wind','') + clear_property('Current.Wind') + + try: + set_property('Current.WindDirection', xbmc.getLocalizedString(WIND_DIR(int(data.get('Windd'))))) + except: + #set_property('Current.WindDirection', '') + clear_property('Current.WindDirection') + + # try: + # set_property('Current.WindGust' , str(SPEED(float(data.get('Gust'))/2.237)) + SPEEDUNIT) + # except: + # clear_property('Current.WindGust') + # ##set_property('Current.WindGust' , '') + + if rain and str(rain) and not "0" == str(rain): + set_property('Current.ChancePrecipitation', str(rain)+'%') + else : + clear_property('Current.ChancePrecipitation') + + # calculate feels like + clear_property('Current.FeelsLike') + try: + wind=data.get('Winds') + if not wind: + wind=0 + feelslike = FEELS_LIKE_C_KPH( FtoC(data.get('Temp')), float(wind)/2.237, int(data.get('Relh'))) + if feelslike: + #xbmc.log('feelslike: %s' % (feelslike),level=xbmc.LOGERROR) + set_property('Current.FeelsLike', str(int(round(feelslike)))) + else: + clear_property('Current.FeelsLike') + except: + clear_property('Current.FeelsLike') + #set_property('Current.FeelsLike', '') + + # # if we have windchill or heatindex directly, then use that instead + # if data.get('WindChill') and not "NA" == data.get('WindChill'): + # set_property('Current.FeelsLike', str(FtoC(data.get('WindChill'))) ) + # if data.get('HeatIndex') and not "NA" == data.get('HeatIndex'): + # set_property('Current.FeelsLike', str(FtoC(data.get('HeatIndex'))) ) + + + + + ######################################################################################## + ## fetches current weather info for location + ######################################################################################## + + def fetchCurrent(self,num): + station=ADDON.getSetting('Location'+str(num)+'Station') + url="https://api.weather.gov/stations/%s/observations/latest" %station + current=get_url_JSON(url) + if current and 'properties' in current: + data=current['properties'] + else: + xbmc.log('failed to find weather data from : %s' % url,level=xbmc.LOGERROR) + xbmc.log('%s' % current,level=xbmc.LOGERROR) + return + + icon = data['icon'] + #https://api.weather.gov/icons/land/night/ovc?size=small + code = None + rain = None + if icon: + if '?' in icon: + icon=icon.rsplit('?', 1)[0] + code, rain = code_from_icon(icon) + weathercode = WEATHER_CODES.get(code) + set_property('Current.RemoteIcon',icon) + set_property('Current.OutlookIcon', '%s.png' % weathercode) # xbmc translates it to Current.ConditionIcon + set_property('Current.FanartCode', weathercode) + + set_property('Current.Condition', FORECAST.get(data.get('textDescription'), data.get('textDescription'))) + try: + set_property('Current.Humidity' , str(round(data.get('relativeHumidity').get('value')))) + except: + ##set_property('Current.Humidity' , '') + clear_property('Current.Humidity') + + try: + temp=int(round(data.get('temperature').get('value'))) + set_property('Current.Temperature',str(temp)) # api values are in C + except: + ##set_property('Current.Temperature','') + clear_property('Current.Temperature') + try: + set_property('Current.Wind', str(int(round(data.get('windSpeed').get('value'))))) + except: + ##set_property('Current.Wind','') + clear_property('Current.Wind') + + try: + set_property('Current.WindDirection', xbmc.getLocalizedString(WIND_DIR(int(round(data.get('windDirection').get('value')))))) + except: + #set_property('Current.WindDirection', '') + clear_property('Current.WindDirection') + + if rain and str(rain) and not "0" == str(rain): + set_property('Current.ChancePrecipitation', str(rain)+'%') + else : + #set_property('Current.ChancePrecipitation', '') + clear_property('Current.ChancePrecipitation') + + clear_property('Current.FeelsLike') + #calculate feels like + windspeed=data.get('windSpeed').get('value') + if not windspeed: + windspeed=0 + + try: + feelslike=FEELS_LIKE_C_KPH(data.get('temperature').get('value'), float(windspeed), data.get('relativeHumidity').get('value')) + if feelslike: + #xbmc.log('feelslike: %s' % (feelslike),level=xbmc.LOGERROR) + set_property('Current.FeelsLike', str(int(round(feelslike)))) + else: + clear_property('Current.FeelsLike') + except: + clear_property('Current.FeelsLike') + + # if we have windchill or heat index directly, then use that instead + if data.get('windChill').get('value'): + #xbmc.log('windchill direct: %s' % (str(int(round(data.get('windChill').get('value'))))),level=xbmc.LOGERROR) + set_property('Current.FeelsLike', str(int(round(data.get('windChill').get('value')))) ) + if data.get('heatIndex').get('value'): + #xbmc.log('windchill direct: %s' % (str(int(round(data.get('heatIndex').get('value'))))),level=xbmc.LOGERROR) + set_property('Current.FeelsLike', str(int(round(data.get('heatIndex').get('value')))) ) + + try: + temp=int(round(data.get('dewpoint').get('value',0))) + set_property('Current.DewPoint', str(temp)) # api values are in C + except: + set_property('Current.DewPoint', '') + + + + ## extended properties + + # try: + # set_property('Current.WindGust' , SPEED(float(data.get('windGust').get('value',0))/3.6) + SPEEDUNIT) + # except: + # set_property('Current.WindGust' , '') + + try: + set_property('Current.SeaLevel' , str(data.get('seaLevelPressure').get('value',0))) + except: + set_property('Current.SeaLevel' , '') + + try: + set_property('Current.GroundLevel' ,str(data.get('barometricPressure').get('value',0))) + except: + set_property('Current.GroundLevel' , '') + + + + + + ######################################################################################## + ## fetches any weather alerts for location + ######################################################################################## + + + def fetchWeatherAlerts(self,num): + + ### we could fetch alerts for either 'County', or 'Zone' + #https://api.weather.gov/alerts/active/zone/CTZ006 + #https://api.weather.gov/alerts/active/zone/CTC009 + ##https://api.weather.gov/alerts/active?status=actual&point=%7Blat%7D,%7Blong%7D + + #for now, lets use the point alert lookup, as suggested by the weather api team + + ##a_zone=ADDON.getSetting('Location'+str(num)+'County') + ##url="https://api.weather.gov/alerts/active/zone/%s" %a_zone + + # we are storing lat,long as comma separated already, so that is convienent for us and we can just drop it into the url + latlong=ADDON.getSetting('Location'+str(num)+'LatLong') + url="https://api.weather.gov/alerts/active?status=actual&point=%s" % (latlong) + + alerts=get_url_JSON(url) + # if we have a valid response then clear our current alerts + if alerts and 'features' in alerts: + for count in range (1, 10): + clear_property('Alerts.%i.event' % (count)) + else: + xbmc.log('failed to get proper alert response %s' % url,level=xbmc.LOGERROR) + xbmc.log('%s' % alerts,level=xbmc.LOGDEBUG) + return + + if 'features' in alerts and alerts['features']: + data=alerts['features'] + set_property('Alerts.IsFetched' , 'true') + else: + clear_property('Alerts.IsFetched') + xbmc.log('No current weather alerts from %s' % url,level=xbmc.LOGDEBUG) + return + + for count, item in enumerate(data, start=1): + + thisdata=item['properties'] + set_property('Alerts.%i.status' % (count), str(thisdata['status'])) + set_property('Alerts.%i.messageType' % (count), str(thisdata['messageType'])) + set_property('Alerts.%i.category' % (count), str(thisdata['category'])) + set_property('Alerts.%i.severity' % (count), str(thisdata['severity'])) + set_property('Alerts.%i.certainty' % (count), str(thisdata['certainty'])) + set_property('Alerts.%i.urgency' % (count), str(thisdata['urgency'])) + set_property('Alerts.%i.event' % (count), str(thisdata['event'])) + set_property('Alerts.%i.headline' % (count), str(thisdata['headline'])) + set_property('Alerts.%i.description' % (count), str(thisdata['description'])) + set_property('Alerts.%i.instruction' % (count), str(thisdata['instruction'])) + set_property('Alerts.%i.response' % (count), str(thisdata['response'])) + + + + ######################################################################################## + ## fetches hourly weather data + ######################################################################################## + + def fetchHourly(self,num): + + log("SOURCEPREF: %s" % SOURCEPREF) + + url=ADDON.getSetting('Location'+str(num)+'forecastHourly_url') + if "preview-api.weather.gov" == SOURCEPREF: + url=url.replace("https://api.weather.gov","https://preview-api.weather.gov") + log("url-x: %s" % url) + + if 'F' in TEMPUNIT: + url="%s?units=us" % url + elif 'C' in TEMPUNIT: + url="%s?units=si" % url + + + + hourly_weather = get_url_JSON(url) + if hourly_weather and 'properties' in hourly_weather: + data=hourly_weather['properties'] + else: + xbmc.log('failed to find proper hourly weather from %s' % url,level=xbmc.LOGERROR) + return + #api is currently returning a 0 % rain icon url, which is not valid, so need to clean it + iconreplacepattern1 = re.compile(r"[,]0$") + + # extended properties + for count, item in enumerate(data['periods'], start = 0): + + icon=item['icon'] + #https://api.weather.gov/icons/land/night/ovc?size=small + if icon: + if '?' in icon: + icon=icon.rsplit('?', 1)[0] + code, rain=code_from_icon(icon) + icon=iconreplacepattern1.sub("",icon) + set_property('Hourly.%i.RemoteIcon' % (count+1), icon) + + weathercode = WEATHER_CODES.get(code) + starttime=item['startTime'] + startstamp=get_timestamp(starttime) + if DATEFORMAT[1] == 'd' or DATEFORMAT[0] == 'D': + set_property('Hourly.%i.LongDate' % (count+1), get_fulldatestr(startstamp, 'dl')) + set_property('Hourly.%i.ShortDate' % (count+1), get_fulldatestr(startstamp, 'ds')) + else: + set_property('Hourly.%i.LongDate' % (count+1), get_fulldatestr(startstamp, 'ml')) + set_property('Hourly.%i.ShortDate' % (count+1), get_fulldatestr(startstamp, 'ms')) + + set_property('Hourly.%i.Time' % (count+1), get_time(startstamp)) + if DATEFORMAT[1] == 'd' or DATEFORMAT[0] == 'D': + set_property('Hourly.%i.LongDate' % (count+1), get_fulldatestr(startstamp, 'dl')) + set_property('Hourly.%i.ShortDate' % (count+1), get_fulldatestr(startstamp, 'ds')) + else: + set_property('Hourly.%i.LongDate' % (count+1), get_fulldatestr(startstamp, 'ml')) + set_property('Hourly.%i.ShortDate' % (count+1), get_fulldatestr(startstamp, 'ms')) + + set_property('Hourly.%i.Outlook' % (count+1), FORECAST.get(item['shortForecast'], item['shortForecast'])) + set_property('Hourly.%i.ShortOutlook' % (count+1), FORECAST.get(item['shortForecast'], item['shortForecast'])) + set_property('Hourly.%i.OutlookIcon' % (count+1), WEATHER_ICON % weathercode) + set_property('Hourly.%i.FanartCode' % (count+1), weathercode) + windspeed=item['windSpeed'] + + if windspeed and (windspeed == "0 mph" or windspeed == "0 km/h"): + windspeed="" + + if windspeed and item['windDirection']: + set_property('Hourly.%i.WindDirection' % (count+1), item['windDirection']) + set_property('Hourly.%i.WindSpeed' % (count+1), windspeed) + else: + clear_property('Hourly.%i.WindDirection' % (count+1)) + clear_property('Hourly.%i.WindSpeed' % (count+1)) + + #set_property('Hourly.%i.Temperature' % (count+1), str(item['temperature'])+u'\N{DEGREE SIGN}'+item['temperatureUnit']) + + ## we passed units to api, so we got back C or F, so don't need to convert + set_property('Hourly.%i.Temperature' % (count+1), u'%s%s' % (int(round(item['temperature'])), TEMPUNIT)) + ##if 'F' in TEMPUNIT: + ## set_property('Hourly.%i.Temperature' % (count+1), u'%s%s' % (item['temperature'], TEMPUNIT)) + ##elif 'C' in TEMPUNIT: + ## set_property('Hourly.%i.Temperature' % (count+1), u'%s%s' % (FtoC(item['temperature']), TEMPUNIT)) + + + rain=0 + if item['probabilityOfPrecipitation'] and item['probabilityOfPrecipitation']['value'] : + rain=item['probabilityOfPrecipitation']['value'] + + if rain and str(rain) and not "0" == str(rain): + set_property('Hourly.%i.ChancePrecipitation' % (count+1), str(rain) + '%') + else: + clear_property('Hourly.%i.ChancePrecipitation' % (count+1)) + + humid=0 + if item['relativeHumidity'] and item['relativeHumidity']['value'] : + humid=item['relativeHumidity']['value'] + + if humid and str(humid) and not "0" == str(humid): + set_property('Hourly.%i.Humidity' % (count+1), str(humid) + '%') + else: + clear_property('Hourly.%i.Humidity' % (count+1)) + + dewpoint=0 + if item['dewpoint'] and item['dewpoint']['value'] : + dewpoint=item['dewpoint']['value'] + + if dewpoint and str(dewpoint) and not "0" == str(dewpoint): + ## API is always returning dewpoint in C rather then obeying our prefered units, so convert + if 'F' in TEMPUNIT: + set_property('Hourly.%i.DewPoint' % (count+1), u'%s%s' % (int(round(CtoF(dewpoint))), TEMPUNIT)) + elif 'C' in TEMPUNIT: + set_property('Hourly.%i.DewPoint' % (count+1), u'%s%s' % (int(round(dewpoint)), TEMPUNIT)) + else: + clear_property('Hourly.%i.DewPoint' % (count+1)) + + if 'F' in TEMPUNIT: + ##xbmc.log('api windspeed %s' % (item['windSpeed']),level=xbmc.LOGERROR) + windspeed=0 + if item['windSpeed'] and item['windSpeed'].endswith(" mph"): + windspeed=item['windSpeed'].rstrip(" mph") + ##xbmc.log('windspeed %s' % (windspeed),level=xbmc.LOGERROR) + ##xbmc.log('T:%s W:%s H:%s' % (item['temperature'], windspeed, humid),level=xbmc.LOGERROR) + + try: + feelslike=FEELS_LIKE_F_MPH(item['temperature'], windspeed, humid) + if feelslike: + set_property('Hourly.%i.FeelsLike' % (count+1), u'%s%s' % (int(round(feelslike)), TEMPUNIT)) + else: + clear_property('Hourly.%i.FeelsLike' % (count+1)) + except: + ###xbmc.log('Error Loading Feels-like %s %s %s' % (item['temperature'], windspeed, humid),level=xbmc.LOGERROR) + clear_property('Hourly.%i.FeelsLike' % (count+1)) + try: + windchill=WIND_CHILL_F_MPH(item['temperature'], windspeed) + if windchill: + set_property('Hourly.%i.WindChill' % (count+1), u'%s%s' % (int(round(windchill)), TEMPUNIT)) + else: + clear_property('Hourly.%i.WindChill' % (count+1)) + except: + ##xbmc.log('Error Loading %s %s %s' % (item['temperature'], windspeed, humid),level=xbmc.LOGERROR) + clear_property('Hourly.%i.WindChill' % (count+1)) + try: + heatindex=HEAT_INDEX_F(item['temperature'], humid) + if heatindex: + set_property('Hourly.%i.HeatIndex' % (count+1), u'%s%s' % (int(round(heatindex)), TEMPUNIT)) + else: + clear_property('Hourly.%i.HeatIndex' % (count+1)) + except: + ##xbmc.log('Error Loading %s %s %s' % (item['temperature'], windspeed, humid),level=xbmc.LOGERROR) + clear_property('Hourly.%i.HeatIndex' % (count+1)) + elif 'C' in TEMPUNIT: + try: + windspeed=0 + if item['windSpeed'] and item['windSpeed'].endswith(" km/h"): + windspeed=item['windSpeed'].rstrip(" km/h") + feelslike=FEELS_LIKE_C_KPH(item['temperature'], windspeed, humid) + if feelslike: + set_property('Hourly.%i.FeelsLike' % (count+1), u'%s%s' % (int(round(feelslike)), TEMPUNIT)) + else: + clear_property('Hourly.%i.FeelsLike' % (count+1)) + except: + clear_property('Hourly.%i.FeelsLike' % (count+1)) + try: + windspeed=0 + if item['windSpeed'] and item['windSpeed'].endswith(" km/h"): + windspeed=item['windSpeed'].rstrip(" km/h") + windchill=WIND_CHILL_C_KPH(item['temperature'], windspeed) + if windchill: + set_property('Hourly.%i.WindChill' % (count+1), u'%s%s' % (int(round(windchill)), TEMPUNIT)) + else: + clear_property('Hourly.%i.WindChill' % (count+1)) + except: + #xbmc.log('Error Loading %s %s %s' % (item['temperature'], windspeed, humid),level=xbmc.LOGERROR) + clear_property('Hourly.%i.WindChill' % (count+1)) + try: + heatindex=HEAT_INDEX_C(item['temperature'], humid) + if heatindex: + set_property('Hourly.%i.HeatIndex' % (count+1), u'%s%s' % (int(round(heatindex)), TEMPUNIT)) + else: + clear_property('Hourly.%i.HeatIndex' % (count+1)) + except: + #xbmc.log('Error Loading %s %s %s' % (item['temperature'], windspeed, humid),level=xbmc.LOGERROR) + clear_property('Hourly.%i.HeatIndex' % (count+1)) + + count = 1 + + + ######################################################################################## + ## Grabs map selection from user in settings + ######################################################################################## + + def mapSettings(self,mapid): + s_sel = ADDON.getSetting(mapid+"Sector") + t_sel = ADDON.getSetting(mapid+"Type") + + t_keys = [] + t_values= [] + + #1st option is blank for removing map + t_keys.append("") + t_values.append("") + + + for key,value in MAPTYPES.items(): + t_keys.append(key) + t_values.append(value) + + + dialog = xbmcgui.Dialog() + + ti=0 + try: + ti=t_keys.index(t_sel) + except: + ti=0 + ti=dialog.select(LANGUAGE(32350), t_values,0,ti) + t_sel=t_keys[ti] + ADDON.setSetting(mapid+"Type",t_keys[ti]) + + if ti > 0: + + if ("LOOP" == t_sel): + Sectors=LOOPSECTORS + else: + Sectors=MAPSECTORS + + # convert our map data into matching arrays to pass into dialog + s_keys = [] + s_values= [] + + for key,value in Sectors.items(): + s_keys.append(key) + s_values.append(value['name']) + + # grab index of current region, and pass in as default to dialog + si=0 + try: + si=s_keys.index(s_sel.lower()) + except: + #ignore if we did not find + si=0 + si=dialog.select(LANGUAGE(32349),s_values,0,si) + s_sel=s_keys[si] + ADDON.setSetting(mapid+"Sector",s_sel) + ADDON.setSetting(mapid+"Label",Sectors[s_sel]['name']+":"+MAPTYPES[t_sel]) + ADDON.setSetting(mapid+"Select",Sectors[s_sel]['name']+":"+MAPTYPES[t_sel]) + else: + ADDON.setSetting(mapid+"Label","") + ADDON.setSetting(mapid+"Select","") + + + # clean up referenced dialog object + del dialog + + + + ######################################################################################## + ## Main Kodi entry point + ######################################################################################## + + def __init__(self, sys): + + + log('version %s started with argv: %s' % (ADDON.getAddonInfo('version'), sys.argv[1])) + + + set_property('Forecast.IsFetched','true') + set_property('Current.IsFetched','true') + set_property('Today.IsFetched' , '') + set_property('Daily.IsFetched' , 'true') + set_property('Detailed.IsFetched' , 'true') + set_property('Weekend.IsFetched' , '') + set_property('36Hour.IsFetched' , '') + set_property('Hourly.IsFetched' , 'true') + set_property('NOAA.IsFetched' , 'true') + set_property('WeatherProvider' , 'NOAA') + set_property('WeatherProviderLogo', xbmcvfs.translatePath(os.path.join(ADDON.getAddonInfo('path'), 'resources', 'media', 'skin-banner.png'))) + + + if sys.argv[1].startswith('EnterLocation'): + num=sys.argv[2] + self.enterLocation(num) + + if sys.argv[1].startswith('EnterAddress'): + num=sys.argv[2] + self.get_lat_long_by_address(num) + + + if sys.argv[1].startswith('FetchLocation'): + num=sys.argv[2] + LatLong = ADDON.getSetting("Location"+num+"LatLong") + if not LatLong: + self.enterLocation(num) + elif LatLong: + self.get_Stations(num,LatLong) + + elif sys.argv[1].startswith('Map'): + + self.mapSettings(sys.argv[1]) + + else: + + num=sys.argv[1] + LatLong = ADDON.getSetting('Location%sLatLong' % num) + + station=ADDON.getSetting('Location'+str(num)+'Station') + if station == '' : + log("calling location with %s" % (LatLong)) + self.get_Stations(str(num),LatLong) + + try: + lastPointsCheck=ADDON.getSetting('Location'+str(num)+'lastPointsCheck') + last_check=parse(lastPointsCheck) + current_datetime = datetime.datetime.now() + next_check=last_check+datetime.timedelta(days=2) + if (next_check < current_datetime): + self.get_Points(str(num),LatLong) + except: + self.get_Points(str(num),LatLong) + + self.refresh_locations() + + LatLong = ADDON.getSetting('Location%s' % num) + + if LatLong: + self.fetchWeatherAlerts(num) + if "forecast.weather.gov" == SOURCEPREF: + self.fetchAltDaily(num) + else: + self.fetchCurrent(num) + self.fetchDaily(num) + self.fetchHourly(num) + Station=ADDON.getSetting('Location%sradarStation' % num) + + set_property('Map.IsFetched', 'true') + #KODI will cache and not re-fetch the weather image, so inject a dummy time-stamp into the url to trick kodi because we want the new image + nowtime=str(time.time()) + #Radar + radarLoop=ADDON.getSetting('RadarLoop') + + #clean up previously fetched radar loop images + imagepath=xbmcvfs.translatePath(xbmcaddon.Addon().getAddonInfo('profile')) + for f in glob.glob(imagepath+"radar*.gif"): + os.remove(f) + + if ("true" == radarLoop): + #kodi will not loop gifs from a url, we have to actually + #download to a local file to get it to loop + + #xbmc.log('Option To Loop Radar Selected',level=xbmc.LOGDEBUG) + xbmc.log('Option To Loop Radar Selected',level=xbmc.LOGDEBUG) + url="https://radar.weather.gov/ridge/standard/%s_loop.gif" % (Station) + radarfilename="radar_%s_%s.gif" % (Station,nowtime) + dest=imagepath+radarfilename + loop_image=get_url_image(url, dest) + set_property('Map.%i.Area' % 1, loop_image) + else: + url="https://radar.weather.gov/ridge/standard/%s_0.gif?%s" % (Station,nowtime) + set_property('Map.%i.Area' % 1, url) + #clear_property('Map.%i.Area' % 1) + #set_property('Map.%i.Layer' % 1, url) + + clear_property('Map.%i.Layer' % 1) + set_property('Map.%i.Heading' % 1, LANGUAGE(32334)) + + + # add satellite maps if we configured any + for count in range (1, 5): + mcount=count+1 + mapsector = ADDON.getSetting('Map%iSector' % (mcount)) + maptype = ADDON.getSetting('Map%iType' % (mcount)) + maplabel = ADDON.getSetting('Map%iLabel' % (mcount)) + + if (mapsector and maptype): + + if ("LOOP" == maptype): + # want looping radar gifs + path=LOOPSECTORS.get(mapsector)['path'] + imagepath=xbmcvfs.translatePath(xbmcaddon.Addon().getAddonInfo('profile')) + url="https://radar.weather.gov/%s" % (path) + radarfilename="radar_%s_%s.gif" % (mapsector,nowtime) + dest=imagepath+radarfilename + loop_image=get_url_image(url, dest) + + set_property('Map.%i.Area' % (mcount), loop_image) + set_property('Map.%i.Heading' % (mcount), "%s" % (maplabel) ) + clear_property('Map.%i.Layer' % (mcount)) + else: + # want normal satellite images + path=MAPSECTORS.get(mapsector)['path'] + if mapsector != 'glm-e' and mapsector != 'glm-w': + path=path.replace("%s",maptype) + url="https://cdn.star.nesdis.noaa.gov/%s?%s" % (path,nowtime) + + set_property('Map.%i.Area' % (mcount), url) + set_property('Map.%i.Heading' % (mcount), "%s" % (maplabel) ) + clear_property('Map.%i.Layer' % (mcount)) + else: + clear_property('Map.%i.Area' % (mcount)) + clear_property('Map.%i.Heading' % (mcount)) + clear_property('Map.%i.Layer' % (mcount)) + else: + log('no location provided') + self.clear() + + + + diff --git a/weather.noaa/lib/utils.py b/weather.noaa/lib/utils.py new file mode 100644 index 000000000..77b3ea557 --- /dev/null +++ b/weather.noaa/lib/utils.py @@ -0,0 +1,818 @@ +# -*- coding: utf-8 -*- +#from __future__ import unicode_literals + +#from future import standard_library +import math +import time + +import xbmc, xbmcaddon + +from dateutil.parser import parse +import socket, urllib.request +import json + +#standard_library.install_aliases() + + +ADDON = xbmcaddon.Addon() +ADDONID = ADDON.getAddonInfo('id') +LANGUAGE = ADDON.getLocalizedString +DEBUG = ADDON.getSetting('Debug') +TEMPUNIT = xbmc.getRegion('tempunit') +SPEEDUNIT = xbmc.getRegion('speedunit') +DATEFORMAT = xbmc.getRegion('dateshort') +TIMEFORMAT = xbmc.getRegion('meridiem') + + + + +def log(txt): + if DEBUG == 'true': + message = u'%s: %s' % (ADDONID, txt) + xbmc.log(msg=message, level=xbmc.LOGDEBUG) + + + + +def get_url_JSON(url): + try: + xbmc.log('fetching url: %s' % url,level=xbmc.LOGDEBUG) + try: + timeout = 30 + socket.setdefaulttimeout(timeout) + # this call to urllib.request.urlopen now uses the default timeout + # we have set in the socket module + req = urllib.request.Request(url) + req.add_header('User-Agent', ' Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3') + try: + response = urllib.request.urlopen(req) + except: + time.sleep(60) + response = urllib.request.urlopen(req) + + responsedata = response.read() + data = json.loads(responsedata) + log('data: %s' % data) + # Happy path, we found and parsed data + return data + except: + xbmc.log('failed to parse json: %s' % url,level=xbmc.LOGERROR) + xbmc.log('data: %s' % data,level=xbmc.LOGERROR) + except: + xbmc.log('failed to fetch : %s' % url,level=xbmc.LOGERROR) + return None + + + +def get_url_response(url): + try: + xbmc.log('fetching url: %s' % url,level=xbmc.LOGDEBUG) + timeout = 30 + socket.setdefaulttimeout(timeout) + # this call to urllib.request.urlopen now uses the default timeout + # we have set in the socket module + req = urllib.request.Request(url) + req.add_header('User-Agent', ' Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3') + + try: + response = urllib.request.urlopen(req) + except: + time.sleep(60) + response = urllib.request.urlopen(req) + + responsedata = response.read() + log('data: %s' % responsedata) + # Happy path, we found and parsed data + return responsedata + except: + xbmc.log('failed to fetch : %s' % url,level=xbmc.LOGERROR) + return None + +def get_url_image(url,destination): + try: + urllib.request.urlretrieve(url, destination) + return destination + except: + xbmc.log('failed to fetch : %s' % url,level=xbmc.LOGERROR) + return None + + +WEATHER_CODES = { + + 'day/skc': '32', #'Fair/clear' + 'day/few': '30', #'A few clouds' + 'day/sct': '30', #'Partly cloudy' + 'day/bkn': '28', #'Mostly cloudy' + 'day/ovc': '26', #'Overcast' + 'day/wind_skc': '24', #'Fair/clear and windy' + 'day/wind_few': '30', #'A few clouds and windy' + 'day/wind_sct': '30', #'Partly cloudy and windy' + 'day/wind_bkn': '28', #'Mostly cloudy and windy' + 'day/wind_ovc': '26', #'Overcast and windy' + 'day/snow': '16', #'Snow' + 'day/sn': '16', #'Snow' + 'day/rain_snow': '5', #'Rain/snow' + 'day/rasn': '5', #'Rain/snow' + 'day/rain_sleet': '6', #'Rain/sleet' + 'day/rasl': '6', #'Rain/sleet' + 'day/snow_sleet': '7', #'Snow/sleet' + 'day/snsl': '7', #'Snow/sleet' + 'day/fzra': '10', #'Freezing rain' + 'day/rain_fzra': '10', #'Rain/freezing rain' + 'day/snow_fzra': '10', #'Freezing rain/snow' + 'day/sleet': '18', #'Sleet' + 'day/rain': '40', #'Rain' + 'day/ra': '40', #'Rain' (forecast.gov) + 'day/rain_showers': '11', #'Rain showers (high cloud cover)' + 'day/shrs': '11', #'Rain showers forecast.gov' + 'day/rain_showers_hi': '12', #'Rain showers (low cloud cover)' + 'day/hi_shrs': '12', #'Rain showers (low cloud cover)' + 'day/tsra': '37', #'Thunderstorm (high cloud cover)' + 'day/tsra_sct': '38', #'Thunderstorm (medium cloud cover)' + 'day/tsra_hi': '39', #'Thunderstorm (low cloud cover)' + 'day/tornado': '0', #'Tornado' + 'day/hurricane': '2', #'Hurricane conditions' + 'day/tropical_storm': '1', #'Tropical storm conditions' + 'day/dust': '19', #'Dust' + 'day/smoke': '22', #'Smoke' + 'day/haze': '21', #'Haze' + 'day/hot': '36', #'Hot' + 'day/cold': '25', #'Cold' + 'day/blizzard': '15', #'Blizzard' + 'day/fog': '20', #'Fog/mist' + + + 'night/skc': '31', #'Fair/clear' + 'night/few': '29', #'A few clouds' + 'night/sct': '29', #'Partly cloudy' + 'night/bkn': '27', #'Mostly cloudy' + 'night/ovc': '26', #'Overcast' + 'night/wind_skc': '24', #'Fair/clear and windy' + 'night/wind_few': '29', #'A few clouds and windy' + 'night/wind_sct': '29', #'Partly cloudy and windy' + 'night/wind_bkn': '27', #'Mostly cloudy and windy' + 'night/wind_ovc': '26', #'Overcast and windy' + 'night/snow': '16', #'Snow' + 'night/rain_snow': '5', #'Rain/snow' + 'night/rain_sleet': '6', #'Rain/sleet' + 'night/snow_sleet': '7', #'Rain/sleet' + 'night/fzra': '10', #'Freezing rain' + 'night/rain_fzra': '10', #'Rain/freezing rain' + 'night/snow_fzra': '10', #'Freezing rain/snow' + 'night/sleet': '18', #'Sleet' + 'night/rain': '40', #'Rain' + 'night/rain_showers': '11', #'Rain showers (high cloud cover)' + 'night/rain_showers_hi': '12', #'Rain showers (low cloud cover)' + 'night/tsra': '37', #'Thunderstorm (high cloud cover)' + 'night/tsra_sct': '38', #'Thunderstorm (medium cloud cover)' + 'night/tsra_hi': '39', #'Thunderstorm (low cloud cover)' + 'night/tornado': '0', #'Tornado' + 'night/hurricane': '2', #'Hurricane conditions' + 'night/tropical_storm': '1', #'Tropical storm conditions' + 'night/dust': '19', #'Dust' + 'night/smoke': '22', #'Smoke' + 'night/haze': '21', #'Haze' + 'night/hot': '36', #'Hot' + 'night/cold': '25', #'Cold' + 'night/blizzard': '15', #'Blizzard' + 'night/fog': '20', #'Fog/mist' + + # special hanlding of forecast.weather.com url patterns that use "nxyz" pattersn for night + 'day/nskc': '31', #'Fair/clear' + 'day/nfew': '29', #'A few clouds' + 'day/nsct': '29', #'Partly cloudy' + 'day/nbkn': '27', #'Mostly cloudy' + 'day/novc': '26', #'Overcast' + 'day/nwind_skc': '24', #'Fair/clear and windy' + 'day/nwind_few': '29', #'A few clouds and windy' + 'day/nwind_sct': '29', #'Partly cloudy and windy' + 'day/nwind_bkn': '27', #'Mostly cloudy and windy' + 'day/nwind_ovc': '26', #'Overcast and windy' + 'day/nsn': '16', #'Snow' + 'day/nrasn': '5', #'Rain/snow' + 'day/nrasl': '6', #'Rain/sleet' + 'day/nsnsl': '7', #'Rain/sleet' + 'day/nfzra': '10', #'Freezing rain' + 'day/nrafzra': '10', #'Rain/freezing rain' + 'day/nsnfzra': '10', #'Freezing rain/snow' + 'day/nsl': '18', #'Sleet' + 'day/nra': '40', #'Rain' + 'day/nshra': '11', #'Rain showers (high cloud cover)' + 'day/hi_nshra': '12', #'Rain showers (low cloud cover)' + 'day/ntsra': '37', #'Thunderstorm (high cloud cover)' + 'day/ntsra_sct': '38', #'Thunderstorm (medium cloud cover)' + 'day/ntsra_hi': '39', #'Thunderstorm (low cloud cover)' + 'day/ntornado': '0', #'Tornado' + 'day/nhurricane': '2', #'Hurricane conditions' + 'day/ntropical_storm': '1', #'Tropical storm conditions' + 'day/ndust': '19', #'Dust' + 'day/nsmoke': '22', #'Smoke' + 'day/nhaze': '21', #'Haze' + 'day/nhot': '36', #'Hot' + 'day/ncold': '25', #'Cold' + 'day/nblizzard': '15', #'Blizzard' + 'day/nfog': '20', #'Fog/mist' + '': 'na' + } + +MONTH_NAME_LONG = { '01' : 21, + '02' : 22, + '03' : 23, + '04' : 24, + '05' : 25, + '06' : 26, + '07' : 27, + '08' : 28, + '09' : 29, + '10' : 30, + '11' : 31, + '12' : 32 + } + +MONTH_NAME_SHORT = { '01' : 51, + '02' : 52, + '03' : 53, + '04' : 54, + '05' : 55, + '06' : 56, + '07' : 57, + '08' : 58, + '09' : 59, + '10' : 60, + '11' : 61, + '12' : 62 + } + +WEEK_DAY_LONG = { '0' : 17, + '1' : 11, + '2' : 12, + '3' : 13, + '4' : 14, + '5' : 15, + '6' : 16 + } + +WEEK_DAY_SHORT = { '0' : 47, + '1' : 41, + '2' : 42, + '3' : 43, + '4' : 44, + '5' : 45, + '6' : 46 + } + +FORECAST = { + 'thunderstorm with light rain': LANGUAGE(32201), + 'thunderstorm with rain': LANGUAGE(32202), + 'thunderstorm with heavy rain': LANGUAGE(32203), + 'light thunderstorm': LANGUAGE(32204), + 'thunderstorm': LANGUAGE(32205), + 'heavy thunderstorm': LANGUAGE(32206), + 'ragged thunderstorm': LANGUAGE(32207), + 'thunderstorm with light drizzle': LANGUAGE(32208), + 'thunderstorm with drizzle': LANGUAGE(32209), + 'thunderstorm with heavy drizzle': LANGUAGE(32210), + 'light intensity drizzle': LANGUAGE(32211), + 'drizzle': LANGUAGE(32212), + 'heavy intensity drizzle': LANGUAGE(32213), + 'light intensity drizzle rain': LANGUAGE(32214), + 'drizzle rain': LANGUAGE(32215), + 'heavy intensity drizzle rain': LANGUAGE(32216), + 'shower rain And drizzle': LANGUAGE(32217), + 'heavy shower rain and drizzle': LANGUAGE(32218), + 'shower drizzle': LANGUAGE(32219), + 'light rain': LANGUAGE(32220), + 'moderate rain': LANGUAGE(32221), + 'heavy intensity rain': LANGUAGE(32222), + 'very heavy rain': LANGUAGE(32223), + 'extreme rain': LANGUAGE(32224), + 'freezing rain': LANGUAGE(32225), + 'light intensity shower rain': LANGUAGE(32226), + 'shower rain': LANGUAGE(32227), + 'heavy intensity shower rain': LANGUAGE(32228), + 'ragged shower rain': LANGUAGE(32229), + 'light snow': LANGUAGE(32230), + 'snow': LANGUAGE(32231), + 'heavy snow': LANGUAGE(32232), + 'sleet': LANGUAGE(32233), + 'shower sleet': LANGUAGE(32234), + 'light rain and snow': LANGUAGE(32235), + 'rain and snow': LANGUAGE(32236), + 'light shower snow': LANGUAGE(32237), + 'shower snow': LANGUAGE(32238), + 'heavy shower snow': LANGUAGE(32239), + 'mist': LANGUAGE(32240), + 'smoke': LANGUAGE(32241), + 'haze': LANGUAGE(32242), + 'sand, dust whirls': LANGUAGE(32243), + 'fog': LANGUAGE(32244), + 'sand': LANGUAGE(32245), + 'dust': LANGUAGE(32246), + 'volcanic ash': LANGUAGE(32247), + 'squalls': LANGUAGE(32248), + 'tornado': LANGUAGE(32249), + 'clear sky': LANGUAGE(32250), + 'few clouds': LANGUAGE(32251), + 'scattered clouds': LANGUAGE(32252), + 'broken clouds': LANGUAGE(32253), + 'overcast clouds': LANGUAGE(32254), + 'tornado': LANGUAGE(32255), + 'tropical storm': LANGUAGE(32256), + 'hurricane': LANGUAGE(32257), + 'cold': LANGUAGE(32258), + 'hot': LANGUAGE(32259), + 'windy': LANGUAGE(32260), + 'hail': LANGUAGE(32261), + 'calm': LANGUAGE(32262), + 'light breeze': LANGUAGE(32263), + 'gentle breeze': LANGUAGE(32264), + 'moderate breeze': LANGUAGE(32265), + 'fresh breeze': LANGUAGE(32266), + 'strong breeze': LANGUAGE(32267), + 'high wind, near gale': LANGUAGE(32268), + 'gale': LANGUAGE(32269), + 'severe gale': LANGUAGE(32270), + 'storm': LANGUAGE(32271), + 'violent storm': LANGUAGE(32272), + 'hurricane': LANGUAGE(32273), + 'clear': LANGUAGE(32274), + 'clouds': LANGUAGE(32275), + 'rain': LANGUAGE(32276) + } + +#def SPEED(mps): +# try: +# val = float(mps) +# except: +# return '' +# +# if SPEEDUNIT == 'km/h': +# speed = mps * 3.6 +# elif SPEEDUNIT == 'm/min': +# speed = mps * 60 +# elif SPEEDUNIT == 'ft/h': +# speed = mps * 11810.88 +# elif SPEEDUNIT == 'ft/min': +# speed = mps * 196.84 +# elif SPEEDUNIT == 'ft/s': +# speed = mps * 3.281 +# elif SPEEDUNIT == 'mph': +# speed = mps * 2.237 +# elif SPEEDUNIT == 'knots': +# speed = mps * 1.944 +# elif SPEEDUNIT == 'Beaufort': +# speed = KPHTOBFT(mps* 3.6) +# elif SPEEDUNIT == 'inch/s': +# speed = mps * 39.37 +# elif SPEEDUNIT == 'yard/s': +# speed = mps * 1.094 +# elif SPEEDUNIT == 'Furlong/Fortnight': +# speed = mps * 6012.886 +# else: +# speed = mps +# return str(int(round(speed))) + + + +def FtoC(Fahrenheit): + try: + Celsius = (float(Fahrenheit) - 32.0) * 5.0/9.0 + return Celsius + except: + return + +def CtoF(Celsius): + try: + Fahrenheit = (float(Celsius) * 9.0/5.0) + 32.0 + return Fahrenheit + except: + return + + + +def TEMP(deg): + if TEMPUNIT == u'\N{DEGREE SIGN}'+'F': + temp = deg * 1.8 + 32 + elif TEMPUNIT == u'K': + temp = deg + 273.15 + elif TEMPUNIT == u'°Ré': + temp = deg * 0.8 + elif TEMPUNIT == u'°Ra': + temp = deg * 1.8 + 491.67 + elif TEMPUNIT == u'°Rø': + temp = deg * 0.525 + 7.5 + elif TEMPUNIT == u'°D': + temp = deg / -0.667 + 150 + elif TEMPUNIT == u'°N': + temp = deg * 0.33 + else: + temp = deg + return str(int(round(temp))) + +def WIND_DIR(deg): + if deg >= 349 or deg <= 11: + return 71 + elif deg >= 12 and deg <= 33: + return 72 + elif deg >= 34 and deg <= 56: + return 73 + elif deg >= 57 and deg <= 78: + return 74 + elif deg >= 79 and deg <= 101: + return 75 + elif deg >= 102 and deg <= 123: + return 76 + elif deg >= 124 and deg <= 146: + return 77 + elif deg >= 147 and deg <= 168: + return 78 + elif deg >= 169 and deg <= 191: + return 79 + elif deg >= 192 and deg <= 213: + return 80 + elif deg >= 214 and deg <= 236: + return 81 + elif deg >= 237 and deg <= 258: + return 82 + elif deg >= 259 and deg <= 281: + return 83 + elif deg >= 282 and deg <= 303: + return 84 + elif deg >= 304 and deg <= 326: + return 85 + elif deg >= 327 and deg <= 348: + return 86 + +#def KPHTOBFT(spd): +# if (spd < 1.0): +# bft = '0' +# elif (spd >= 1.0) and (spd < 5.6): +# bft = '1' +# elif (spd >= 5.6) and (spd < 12.0): +# bft = '2' +# elif (spd >= 12.0) and (spd < 20.0): +# bft = '3' +# elif (spd >= 20.0) and (spd < 29.0): +# bft = '4' +# elif (spd >= 29.0) and (spd < 39.0): +# bft = '5' +# elif (spd >= 39.0) and (spd < 50.0): +# bft = '6' +# elif (spd >= 50.0) and (spd < 62.0): +# bft = '7' +# elif (spd >= 62.0) and (spd < 75.0): +# bft = '8' +# elif (spd >= 75.0) and (spd < 89.0): +# bft = '9' +# elif (spd >= 89.0) and (spd < 103.0): +# bft = '10' +# elif (spd >= 103.0) and (spd < 118.0): +# bft = '11' +# elif (spd >= 118.0): +# bft = '12' +# else: +# bft = '' +# return bft + + + +def FEELS_LIKE_C_KPH(Ts, Vs=0, Hs=0): + #xbmc.log('Running FEELS_LIKE_C_KPH: %s %s %s' % (Ts, Vs, Hs),level=xbmc.LOGERROR) + if not Vs: + Vs=0 + T=float(Ts) + V=float(Vs) + H=float(Hs) + # first check if we have a wind-chill value + windchill = WIND_CHILL_C_KPH(T, V) + if windchill and windchill < T : + return windchill + else: # otherwise, check for heat index + heatindex = HEAT_INDEX_C(T, H) + if heatindex and heatindex > T and heatindex > 80 : + return heatindex + # otherwise, neither windchill nor heatindex apply + #xbmc.log('FEELS_LIKE_C_KPH: Not Applicable',level=xbmc.LOGERROR) + return + + +def FEELS_LIKE_F_MPH(Ts, Vs=0, Hs=0): + #xbmc.log('Running FEELS_LIKE_F_MPH: %s %s %s' % (Ts, Vs, Hs),level=xbmc.LOGERROR) + + if not Vs: + Vs=0 + T=float(Ts) + V=float(Vs) + H=float(Hs) + #xbmc.log('Running FEELS_LIKE_F_MPH: %s %s %s' % (T, V, H),level=xbmc.LOGERROR) + # first check if we have a wind-chill value + windchill = WIND_CHILL_F_MPH(T, V) + #xbmc.log('windchill returns: %s' % (windchill),level=xbmc.LOGERROR) + + if windchill and windchill < T: + return windchill + else: # otherwise, check for heat index + heatindex = HEAT_INDEX_F(T, H) + ##xbmc.log'heatindex returns: %s' % (heatindex),level=xbmc.LOGERROR) + + if heatindex and heatindex > T+2 and heatindex > 80 : + return heatindex + # otherwise, neither windchill nor heatindex apply + #xbmc.log('FEELS_LIKE_F_MPH: Not Applicable',level=xbmc.LOGERROR) + return + + +def WIND_CHILL_F_MPH(Ts, Vs): + T=float(Ts) + V=float(Vs) + ##xbmc.log('wind_chill_f_mph %s %s' % (T, V),level=xbmc.LOGERROR) + if T <= 50.0 and V >= 3.0: + #xbmc.log('We are in windchill range %s' % (T),level=xbmc.LOGERROR) + WC=35.74 + (0.6215 * T) - (35.75 * math.pow(V,0.16)) + (0.4275 * T * math.pow(V,0.16)) + ##xbmc.log('WindChill for %sF %s mph = %sF' % (T, V, WC),level=xbmc.LOGERROR) + if WC < T-2.0: + #xbmc.log('WindChill for %sF %s mph = %sF' % (T, V, WC),level=xbmc.LOGERROR) + return WC + + # otherwise, windchill is not relevant, so return + return + + +def WIND_CHILL_C_KPH(Ts, Vs): + T=float(Ts) + V=float(Vs) + TF = CtoF(T) + Vmph = V/1.609344 + windchill=WIND_CHILL_F_MPH(TF,Vmph) + if windchill: + return FtoC(windchill) + # otherwise, no windchill so return + return + + +# https://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml +def HEAT_INDEX_F(Ts, Rs): + T=float(Ts) + R=float(Rs) + ##xbmc.log'Heat_index_F %sF %s' % (T, R),level=xbmc.LOGERROR) + if T <40: # too cold for heat indexes + return + # Try simple formula first + HI = 0.5 * (T + 61.0 + ((T-68.0)*1.2) + (R*0.094)) + ##xbmc.log'Initial HI is %sF' % (HI),level=xbmc.LOGERROR) + # Test if simply formula is applicable + if HI > 80: # then we need to use the full fancy formula + ##xbmc.log'HI is over 80 %sF' % (HI),level=xbmc.LOGERROR) + HI = ( -42.379 + + 2.04901523*T + + 10.14333127*R + - .22475541*T*R + - .00683783*T*T + - .05481717*R*R + + .00122874*T*T*R + + .00085282*T*R*R + - .00000199*T*T*R*R + ) + ##xbmc.log('Fancy hi 1 is %sF' % (HI),level=xbmc.LOGERROR) + if R < 12 and T >80 and T <115: + ADJUSTMENT = ( (13.0-R)/4.0 ) * math.sqrt( ( 17.0-math.fabs( T-95.0) ) / 17.0 ) + HI = HI - ADJUSTMENT + ##xbmc.log('adjusted hi 2 is %sF' % (HI),level=xbmc.LOGERROR) + if R > 85 and T >80 and T <87: + ADJUSTMENT = ( (R-85.0)/10.0 ) * ( (87.0-T)/5.0 ) + HI = HI + ADJUSTMENT + ##xbmc.log('adjusted hi 3 is %sF' % (HI),level=xbmc.LOGERROR) + ##xbmc.log'Final HI is %sF' % (HI),level=xbmc.LOGERROR) + if HI > 80 and HI > (T+2): # if we have a heat-index, over 80 (and it's highter then the normal temp) then return it + ##xbmc.log'Heat Index for %sF %sH = %sF' % (T, R, HI),level=xbmc.LOGERROR) + return HI + +def HEAT_INDEX_C(Ts, Rs): + T=float(Ts) + R=float(Rs) + TF = CtoF(T) # calaculation is done in F + HI = HEAT_INDEX_F(TF, R) + if HI: + return FtoC(HI) + # otherwise, no relevennt heat index so return + + +#### thanks to FrostBox @ http://forum.kodi.tv/showthread.php?tid=114637&pid=937168#pid937168 +def DEW_POINT(Tc=0.0, R=93.0, ext=True, minR=( 0, 0.075 )[ 0 ]): + Es = 6.11 * math.pow(10.0,( 7.5 * Tc / ( 237.7 + Tc ) )) + R = R or minR + E = ( R * Es ) / 100.0 + try: + DewPoint = ( -430.22 + 237.7 * math.log( E ) ) / ( -math.log( E ) + 19.08 ) + except ValueError: + DewPoint = 0 + if ext: + return TEMP( DewPoint ) + else: + return str(int(round(DewPoint))) + + +## a couple functions from itertools +def repeat_x(object_x, times=None): + # repeat(10, 3) --> 10 10 10 + if times is None: + while True: + yield object_x + else: + for i in range(times): + yield object_x + +def zip_x(fill, *args): + # zip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D- + iterators = [iter(it) for it in args] + num_active = len(iterators) + if not num_active: + return + while True: + values = [] + for i, it in enumerate(iterators): + try: + value = next(it) + except StopIteration: + num_active -= 1 + if not num_active: + return + iterators[i] = repeat_x(fill) + value = fill + values.append(value) + yield tuple(values) + +def get_timestamp(datestr): + #"2019-04-29T16:00:00-04:00" + #iso_fmt = '%Y-%m-%dT%H:%M:%S%z' + datestamp=parse(datestr) + return time.mktime(datestamp.timetuple()) + + +def convert_date(stamp): + if str(stamp).startswith('-'): + return '' + date_time = time.localtime(stamp) + if DATEFORMAT[1] == 'd' or DATEFORMAT[0] == 'D': + localdate = time.strftime('%d-%m-%Y', date_time) + elif DATEFORMAT[1] == 'm' or DATEFORMAT[0] == 'M': + localdate = time.strftime('%m-%d-%Y', date_time) + else: + localdate = time.strftime('%Y-%m-%d', date_time) + + if TIMEFORMAT != '/': + localtime = time.strftime('%I:%M%p', date_time) + else: + localtime = time.strftime('%H:%M', date_time) + return localtime + ' ' + localdate + +def get_time(stamp): + date_time = time.localtime(stamp) + if TIMEFORMAT != '/': + localtime = time.strftime('%I:%M%p', date_time) + else: + localtime = time.strftime('%H:%M', date_time) + return localtime + +def get_weekday(stamp, form): + date_time = time.localtime(stamp) + weekday = time.strftime('%w', date_time) + if form == 's': + return xbmc.getLocalizedString(WEEK_DAY_SHORT[weekday]) + elif form == 'l': + return xbmc.getLocalizedString(WEEK_DAY_LONG[weekday]) + else: + return int(weekday) + +#def get_month(stamp, form): +# date_time = time.localtime(stamp) +# month = time.strftime('%m', date_time) +# day = time.strftime('%d', date_time) +# weekday = time.strftime('%u', date_time)-1 +# if form == 'ds': +# label = xbmc.getLocalizedString(MONTH_NAME_SHORT[month]) +# elif form == 'dl': +# label = xbmc.getLocalizedString(MONTH_NAME_LONG[month]) +# elif form == 'ms': +# label = xbmc.getLocalizedString(MONTH_NAME_SHORT[month]) +# elif form == 'ml': +# label = xbmc.getLocalizedString(MONTH_NAME_LONG[month]) +# return label + +def get_fulldatestr(stamp,form): + date_time = time.localtime(stamp) + month = time.strftime('%m', date_time) + day = time.strftime('%d', date_time) + weekday = time.strftime('%w', date_time) + if form == 'ds': + label = xbmc.getLocalizedString(WEEK_DAY_SHORT[weekday]) + ' ' + day + ' ' + xbmc.getLocalizedString(MONTH_NAME_SHORT[month]) + elif form == 'dl': + label = xbmc.getLocalizedString(WEEK_DAY_LONG[weekday]) + ' ' + day + ' ' + xbmc.getLocalizedString(MONTH_NAME_LONG[month]) + elif form == 'ms': + label = xbmc.getLocalizedString(WEEK_DAY_SHORT[weekday]) + ' ' + xbmc.getLocalizedString(MONTH_NAME_SHORT[month]) + ' ' + day + elif form == 'ml': + label = xbmc.getLocalizedString(WEEK_DAY_LONG[weekday]) + ' ' + xbmc.getLocalizedString(MONTH_NAME_LONG[month]) + ' ' + day + return label + +def get_datestr(stamp,form): + date_time = time.localtime(stamp) + month = time.strftime('%m', date_time) + day = time.strftime('%d', date_time) + if form == 'ds': + label = day + ' ' + xbmc.getLocalizedString(MONTH_NAME_SHORT[month]) + elif form == 'dl': + label = day + ' ' + xbmc.getLocalizedString(MONTH_NAME_LONG[month]) + elif form == 'ms': + label = xbmc.getLocalizedString(MONTH_NAME_SHORT[month]) + ' ' + day + elif form == 'ml': + label = xbmc.getLocalizedString(MONTH_NAME_LONG[month]) + ' ' + day + return label + +# Satellite Imagery paths + +MAPSECTORS = { + "conus-e": {"name":LANGUAGE(32360),"path":"GOES16/ABI/CONUS/%s/1250x750.jpg"}, + "conus-w": {"name":LANGUAGE(32361),"path":"GOES17/ABI/CONUS/%s/1250x750.jpg"}, + "glm-e": {"name":LANGUAGE(32362),"path":"GOES16/GLM/CONUS/EXTENT/1250x750.jpg"}, + "glm-w": {"name":LANGUAGE(32363),"path":"GOES17/GLM/CONUS/EXTENT/1250x750.jpg"}, + "ak": {"name":LANGUAGE(32364),"path":"GOES17/ABI/SECTOR/ak/%s/1000x1000.jpg"}, + "cak": {"name":LANGUAGE(32365),"path":"GOES17/ABI/SECTOR/cak/%s/1200x1200.jpg"}, + "sea": {"name":LANGUAGE(32366),"path":"GOES17/ABI/SECTOR/sea/%s/1200x1200.jpg"}, + "np": {"name":LANGUAGE(32367),"path":"GOES17/ABI/SECTOR/np/%s/900x540.jpg"}, + "wus": {"name":LANGUAGE(32368),"path":"GOES17/ABI/SECTOR/wus/%s/1000x1000.jpg"}, + "pnw-w": {"name":LANGUAGE(32369),"path":"GOES17/ABI/SECTOR/pnw/%s/1200x1200.jpg"}, + "pnw-e": {"name":LANGUAGE(32370),"path":"GOES16/ABI/SECTOR/pnw/%s/1200x1200.jpg"}, + "psw-w": {"name":LANGUAGE(32371),"path":"GOES17/ABI/SECTOR/psw/%s/1200x1200.jpg"}, + "psw-e": {"name":LANGUAGE(32371),"path":"GOES16/ABI/SECTOR/psw/%s/1200x1200.jpg"}, + "nr": {"name":LANGUAGE(32373),"path":"GOES16/ABI/SECTOR/nr/%s/1200x1200.jpg"}, + "sr": {"name":LANGUAGE(32374),"path":"GOES16/ABI/SECTOR/sr/%s/1200x1200.jpg"}, + "sp": {"name":LANGUAGE(32375),"path":"GOES16/ABI/SECTOR/sp/%s/1200x1200.jpg"}, + "umv": {"name":LANGUAGE(32376),"path":"GOES16/ABI/SECTOR/umv/%s/1200x1200.jpg"}, + "smv": {"name":LANGUAGE(32377),"path":"GOES16/ABI/SECTOR/smv/%s/1200x1200.jpg"}, + "can": {"name":LANGUAGE(32378),"path":"GOES16/ABI/SECTOR/can/%s/1125x560.jpg"}, + "cgl": {"name":LANGUAGE(32379),"path":"GOES16/ABI/SECTOR/cgl/%s/1200x1200.jpg"}, + "eus": {"name":LANGUAGE(32380),"path":"GOES16/ABI/SECTOR/eus/%s/1000x1000.jpg"}, + "ne": {"name":LANGUAGE(32381),"path":"GOES16/ABI/SECTOR/ne/%s/1200x1200.jpg"}, + "na": {"name":LANGUAGE(32382),"path":"GOES16/ABI/SECTOR/na/%s/900x540.jpg"}, + "se": {"name":LANGUAGE(32383),"path":"GOES16/ABI/SECTOR/se/%s/1200x1200.jpg"}, + "car": {"name":LANGUAGE(32384),"path":"GOES16/ABI/SECTOR/car/%s/1000x1000.jpg"}, + "pr": {"name":LANGUAGE(32385),"path":"GOES16/ABI/SECTOR/pr/%s/1200x1200.jpg"}, + "gm": {"name":LANGUAGE(32386),"path":"GOES16/ABI/SECTOR/gm/%s/1000x1000.jpg"}, + "taw": {"name":LANGUAGE(32387),"path":"GOES16/ABI/SECTOR/taw/%s/900x540.jpg"}, + "mex": {"name":LANGUAGE(32388),"path":"GOES16/ABI/SECTOR/mex/%s/1000x1000.jpg"}, + "hi": {"name":LANGUAGE(32389),"path":"GOES17/ABI/SECTOR/hi/%s/1200x1200.jpg"}, + "tpw": {"name":LANGUAGE(32390),"path":"GOES17/ABI/SECTOR/tpw/%s/900x540.jpg"}, + "tsp": {"name":LANGUAGE(32391),"path":"GOES17/ABI/SECTOR/tsp/%s/900x540.jpg"}, + "eep": {"name":LANGUAGE(32392),"path":"GOES16/ABI/SECTOR/eep/%s/900x540.jpg"}, + "cam": {"name":LANGUAGE(32393),"path":"GOES16/ABI/SECTOR/cam/%s/1000x1000.jpg"}, + "nsa": {"name":LANGUAGE(32394),"path":"GOES16/ABI/SECTOR/nsa/%s/900x540.jpg"}, + "ssa": {"name":LANGUAGE(32395),"path":"GOES16/ABI/SECTOR/ssa/%s/900x540.jpg"} + } + +MAPTYPES = { + "LOOP": LANGUAGE(32333), + "GEOCOLOR": LANGUAGE(32400), + "EXTENT": LANGUAGE(32401), + "Sandwich": LANGUAGE(32402), + "AirMass": LANGUAGE(32404), + "DayCloudPhase": LANGUAGE(32405), + "NightMicrophysics": LANGUAGE(32406), + "DMW": LANGUAGE(32407), + "Dust": LANGUAGE(32408), + "01": LANGUAGE(32409), + "02": LANGUAGE(32410), + "03": LANGUAGE(32411), + "04": LANGUAGE(32412), + "05": LANGUAGE(32413), + "06": LANGUAGE(32414), + "07": LANGUAGE(32415), + "08": LANGUAGE(32416), + "09": LANGUAGE(32417), + "10": LANGUAGE(32418), + "11": LANGUAGE(32419), + "12": LANGUAGE(32420), + "13": LANGUAGE(32421), + "14": LANGUAGE(32422), + "15": LANGUAGE(32423), + "16": LANGUAGE(32424) + } + +LOOPSECTORS = { + ##ridge/standard/CONUS_loop.gif + "us": {"name":LANGUAGE(32396),"path":"ridge/standard/CONUS-LARGE_loop.gif"}, + "pnw": {"name":LANGUAGE(32369),"path":"ridge/standard/PACNORTHWEST_loop.gif"}, + "psw": {"name":LANGUAGE(32371),"path":"ridge/standard/PACSOUTHWEST_loop.gif"}, + "nr": {"name":LANGUAGE(32373),"path":"ridge/standard/NORTHROCKIES_loop.gif"}, + "sr": {"name":LANGUAGE(32374),"path":"ridge/standard/SOUTHROCKIES_loop.gif"}, + "sp": {"name":LANGUAGE(32375),"path":"ridge/standard/SOUTHPLAINS_loop.gif"}, + "umv": {"name":LANGUAGE(32376),"path":"ridge/standard/UPPERMISSVLY_loop.gif"}, + "smv": {"name":LANGUAGE(32377),"path":"ridge/standard/SOUTHMISSVLY_loop.gif"}, + "cgl": {"name":LANGUAGE(32379),"path":"ridge/standard/CENTGRLAKES_loop.gif"}, + "ne": {"name":LANGUAGE(32381),"path":"ridge/standard/NORTHEAST_loop.gif"}, + "se": {"name":LANGUAGE(32383),"path":"ridge/standard/SOUTHEAST_loop.gif"}, + "car": {"name":LANGUAGE(32384),"path":"ridge/standard/CARIB_loop.gif"}, + "ak": {"name":LANGUAGE(32364),"path":"ridge/standard/ALASKA_loop.gif"}, + "hi": {"name":LANGUAGE(32389),"path":"ridge/standard/HAWAII_loop.gif"}, + "guam": {"name":LANGUAGE(32397),"path":"ridge/standard/GUAM_loop.gif"} + } + diff --git a/weather.noaa/resources/lib/utils.py b/weather.noaa/resources/lib/utils.py deleted file mode 100644 index b5bd018b9..000000000 --- a/weather.noaa/resources/lib/utils.py +++ /dev/null @@ -1,798 +0,0 @@ -# -*- coding: utf-8 -*- -#from __future__ import unicode_literals - -#from future import standard_library -import math -import time - -import xbmc, xbmcaddon - -from dateutil.parser import parse -import socket, urllib.request -import json - -#standard_library.install_aliases() - - -ADDON = xbmcaddon.Addon() -ADDONID = ADDON.getAddonInfo('id') -LANGUAGE = ADDON.getLocalizedString - -DEBUG = ADDON.getSetting('Debug') -TEMPUNIT = xbmc.getRegion('tempunit') -SPEEDUNIT = xbmc.getRegion('speedunit') -DATEFORMAT = xbmc.getRegion('dateshort') -TIMEFORMAT = xbmc.getRegion('meridiem') - - -def log(txt): - if DEBUG == 'true': - message = u'%s: %s' % (ADDONID, txt) - xbmc.log(msg=message, level=xbmc.LOGDEBUG) - - - - -def get_url_JSON(url): - try: - xbmc.log('fetching url: %s' % url,level=xbmc.LOGDEBUG) - try: - timeout = 30 - socket.setdefaulttimeout(timeout) - # this call to urllib.request.urlopen now uses the default timeout - # we have set in the socket module - req = urllib.request.Request(url) - req.add_header('User-Agent', ' Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3') - try: - response = urllib.request.urlopen(req) - except: - time.sleep(60) - response = urllib.request.urlopen(req) - - responsedata = response.read() - data = json.loads(responsedata) - log('data: %s' % data) - # Happy path, we found and parsed data - return data - except: - xbmc.log('failed to parse json: %s' % url,level=xbmc.LOGERROR) - xbmc.log('data: %s' % data,level=xbmc.LOGERROR) - except: - xbmc.log('failed to fetch : %s' % url,level=xbmc.LOGERROR) - return None - - - -def get_url_response(url): - try: - xbmc.log('fetching url: %s' % url,level=xbmc.LOGDEBUG) - timeout = 30 - socket.setdefaulttimeout(timeout) - # this call to urllib.request.urlopen now uses the default timeout - # we have set in the socket module - req = urllib.request.Request(url) - req.add_header('User-Agent', ' Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3') - - try: - response = urllib.request.urlopen(req) - except: - time.sleep(60) - response = urllib.request.urlopen(req) - - responsedata = response.read() - log('data: %s' % responsedata) - # Happy path, we found and parsed data - return responsedata - except: - xbmc.log('failed to fetch : %s' % url,level=xbmc.LOGERROR) - return None - -def get_url_image(url,destination): - try: - urllib.request.urlretrieve(url, destination) - return destination - except: - xbmc.log('failed to fetch : %s' % url,level=xbmc.LOGERROR) - return None - - -WEATHER_CODES = { - - 'day/skc': '32', #'Fair/clear' - 'day/few': '30', #'A few clouds' - 'day/sct': '30', #'Partly cloudy' - 'day/bkn': '28', #'Mostly cloudy' - 'day/ovc': '26', #'Overcast' - 'day/wind_skc': '24', #'Fair/clear and windy' - 'day/wind_few': '30', #'A few clouds and windy' - 'day/wind_sct': '30', #'Partly cloudy and windy' - 'day/wind_bkn': '28', #'Mostly cloudy and windy' - 'day/wind_ovc': '26', #'Overcast and windy' - 'day/snow': '16', #'Snow' - 'day/sn': '16', #'Snow' - 'day/rain_snow': '5', #'Rain/snow' - 'day/rasn': '5', #'Rain/snow' - 'day/rain_sleet': '6', #'Rain/sleet' - 'day/rasl': '6', #'Rain/sleet' - 'day/snow_sleet': '7', #'Snow/sleet' - 'day/snsl': '7', #'Snow/sleet' - 'day/fzra': '10', #'Freezing rain' - 'day/rain_fzra': '10', #'Rain/freezing rain' - 'day/snow_fzra': '10', #'Freezing rain/snow' - 'day/sleet': '18', #'Sleet' - 'day/rain': '40', #'Rain' - 'day/ra': '40', #'Rain' (forecast.gov) - 'day/rain_showers': '11', #'Rain showers (high cloud cover)' - 'day/shrs': '11', #'Rain showers forecast.gov' - 'day/rain_showers_hi': '12', #'Rain showers (low cloud cover)' - 'day/hi_shrs': '12', #'Rain showers (low cloud cover)' - 'day/tsra': '37', #'Thunderstorm (high cloud cover)' - 'day/tsra_sct': '38', #'Thunderstorm (medium cloud cover)' - 'day/tsra_hi': '39', #'Thunderstorm (low cloud cover)' - 'day/tornado': '0', #'Tornado' - 'day/hurricane': '2', #'Hurricane conditions' - 'day/tropical_storm': '1', #'Tropical storm conditions' - 'day/dust': '19', #'Dust' - 'day/smoke': '22', #'Smoke' - 'day/haze': '21', #'Haze' - 'day/hot': '36', #'Hot' - 'day/cold': '25', #'Cold' - 'day/blizzard': '15', #'Blizzard' - 'day/fog': '20', #'Fog/mist' - - - 'night/skc': '31', #'Fair/clear' - 'night/few': '29', #'A few clouds' - 'night/sct': '29', #'Partly cloudy' - 'night/bkn': '27', #'Mostly cloudy' - 'night/ovc': '26', #'Overcast' - 'night/wind_skc': '24', #'Fair/clear and windy' - 'night/wind_few': '29', #'A few clouds and windy' - 'night/wind_sct': '29', #'Partly cloudy and windy' - 'night/wind_bkn': '27', #'Mostly cloudy and windy' - 'night/wind_ovc': '26', #'Overcast and windy' - 'night/snow': '16', #'Snow' - 'night/rain_snow': '5', #'Rain/snow' - 'night/rain_sleet': '6', #'Rain/sleet' - 'night/snow_sleet': '7', #'Rain/sleet' - 'night/fzra': '10', #'Freezing rain' - 'night/rain_fzra': '10', #'Rain/freezing rain' - 'night/snow_fzra': '10', #'Freezing rain/snow' - 'night/sleet': '18', #'Sleet' - 'night/rain': '40', #'Rain' - 'night/rain_showers': '11', #'Rain showers (high cloud cover)' - 'night/rain_showers_hi':'12', #'Rain showers (low cloud cover)' - 'night/tsra': '37', #'Thunderstorm (high cloud cover)' - 'night/tsra_sct': '38', #'Thunderstorm (medium cloud cover)' - 'night/tsra_hi': '39', #'Thunderstorm (low cloud cover)' - 'night/tornado': '0', #'Tornado' - 'night/hurricane': '2', #'Hurricane conditions' - 'night/tropical_storm': '1', #'Tropical storm conditions' - 'night/dust': '19', #'Dust' - 'night/smoke': '22', #'Smoke' - 'night/haze': '21', #'Haze' - 'night/hot': '36', #'Hot' - 'night/cold': '25', #'Cold' - 'night/blizzard': '15', #'Blizzard' - 'night/fog': '20', #'Fog/mist' - - # special hanlding of forecast.weather.com url patterns that use "nxyz" pattersn for night - 'day/nskc': '31', #'Fair/clear' - 'day/nfew': '29', #'A few clouds' - 'day/nsct': '29', #'Partly cloudy' - 'day/nbkn': '27', #'Mostly cloudy' - 'day/novc': '26', #'Overcast' - 'day/nwind_skc': '24', #'Fair/clear and windy' - 'day/nwind_few': '29', #'A few clouds and windy' - 'day/nwind_sct': '29', #'Partly cloudy and windy' - 'day/nwind_bkn': '27', #'Mostly cloudy and windy' - 'day/nwind_ovc': '26', #'Overcast and windy' - 'day/nsn': '16', #'Snow' - 'day/nrasn': '5', #'Rain/snow' - 'day/nrasl': '6', #'Rain/sleet' - 'day/nsnsl': '7', #'Rain/sleet' - 'day/nfzra': '10', #'Freezing rain' - 'day/nrafzra': '10', #'Rain/freezing rain' - 'day/nsnfzra': '10', #'Freezing rain/snow' - 'day/nsl': '18', #'Sleet' - 'day/nra': '40', #'Rain' - 'day/nshra': '11', #'Rain showers (high cloud cover)' - 'day/hi_nshra':'12', #'Rain showers (low cloud cover)' - 'day/ntsra': '37', #'Thunderstorm (high cloud cover)' - 'day/ntsra_sct': '38', #'Thunderstorm (medium cloud cover)' - 'day/ntsra_hi': '39', #'Thunderstorm (low cloud cover)' - 'day/ntornado': '0', #'Tornado' - 'day/nhurricane': '2', #'Hurricane conditions' - 'day/ntropical_storm': '1', #'Tropical storm conditions' - 'day/ndust': '19', #'Dust' - 'day/nsmoke': '22', #'Smoke' - 'day/nhaze': '21', #'Haze' - 'day/nhot': '36', #'Hot' - 'day/ncold': '25', #'Cold' - 'day/nblizzard': '15', #'Blizzard' - 'day/nfog': '20', #'Fog/mist' - - - - - - '': 'na' - } - -MONTH_NAME_LONG = { '01' : 21, - '02' : 22, - '03' : 23, - '04' : 24, - '05' : 25, - '06' : 26, - '07' : 27, - '08' : 28, - '09' : 29, - '10' : 30, - '11' : 31, - '12' : 32 - } - -MONTH_NAME_SHORT = { '01' : 51, - '02' : 52, - '03' : 53, - '04' : 54, - '05' : 55, - '06' : 56, - '07' : 57, - '08' : 58, - '09' : 59, - '10' : 60, - '11' : 61, - '12' : 62 - } - -WEEK_DAY_LONG = { '0' : 17, - '1' : 11, - '2' : 12, - '3' : 13, - '4' : 14, - '5' : 15, - '6' : 16 - } - -WEEK_DAY_SHORT = { '0' : 47, - '1' : 41, - '2' : 42, - '3' : 43, - '4' : 44, - '5' : 45, - '6' : 46 - } - -FORECAST = { 'thunderstorm with light rain': LANGUAGE(32201), - 'thunderstorm with rain': LANGUAGE(32202), - 'thunderstorm with heavy rain': LANGUAGE(32203), - 'light thunderstorm': LANGUAGE(32204), - 'thunderstorm': LANGUAGE(32205), - 'heavy thunderstorm': LANGUAGE(32206), - 'ragged thunderstorm': LANGUAGE(32207), - 'thunderstorm with light drizzle': LANGUAGE(32208), - 'thunderstorm with drizzle': LANGUAGE(32209), - 'thunderstorm with heavy drizzle': LANGUAGE(32210), - 'light intensity drizzle': LANGUAGE(32211), - 'drizzle': LANGUAGE(32212), - 'heavy intensity drizzle': LANGUAGE(32213), - 'light intensity drizzle rain': LANGUAGE(32214), - 'drizzle rain': LANGUAGE(32215), - 'heavy intensity drizzle rain': LANGUAGE(32216), - 'shower rain And drizzle': LANGUAGE(32217), - 'heavy shower rain and drizzle': LANGUAGE(32218), - 'shower drizzle': LANGUAGE(32219), - 'light rain': LANGUAGE(32220), - 'moderate rain': LANGUAGE(32221), - 'heavy intensity rain': LANGUAGE(32222), - 'very heavy rain': LANGUAGE(32223), - 'extreme rain': LANGUAGE(32224), - 'freezing rain': LANGUAGE(32225), - 'light intensity shower rain': LANGUAGE(32226), - 'shower rain': LANGUAGE(32227), - 'heavy intensity shower rain': LANGUAGE(32228), - 'ragged shower rain': LANGUAGE(32229), - 'light snow': LANGUAGE(32230), - 'snow': LANGUAGE(32231), - 'heavy snow': LANGUAGE(32232), - 'sleet': LANGUAGE(32233), - 'shower sleet': LANGUAGE(32234), - 'light rain and snow': LANGUAGE(32235), - 'rain and snow': LANGUAGE(32236), - 'light shower snow': LANGUAGE(32237), - 'shower snow': LANGUAGE(32238), - 'heavy shower snow': LANGUAGE(32239), - 'mist': LANGUAGE(32240), - 'smoke': LANGUAGE(32241), - 'haze': LANGUAGE(32242), - 'sand, dust whirls': LANGUAGE(32243), - 'fog': LANGUAGE(32244), - 'sand': LANGUAGE(32245), - 'dust': LANGUAGE(32246), - 'volcanic ash': LANGUAGE(32247), - 'squalls': LANGUAGE(32248), - 'tornado': LANGUAGE(32249), - 'clear sky': LANGUAGE(32250), - 'few clouds': LANGUAGE(32251), - 'scattered clouds': LANGUAGE(32252), - 'broken clouds': LANGUAGE(32253), - 'overcast clouds': LANGUAGE(32254), - 'tornado': LANGUAGE(32255), - 'tropical storm': LANGUAGE(32256), - 'hurricane': LANGUAGE(32257), - 'cold': LANGUAGE(32258), - 'hot': LANGUAGE(32259), - 'windy': LANGUAGE(32260), - 'hail': LANGUAGE(32261), - 'calm': LANGUAGE(32262), - 'light breeze': LANGUAGE(32263), - 'gentle breeze': LANGUAGE(32264), - 'moderate breeze': LANGUAGE(32265), - 'fresh breeze': LANGUAGE(32266), - 'strong breeze': LANGUAGE(32267), - 'high wind, near gale': LANGUAGE(32268), - 'gale': LANGUAGE(32269), - 'severe gale': LANGUAGE(32270), - 'storm': LANGUAGE(32271), - 'violent storm': LANGUAGE(32272), - 'hurricane': LANGUAGE(32273), - 'clear': LANGUAGE(32274), - 'clouds': LANGUAGE(32275), - 'rain': LANGUAGE(32276) - } - -#def SPEED(mps): -# try: -# val = float(mps) -# except: -# return '' -# -# if SPEEDUNIT == 'km/h': -# speed = mps * 3.6 -# elif SPEEDUNIT == 'm/min': -# speed = mps * 60 -# elif SPEEDUNIT == 'ft/h': -# speed = mps * 11810.88 -# elif SPEEDUNIT == 'ft/min': -# speed = mps * 196.84 -# elif SPEEDUNIT == 'ft/s': -# speed = mps * 3.281 -# elif SPEEDUNIT == 'mph': -# speed = mps * 2.237 -# elif SPEEDUNIT == 'knots': -# speed = mps * 1.944 -# elif SPEEDUNIT == 'Beaufort': -# speed = KPHTOBFT(mps* 3.6) -# elif SPEEDUNIT == 'inch/s': -# speed = mps * 39.37 -# elif SPEEDUNIT == 'yard/s': -# speed = mps * 1.094 -# elif SPEEDUNIT == 'Furlong/Fortnight': -# speed = mps * 6012.886 -# else: -# speed = mps -# return str(int(round(speed))) - - - -def FtoC(Fahrenheit): - try: - Celsius = (float(Fahrenheit) - 32.0) * 5.0/9.0 - return Celsius - except: - return - -def CtoF(Celsius): - try: - Fahrenheit = (float(Celsius) * 9.0/5.0) + 32.0 - return Fahrenheit - except: - return - - - -#def TEMP(deg): -# if TEMPUNIT == u'\N{DEGREE SIGN}'+'F': -# temp = deg * 1.8 + 32 -# elif TEMPUNIT == u'K': -# temp = deg + 273.15 -# elif TEMPUNIT == u'°Ré': -# temp = deg * 0.8 -# elif TEMPUNIT == u'°Ra': -# temp = deg * 1.8 + 491.67 -# elif TEMPUNIT == u'°Rø': -# temp = deg * 0.525 + 7.5 -# elif TEMPUNIT == u'°D': -# temp = deg / -0.667 + 150 -# elif TEMPUNIT == u'°N': -# temp = deg * 0.33 -# else: -# temp = deg -# return str(int(round(temp))) - -def WIND_DIR(deg): - if deg >= 349 or deg <= 11: - return 71 - elif deg >= 12 and deg <= 33: - return 72 - elif deg >= 34 and deg <= 56: - return 73 - elif deg >= 57 and deg <= 78: - return 74 - elif deg >= 79 and deg <= 101: - return 75 - elif deg >= 102 and deg <= 123: - return 76 - elif deg >= 124 and deg <= 146: - return 77 - elif deg >= 147 and deg <= 168: - return 78 - elif deg >= 169 and deg <= 191: - return 79 - elif deg >= 192 and deg <= 213: - return 80 - elif deg >= 214 and deg <= 236: - return 81 - elif deg >= 237 and deg <= 258: - return 82 - elif deg >= 259 and deg <= 281: - return 83 - elif deg >= 282 and deg <= 303: - return 84 - elif deg >= 304 and deg <= 326: - return 85 - elif deg >= 327 and deg <= 348: - return 86 - -#def KPHTOBFT(spd): -# if (spd < 1.0): -# bft = '0' -# elif (spd >= 1.0) and (spd < 5.6): -# bft = '1' -# elif (spd >= 5.6) and (spd < 12.0): -# bft = '2' -# elif (spd >= 12.0) and (spd < 20.0): -# bft = '3' -# elif (spd >= 20.0) and (spd < 29.0): -# bft = '4' -# elif (spd >= 29.0) and (spd < 39.0): -# bft = '5' -# elif (spd >= 39.0) and (spd < 50.0): -# bft = '6' -# elif (spd >= 50.0) and (spd < 62.0): -# bft = '7' -# elif (spd >= 62.0) and (spd < 75.0): -# bft = '8' -# elif (spd >= 75.0) and (spd < 89.0): -# bft = '9' -# elif (spd >= 89.0) and (spd < 103.0): -# bft = '10' -# elif (spd >= 103.0) and (spd < 118.0): -# bft = '11' -# elif (spd >= 118.0): -# bft = '12' -# else: -# bft = '' -# return bft - - - -def FEELS_LIKE_C_KPH(Ts, Vs=0, Hs=0): - if not Vs: - Vs=0 - T=float(Ts) - V=float(Vs) - H=float(Hs) - # first check if we have a wind-chill value - windchill = WIND_CHILL_C_KPH(T, V) - if windchill and windchill < T : - return windchill - else: # otherwise, check for heat index - heatindex = HEAT_INDEX_C(T, H) - if heatindex and heatindex > T and heatindex > 80 : - return heatindex - # otherwise, neither windchill nor heatindex apply - return '' - - -def FEELS_LIKE_F_MPH(Ts, Vs=0, Hs=0): -# xbmc.log('Running FEELS_LIKE_F_MPH: %s %s %s' % (Ts, Vs, Hs),level=xbmc.LOGERROR) - - if not Vs: - Vs=0 - T=float(Ts) - V=float(Vs) - H=float(Hs) - ###xbmc.log'Running FEELS_LIKE_F_MPH: %s %s %s' % (T, V, H),level=xbmc.LOGERROR) - # first check if we have a wind-chill value - windchill = WIND_CHILL_F_MPH(T, V) - ###xbmc.log'windchill returns: %s' % (windchill),level=xbmc.LOGERROR) - - if windchill and windchill < T: - return windchill - else: # otherwise, check for heat index - heatindex = HEAT_INDEX_F(T, H) - ##xbmc.log'heatindex returns: %s' % (heatindex),level=xbmc.LOGERROR) - - if heatindex and heatindex > T+2 and heatindex > 80 : - return heatindex - # otherwise, neither windchill nor heatindex apply - return '' - - -def WIND_CHILL_F_MPH(Ts, Vs): - T=float(Ts) - V=float(Vs) - ###xbmc.log'wind_chill_f_mph %sF %s mph' % (T, V),level=xbmc.LOGERROR) - if T <= 50.0 and V >= 3.0: - ###xbmc.log'We are in windchill range %s' % (T),level=xbmc.LOGERROR) - WC=35.74 + 0.6215*T - 35.75*(V^0.16) + 0.4275*T*(V^0.16) - if WC < T-2.0: - ###xbmc.log'WindChill for %sF %s mph = %sF' % (T, V, WC),level=xbmc.LOGERROR) - return WC - # otherwise, windchill is not relevant, so return - return - - -def WIND_CHILL_C_KPH(Ts, Vs): - T=float(Ts) - V=float(Vs) - - TF = CtoF(T) - Vmph = V/1.609344 - windchill=WIND_CHILL_F_MPH(TF,Vmph) - if windchill: - return FtoC(windchill) - # otherwise, no windchill so return - return - - -# https://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml -def HEAT_INDEX_F(Ts, Rs): - T=float(Ts) - R=float(Rs) - - ##xbmc.log'Heat_index_F %sF %s' % (T, R),level=xbmc.LOGERROR) - - if T <40: # too cold for heat indexes - return - - # Try simple formula first - HI = 0.5 * (T + 61.0 + ((T-68.0)*1.2) + (R*0.094)) - - ##xbmc.log'Initial HI is %sF' % (HI),level=xbmc.LOGERROR) - - # Test if simply formula is applicable - - if HI > 80: # then we need to use the full fancy formula - ##xbmc.log'HI is over 80 %sF' % (HI),level=xbmc.LOGERROR) - HI = ( -42.379 - + 2.04901523*T - + 10.14333127*R - - .22475541*T*R - - .00683783*T*T - - .05481717*R*R - + .00122874*T*T*R - + .00085282*T*R*R - - .00000199*T*T*R*R - ) - ##xbmc.log('Fancy hi 1 is %sF' % (HI),level=xbmc.LOGERROR) - - if R < 12 and T >80 and T <115: - ADJUSTMENT = ( (13.0-R)/4.0 ) * math.sqrt( ( 17.0-math.fabs( T-95.0) ) / 17.0 ) - HI = HI - ADJUSTMENT - ##xbmc.log('adjusted hi 2 is %sF' % (HI),level=xbmc.LOGERROR) - - if R > 85 and T >80 and T <87: - ADJUSTMENT = ( (R-85.0)/10.0 ) * ( (87.0-T)/5.0 ) - HI = HI + ADJUSTMENT - ##xbmc.log('adjusted hi 3 is %sF' % (HI),level=xbmc.LOGERROR) - - ##xbmc.log'Final HI is %sF' % (HI),level=xbmc.LOGERROR) - - if HI > 80 and HI > (T+2): # if we have a heat-index, over 80 (and it's highter then the normal temp) then return it - ##xbmc.log'Heat Index for %sF %sH = %sF' % (T, R, HI),level=xbmc.LOGERROR) - return HI - - -def HEAT_INDEX_C(Ts, Rs): - T=float(Ts) - R=float(Rs) - TF = CtoF(T) # calaculation is done in F - HI = HEAT_INDEX_F(TF, R) - if HI: - return FtoC(HI) - # otherwise, no relevennt heat index so return - - -#### thanks to FrostBox @ http://forum.kodi.tv/showthread.php?tid=114637&pid=937168#pid937168 -def DEW_POINT(Tc=0, R=93.0, ext=True, minR=( 0, 0.075 )[ 0 ]): - Es = 6.11 * 10.0**( 7.5 * Tc / ( 237.7 + Tc ) ) - R = R or minR - E = ( R * Es ) / 100 - try: - DewPoint = ( -430.22 + 237.7 * math.log( E ) ) / ( -math.log( E ) + 19.08 ) - except ValueError: - DewPoint = 0 - if ext: - return TEMP( DewPoint ) - else: - return str(int(round(DewPoint))) - - -## a couple functions from itertools -def repeat_x(object_x, times=None): - # repeat(10, 3) --> 10 10 10 - if times is None: - while True: - yield object_x - else: - for i in range(times): - yield object_x - -def zip_x(fill, *args): - # zip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D- - iterators = [iter(it) for it in args] - num_active = len(iterators) - if not num_active: - return - while True: - values = [] - for i, it in enumerate(iterators): - try: - value = next(it) - except StopIteration: - num_active -= 1 - if not num_active: - return - iterators[i] = repeat_x(fill) - value = fill - values.append(value) - yield tuple(values) - -def get_timestamp(datestr): - #"2019-04-29T16:00:00-04:00" - #iso_fmt = '%Y-%m-%dT%H:%M:%S%z' - datestamp=parse(datestr) - return time.mktime(datestamp.timetuple()) - - -def convert_date(stamp): - if str(stamp).startswith('-'): - return '' - date_time = time.localtime(stamp) - if DATEFORMAT[1] == 'd' or DATEFORMAT[0] == 'D': - localdate = time.strftime('%d-%m-%Y', date_time) - elif DATEFORMAT[1] == 'm' or DATEFORMAT[0] == 'M': - localdate = time.strftime('%m-%d-%Y', date_time) - else: - localdate = time.strftime('%Y-%m-%d', date_time) - - if TIMEFORMAT != '/': - localtime = time.strftime('%I:%M%p', date_time) - else: - localtime = time.strftime('%H:%M', date_time) - return localtime + ' ' + localdate - -def get_time(stamp): - date_time = time.localtime(stamp) - if TIMEFORMAT != '/': - localtime = time.strftime('%I:%M%p', date_time) - else: - localtime = time.strftime('%H:%M', date_time) - return localtime - -def get_weekday(stamp, form): - date_time = time.localtime(stamp) - weekday = time.strftime('%w', date_time) - if form == 's': - return xbmc.getLocalizedString(WEEK_DAY_SHORT[weekday]) - elif form == 'l': - return xbmc.getLocalizedString(WEEK_DAY_LONG[weekday]) - else: - return int(weekday) - -def get_month(stamp, form): - date_time = time.localtime(stamp) - month = time.strftime('%m', date_time) - day = time.strftime('%d', date_time) - if form == 'ds': - label = day + ' ' + xbmc.getLocalizedString(MONTH_NAME_SHORT[month]) - elif form == 'dl': - label = day + ' ' + xbmc.getLocalizedString(MONTH_NAME_LONG[month]) - elif form == 'ms': - label = xbmc.getLocalizedString(MONTH_NAME_SHORT[month]) + ' ' + day - elif form == 'ml': - label = xbmc.getLocalizedString(MONTH_NAME_LONG[month]) + ' ' + day - return label - -# Satellite Imagery paths - -MAPSECTORS = { - "conus-e": {"name":LANGUAGE(32360),"path":"GOES16/ABI/CONUS/%s/1250x750.jpg"}, - "conus-w": {"name":LANGUAGE(32361),"path":"GOES17/ABI/CONUS/%s/1250x750.jpg"}, - "glm-e": {"name":LANGUAGE(32362),"path":"GOES16/GLM/CONUS/EXTENT/1250x750.jpg"}, - "glm-w": {"name":LANGUAGE(32363),"path":"GOES17/GLM/CONUS/EXTENT/1250x750.jpg"}, - "ak": {"name":LANGUAGE(32364),"path":"GOES17/ABI/SECTOR/ak/%s/1000x1000.jpg"}, - "cak": {"name":LANGUAGE(32365),"path":"GOES17/ABI/SECTOR/cak/%s/1200x1200.jpg"}, - "sea": {"name":LANGUAGE(32366),"path":"GOES17/ABI/SECTOR/sea/%s/1200x1200.jpg"}, - "np": {"name":LANGUAGE(32367),"path":"GOES17/ABI/SECTOR/np/%s/900x540.jpg"}, - "wus": {"name":LANGUAGE(32368),"path":"GOES17/ABI/SECTOR/wus/%s/1000x1000.jpg"}, - "pnw-w": {"name":LANGUAGE(32369),"path":"GOES17/ABI/SECTOR/pnw/%s/1200x1200.jpg"}, - "pnw-e": {"name":LANGUAGE(32370),"path":"GOES16/ABI/SECTOR/pnw/%s/1200x1200.jpg"}, - "psw-w": {"name":LANGUAGE(32371),"path":"GOES17/ABI/SECTOR/psw/%s/1200x1200.jpg"}, - "psw-e": {"name":LANGUAGE(32371),"path":"GOES16/ABI/SECTOR/psw/%s/1200x1200.jpg"}, - "nr": {"name":LANGUAGE(32373),"path":"GOES16/ABI/SECTOR/nr/%s/1200x1200.jpg"}, - "sr": {"name":LANGUAGE(32374),"path":"GOES16/ABI/SECTOR/sr/%s/1200x1200.jpg"}, - "sp": {"name":LANGUAGE(32375),"path":"GOES16/ABI/SECTOR/sp/%s/1200x1200.jpg"}, - "umv": {"name":LANGUAGE(32376),"path":"GOES16/ABI/SECTOR/umv/%s/1200x1200.jpg"}, - "smv": {"name":LANGUAGE(32377),"path":"GOES16/ABI/SECTOR/smv/%s/1200x1200.jpg"}, - "can": {"name":LANGUAGE(32378),"path":"GOES16/ABI/SECTOR/can/%s/1125x560.jpg"}, - "cgl": {"name":LANGUAGE(32379),"path":"GOES16/ABI/SECTOR/cgl/%s/1200x1200.jpg"}, - "eus": {"name":LANGUAGE(32380),"path":"GOES16/ABI/SECTOR/eus/%s/1000x1000.jpg"}, - "ne": {"name":LANGUAGE(32381),"path":"GOES16/ABI/SECTOR/ne/%s/1200x1200.jpg"}, - "na": {"name":LANGUAGE(32382),"path":"GOES16/ABI/SECTOR/na/%s/900x540.jpg"}, - "se": {"name":LANGUAGE(32383),"path":"GOES16/ABI/SECTOR/se/%s/1200x1200.jpg"}, - "car": {"name":LANGUAGE(32384),"path":"GOES16/ABI/SECTOR/car/%s/1000x1000.jpg"}, - "pr": {"name":LANGUAGE(32385),"path":"GOES16/ABI/SECTOR/pr/%s/1200x1200.jpg"}, - "gm": {"name":LANGUAGE(32386),"path":"GOES16/ABI/SECTOR/gm/%s/1000x1000.jpg"}, - "taw": {"name":LANGUAGE(32387),"path":"GOES16/ABI/SECTOR/taw/%s/900x540.jpg"}, - "mex": {"name":LANGUAGE(32388),"path":"GOES16/ABI/SECTOR/mex/%s/1000x1000.jpg"}, - "hi": {"name":LANGUAGE(32389),"path":"GOES17/ABI/SECTOR/hi/%s/1200x1200.jpg"}, - "tpw": {"name":LANGUAGE(32390),"path":"GOES17/ABI/SECTOR/tpw/%s/900x540.jpg"}, - "tsp": {"name":LANGUAGE(32391),"path":"GOES17/ABI/SECTOR/tsp/%s/900x540.jpg"}, - "eep": {"name":LANGUAGE(32392),"path":"GOES16/ABI/SECTOR/eep/%s/900x540.jpg"}, - "cam": {"name":LANGUAGE(32393),"path":"GOES16/ABI/SECTOR/cam/%s/1000x1000.jpg"}, - "nsa": {"name":LANGUAGE(32394),"path":"GOES16/ABI/SECTOR/nsa/%s/900x540.jpg"}, - "ssa": {"name":LANGUAGE(32395),"path":"GOES16/ABI/SECTOR/ssa/%s/900x540.jpg"} - } - -MAPTYPES = { - "LOOP": LANGUAGE(32333), - "GEOCOLOR": LANGUAGE(32400), - "EXTENT": LANGUAGE(32401), - "Sandwich": LANGUAGE(32402), - "AirMass": LANGUAGE(32404), - "DayCloudPhase":LANGUAGE(32405), - "NightMicrophysics":LANGUAGE(32406), - "DMW": LANGUAGE(32407), - "Dust": LANGUAGE(32408), - "01": LANGUAGE(32409), - "02": LANGUAGE(32410), - "03": LANGUAGE(32411), - "04": LANGUAGE(32412), - "05": LANGUAGE(32413), - "06": LANGUAGE(32414), - "07": LANGUAGE(32415), - "08": LANGUAGE(32416), - "09": LANGUAGE(32417), - "10": LANGUAGE(32418), - "11": LANGUAGE(32419), - "12": LANGUAGE(32420), - "13": LANGUAGE(32421), - "14": LANGUAGE(32422), - "15": LANGUAGE(32423), - "16": LANGUAGE(32424) - } - -LOOPSECTORS = { - ##ridge/standard/CONUS_loop.gif - "us": {"name":LANGUAGE(32396),"path":"ridge/standard/CONUS-LARGE_loop.gif"}, - "pnw": {"name":LANGUAGE(32369),"path":"ridge/standard/PACNORTHWEST_loop.gif"}, - "psw": {"name":LANGUAGE(32371),"path":"ridge/standard/PACSOUTHWEST_loop.gif"}, - "nr": {"name":LANGUAGE(32373),"path":"ridge/standard/NORTHROCKIES_loop.gif"}, - "sr": {"name":LANGUAGE(32374),"path":"ridge/standard/SOUTHROCKIES_loop.gif"}, - "sp": {"name":LANGUAGE(32375),"path":"ridge/standard/SOUTHPLAINS_loop.gif"}, - "umv": {"name":LANGUAGE(32376),"path":"ridge/standard/UPPERMISSVLY_loop.gif"}, - "smv": {"name":LANGUAGE(32377),"path":"ridge/standard/SOUTHMISSVLY_loop.gif"}, - "cgl": {"name":LANGUAGE(32379),"path":"ridge/standard/CENTGRLAKES_loop.gif"}, - "ne": {"name":LANGUAGE(32381),"path":"ridge/standard/NORTHEAST_loop.gif"}, - "se": {"name":LANGUAGE(32383),"path":"ridge/standard/SOUTHEAST_loop.gif"}, - "car": {"name":LANGUAGE(32384),"path":"ridge/standard/CARIB_loop.gif"}, - "ak": {"name":LANGUAGE(32364),"path":"ridge/standard/ALASKA_loop.gif"}, - "hi": {"name":LANGUAGE(32389),"path":"ridge/standard/HAWAII_loop.gif"}, - "guam": {"name":LANGUAGE(32397),"path":"ridge/standard/GUAM_loop.gif"} - } -