Add support for calculating Evapotranspiration

This commit is contained in:
Samer Albahra
2015-04-23 14:38:14 -05:00
parent 2ce7450d21
commit b4fa0f3350

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python #!/usr/bin/python
import urllib, urllib2, cgi, re import urllib, urllib2, cgi, re, math
import json, datetime, time, sys, calendar import json, datetime, time, sys, calendar
import pytz, ephem import pytz, ephem
from datetime import datetime, timedelta, date from datetime import datetime, timedelta, date
@@ -33,6 +33,18 @@ def isFloat(s):
return 0 return 0
return 1 return 1
def F2C(temp):
return (temp-32)*5/9
def C2F(temp):
return temp*9/5 + 32
def mm2in(x):
return x*0.03937008
def ft2m(x):
return x*0.3048
def IP2Int(ip): def IP2Int(ip):
o = map(int, ip.split('.')) o = map(int, ip.split('.'))
res = (16777216 * o[0]) + (65536 * o[1]) + (256 * o[2]) + o[3] res = (16777216 * o[0]) + (65536 * o[1]) + (256 * o[2]) + o[3]
@@ -44,6 +56,78 @@ def getClientAddress(environ):
except KeyError: except KeyError:
return environ['REMOTE_ADDR'] return environ['REMOTE_ADDR']
def computeETs(latitude, longitude, elevation, temp_high, temp_low, temp_avg, hum_high, hum_low, hum_avg, wind, solar):
tm = time.gmtime()
dayofyear = tm.tm_yday
latitude = safe_float(latitude, 0);
longitude = safe_float(longitude, 0);
# Converted values
El = ft2m(elevation)
Rs = float(solar) * 0.0864 # W/m2 to MJ/d /m2
Tx = F2C(float(temp_high))
Tn = F2C(float(temp_low))
Tm = F2C(float(temp_avg))
RHx = float(hum_high)
RHn = float(hum_low)
RHm = float(hum_avg)
Td = Tm-(100-RHm)/5 # approx. dewpoint (daily mean)
U2 = float(wind) * 0.44704 # wind speed in m/s
# Step 1: Extraterrestrial radiation
Gsc = 0.082
sigma = 4.90e-9
phi = math.pi * latitude / 180
dr = 1 + 0.033*math.cos(2*math.pi*dayofyear/365)
delta = 0.409*math.sin(2*math.pi*dayofyear/365 - 1.39)
omegas = math.acos(-math.tan(phi)*math.tan(delta))
Ra = (24*60/math.pi)*Gsc*dr*(omegas*math.sin(delta)*math.sin(phi)+math.cos(phi)*math.cos(delta)*math.sin(omegas))
# Step 2: Daily net radiation
Rso = Ra*(0.75 + 2.0e-5 * El) # 5
Rns = (1-0.23)*Rs
f = 1.35*Rs/Rso - 0.35 # 7
esTx = 0.6108*math.exp(17.27*Tx/(Tx+237.3)) # 8
esTn = 0.6108*math.exp(17.27*Tn/(Tn+237.3))
ed = (esTx*RHn/100 + esTn*RHx/100)/2 # 10
ea = (esTx+esTn)/2 # 22
epsilonp = 0.34-0.14*math.sqrt(ea) # 12
Rnl = -f*epsilonp*sigma*((Tx+273.14)**4 + (Tn+273.15)**4)/2 # 13
Rn = Rns + Rnl
# Step 3: variables needed to compute ET
beta = 101.3*((293-0.0065*El)/293)**5.26 # 15
lam = 2.45
gamma = 0.00163*beta/lam
e0 = 0.6108*math.exp(17.27*Tm/(Tm+237.3)) # 19
Delta = 4099*e0/(Tm+237.3)**2 # 20
G = 0
ea = (esTx+esTn)/2
# Step 4: calculate ETh
ETh = 0.408*(0.0023*Ra*(Tm+17.8)*math.sqrt(Tx-Tn)) # 23
# Step 5: calculate ET0
R0 = 0.408*Delta*(Rn-G)/(Delta+gamma*(1+0.34*U2)) # 24
A0 = (900*gamma/(Tm+273))*U2*(ea-ed) / (Delta+gamma*(1+0.34*U2)) # 25
ET0 = R0 + A0
# Step 6: calculate ETr
Rr = 0.408*Delta*(Rn-G) / (Delta+gamma*(1+0.38*U2)) # 27
Ar = (1600*gamma/(Tm+273))*U2*(ea-ed) / (Delta+gamma*(1+0.38*U2)) # 28
ETr = Rr + Ar
return (mm2in(ETh), mm2in(ET0), mm2in(ETr))
def application(environ, start_response): def application(environ, start_response):
path = environ.get('PATH_INFO') path = environ.get('PATH_INFO')
uwt = re.match('/weather(\d+)\.py',path) uwt = re.match('/weather(\d+)\.py',path)
@@ -72,8 +156,8 @@ def application(environ, start_response):
else: else:
fwv = '' fwv = ''
restrict, maxh, minh, meant, pre, pre_today, h_today, sunrise, sunset, scale, toffset = [0, -1, -1, -500, -1, -1, -1, -1, -1, -1, -1] solar, wind, avehumidity, minhumidity, maxhumidity, maxt, mint, elevation, restrict, maxh, minh, meant, pre, pre_today, h_today, sunrise, sunset, scale, toffset = [0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -500, -1, -1, -1, -1, -1, -1, -1]
ET = [0,0,0]
eip = IP2Int(getClientAddress(environ)) eip = IP2Int(getClientAddress(environ))
# if loc is GPS coordinate itself # if loc is GPS coordinate itself
@@ -94,30 +178,55 @@ def application(environ, start_response):
# if loc is pws, query wunderground geolookup to get GPS coordinates # if loc is pws, query wunderground geolookup to get GPS coordinates
if loc.startswith('pws:') or loc.startswith('icao:'): if loc.startswith('pws:') or loc.startswith('icao:'):
try: try:
req = urllib2.urlopen('http://api.wunderground.com/api/'+key+'/geolookup/q/'+urllib.quote(loc)+'.json') req = urllib2.urlopen('http://api.wunderground.com/api/'+key+'/conditions/forecast/q/'+urllib.quote(loc)+'.json')
dat = json.load(req) dat = json.load(req)
if dat['location']: if 'current_observation' in dat:
v = dat['location']['lat'] v = dat['current_observation']['observation_location']['latitude']
if v and isFloat(v): if v and isFloat(v):
lat = v lat = v
v = dat['location']['lon'] v = dat['current_observation']['observation_location']['longitude']
if v and isFloat(v): if v and isFloat(v):
lon = v lon = v
v = dat['location']['tz_long'] v = dat['current_observation']['observation_location']['elevation']
if v:
elevation = safe_int(int(v.split()[0]), 0)
v = dat['current_observation']['solarradiation']
if v:
solar = safe_int(v, 0)
v = dat['current_observation']['local_tz_long']
if v: if v:
tzone = v tzone = v
else: else:
v = dat['location']['tz'] v = dat['current_observation']['local_tz_short']
if v: if v:
tzone = v tzone = v
forecast = dat['forecast']['simpleforecast']['forecastday'][0]
v = forecast['high']['fahrenheit']
if v:
maxt = safe_int(v, 0)
v = forecast['low']['fahrenheit']
if v:
mint = safe_int(v, 0)
v = forecast['avehumidity']
if v:
avehumidity = safe_int(v, 0)
v = forecast['maxhumidity']
if v:
maxhumidity = safe_int(v, 0)
v = forecast['minhumidity']
if v:
minhumidity = safe_int(v, 0)
v = forecast['avewind']['mph']
if v:
wind = safe_int(v, 0)
except: except:
lat = None lat = None
lon = None lon = None
tzone = None tzone = None
#loc = loc.replace(' ','_')
# now do autocomplete lookup to get GPS coordinates # now do autocomplete lookup to get GPS coordinates
if lat==None or lon==None: if lat==None or lon==None:
try: try:
@@ -195,6 +304,7 @@ def application(environ, start_response):
if v: if v:
h_today = safe_float(v, h_today) h_today = safe_float(v, h_today)
# Check which weather method is being used
if ((uwt & ~(1 << 7)) == 1): if ((uwt & ~(1 << 7)) == 1):
# calculate water time scale, per https://github.com/rszimm/sprinklers_pi/blob/master/Weather.cpp # calculate water time scale, per https://github.com/rszimm/sprinklers_pi/blob/master/Weather.cpp
hf = 0 hf = 0
@@ -217,6 +327,11 @@ def application(environ, start_response):
if (scale>200): if (scale>200):
scale = 200 scale = 200
elif ((uwt & ~(1 << 7)) == 2):
ET = computeETs(lat, lon, elevation, maxt, mint, meant, maxhumidity, minhumidity, avehumidity, wind, solar)
# TODO: Actually generate correct scale using ET (ET[1] is ET0 for short canopy)
scale = safe_int(ET[1] * 100, -1)
# Check weather modifier bits and apply scale modification # Check weather modifier bits and apply scale modification
if ((uwt>>7) & 1): if ((uwt>>7) & 1):
# California modification to prevent watering when rain has occured within 48 hours # California modification to prevent watering when rain has occured within 48 hours