Skip to content
Snippets Groups Projects
Verified Commit 1cfe5460 authored by David Hoese's avatar David Hoese
Browse files

Move data's calc and data_time functions to metobscommon.util

parent b155fb53
No related branches found
No related tags found
No related merge requests found
import numpy as np
def dewpoint(tempC, relhum):
"""
Algorithm from Tom Whittaker tempC is the temperature in degrees Celsius,
relhum is the relative humidity as a percentage.
:param tempC: temperature in celsius
:param relhum: relative humidity as a percentage
"""
if tempC is None or relhum is None:
return np.nan
gasconst = 461.5
latheat = 2500800.0
dp = 1.0 / (1.0 / (273.15 + tempC) - gasconst * np.log((0.0 + relhum) / 100) /
(latheat - tempC * 2397.5))
return min(dp - 273.15, tempC)
def relhum(airTempK, dewpointTempK):
"""
Algorithm derived by David Hoese from the above
dewpoint(tempC, relhum) function, both parameters are in Kelvin units.
:param airTempK: air temperature in Kelvin
:param dewpointTempK: dewpoint temp in Kelvin
"""
if airTempK is None or dewpointTempK is None:
return np.nan
gas_constant = 461.5
latheat = 2500800.0
# Only one section of the equation
latpart = (latheat - (airTempK - 273.15) * 2397.5)
relativehum = 100 * np.e ** ((latpart / airTempK - latpart / dewpointTempK) / gas_constant)
return relativehum
def potentialtemp(airTempK, pressureMB):
"""
Algorithm from David Hoese to calculate potential temperature.
:param airTempK: air temperature in Kelvin
:param pressureMB: air pressure in millibars
"""
if airTempK == None or pressureMB == None:
return np.nan
pT = airTempK * (pressureMB.max() / pressureMB) ** .286
return pT
def altimeter(p, alt):
"""Compute altimeter from pressure and altitude.
Converted from code provided by TomW.
:param p: pressure in hPa.
:param alt: altitude of the measurement in meters.
:returns: altimeter in inHg
"""
n = .190284
c1 = .0065 * pow(1013.25, n) / 288.
c2 = alt / pow((p - .3), n)
ff = pow(1. + c1 * c2, 1. / n)
return ((p - .3) * ff * 29.92 / 1013.25)
def dir2txt(val):
"""Convert degrees [0, 360) to a textual representation.
:param val: decimal degrees
>>> dir2txt(0)
'N'
>>> dir2txt(90)
'E'
>>> dir2txt(180)
'S'
>>> dir2txt(270)
'W'
>>> dir2txt(359)
'N'
"""
assert val >= 0 and val < 360, "'%s' out of range" % val
dirs = ("NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW")
if ((val >= 348.75 and val <= 360) or val >= 0 and val < 11.25): return "N"
# 1/2 degree increment between the directions
i = 11.25;
for dir in dirs:
if val >= i and val < (i + 22.5):
return dir
i += 22.5
def wind_vector_components(windspd, winddir):
"""Decompose scalar or list/array polar wind direction and speed data
into the horizontal and vertical vector components and speed vector.
Inputs can be scalar or arrays.
"""
dir_rad = np.deg2rad(winddir)
spd_arr = np.array(windspd)
V_e = spd_arr * np.sin(dir_rad)
V_n = spd_arr * np.cos(dir_rad)
U_spd = np.sqrt(pow(V_e, 2) + pow(V_n, 2))
return V_e, V_n, U_spd
def wind_vector_degrees(vector_east, vector_north):
"""Re-compose horizontal (east/west) and vertical (north/south) vector
components into wind direction in degrees.
Inputs can be scalar or arrays.
"""
rads = np.arctan2(vector_east, vector_north)
winddir = np.rad2deg(rads)
if isinstance(winddir, np.ndarray):
winddir[np.less(winddir, 0)] += 360
elif winddir < 0:
winddir += 360
return winddir % 360
def mean_wind_vector(windspd, winddir):
V_e, V_n, V_spd = wind_vector_components(windspd, winddir)
avg_dir = wind_vector_degrees(np.mean(V_e), np.mean(V_n))
return avg_dir, np.mean(V_spd)
print("metobscommon.data.calc deprecated. Use 'metobscommon.util.calc' instead.")
from metobscommon.util.calc import *
from datetime import timedelta, datetime
from calendar import timegm
def trunc_datetime(dt, interval):
"""Truncate a datetime to the nearest time on interval with basetime
of epoch.
>>> v = trunc_datetime(datetime(2014, 5, 20, 0, 0, 4), timedelta(seconds=5)
>>> assert v == datetime(2014, 5, 20, 0, 0, 0)
>>> v = trunc_datetime(datetime(2014, 5, 20, 0, 0, 1), 5)
>>> assert v == datetime(2014, 5, 20, 0, 0, 0)
"""
if not isinstance(interval, timedelta):
interval = timedelta(seconds=interval)
utime = to_unix_timestamp(dt)
return datetime.utcfromtimestamp(utime - utime % interval.total_seconds())
def to_unix_timestamp(dtval):
"""Convert a datetime to a unix timestamp.
"""
return timegm(dtval.utctimetuple())
def hhmm_to_offset(hhmm):
"""Convert a string time, possibly with missing hours and minutes, to an
offset of seconds.
"""
hhmm = '{:04d}'.format(int(hhmm))
return timedelta(hours=int(hhmm[0:2]),
minutes=int(hhmm[2:])).total_seconds()
from datetime import datetime, timedelta
from .units import *
from .data_time import to_unix_timestamp
from metobscommon.util.mytime import to_epoch
import numpy as np
from numpy.ma import (masked_array, average as masked_average, MaskedArray,
......@@ -112,7 +112,7 @@ def average_for_interval(basetime, arr, interval):
# timestamp for center of averaging interval
int_dt = stop_dt - timedelta(seconds=(interval / 2))
avg_out[0] = to_unix_timestamp(int_dt)
avg_out[0] = to_epoch(int_dt)
avg_out[1:] = avg_func(avg_input, axis=0)
arr_out.append(avg_out)
......
......@@ -19,7 +19,7 @@ import importlib
import math
from itertools import zip_longest
from datetime import datetime
from metobscommon.data.calc import wind_vector_components
from metobscommon.util.calc import wind_vector_components
LOG = logging.getLogger(__name__)
DB_NAME = 'metobs'
......
......@@ -7,8 +7,8 @@ import rrdtool
import numpy as np
from zope.interface import implementer
from metobscommon.data.calc import wind_vector_degrees
from metobscommon.data.data_time import to_unix_timestamp
from metobscommon.util.calc import wind_vector_degrees
from metobscommon.util.mytime import to_epoch
from metobscommon import interface
......@@ -92,7 +92,7 @@ class RrdModel(object):
raise ModelError("No valid data keys provided", data)
tmpl = ':'.join(validkeys)
values = ':'.join([str(data[k]) for k in validkeys])
values = '{:d}@{}'.format(to_unix_timestamp(stamp), values)
values = '{:d}@{}'.format(to_epoch(stamp), values)
return tmpl, values
def add_record(self, stamp, record):
......@@ -100,7 +100,7 @@ class RrdModel(object):
object with keys for each dataset. Additional keys are ignored.
"""
# Normalize to data interval
utime = to_unix_timestamp(stamp)
utime = to_epoch(stamp)
data_interval = min(self.averaging_intervals())
stamp = datetime.utcfromtimestamp(utime - utime % data_interval)
......@@ -121,9 +121,9 @@ class RrdModel(object):
names = names or self.datasets[:]
if isinstance(start, datetime):
start = to_unix_timestamp(start)
start = to_epoch(start)
if isinstance(end, datetime):
end = to_unix_timestamp(end)
end = to_epoch(end)
# normalize request times to averaging interval
start -= start % average
......
from datetime import datetime
def test_to_unix_timestamp():
from metobscommon.data.data_time import to_unix_timestamp
assert to_unix_timestamp(datetime(1970, 1, 1)) == 0
def test_to_epoch():
from metobscommon.util.mytime import to_epoch
assert to_epoch(datetime(1970, 1, 1)) == 0
def test_hhmm_to_secs():
from metobscommon.data.data_time import hhmm_to_offset
from metobscommon.util.mytime import hhmm_to_offset
assert hhmm_to_offset('2400') == 86400, "Can't handle > 23:59"
assert hhmm_to_offset('2401') == 86460, "Can't handle > 23:59"
......
......@@ -4,7 +4,7 @@ import unittest
class MeanWindVectorTests(unittest.TestCase):
def _fut(self, winddir, windspd=None):
from metobscommon.data.calc import mean_wind_vector
from metobscommon.util.calc import mean_wind_vector
windspd = windspd or [1]*len(winddir)
return mean_wind_vector(windspd, winddir)[0]
......
......@@ -6,7 +6,6 @@ This is used by the `nc` module for generating NetCDF4 variables.
"""
import math
import numpy as np
try:
......@@ -17,9 +16,6 @@ except ImportError:
pd = None
Series = np.ndarray
NaN = float('nan')
is_nan = lambda a: a != a
def knots_to_mps(knots):
return knots * 0.51444
......@@ -34,7 +30,7 @@ def dewpoint(tempC, relhum):
:param relhum: relative humidity as a percentage
"""
if tempC is None or relhum is None:
return NaN
return np.nan
gasconst = 461.5
latheat = 2500800.0
......@@ -56,14 +52,14 @@ def relhum(airTempK, dewpointTempK):
:param dewpointTempK: dewpoint temp in Kelvin
"""
if airTempK == None or dewpointTempK == None:
return NaN
return np.nan
gas_constant = 461.5
latheat = 2500800.0
# Only one section of the equation
latpart = (latheat - (airTempK - 273.15) * 2397.5)
relativehum = 100 * math.e ** ((latpart / airTempK - latpart / dewpointTempK) / gas_constant)
relativehum = 100 * np.e ** ((latpart / airTempK - latpart / dewpointTempK) / gas_constant)
return relativehum
......@@ -76,7 +72,7 @@ def potentialtemp(airTempK, pressureMB):
:param pressureMB: air pressure in millibars
"""
if airTempK == None or pressureMB == None:
return NaN
return np.nan
pT = airTempK * (pressureMB.max() / pressureMB) ** .286
......
......@@ -5,14 +5,8 @@ All functions that return datetime objects will return with a timezone.
If a datetime input does not have a tzinfo implementation it is assumed
it represents UTC.
"""
__author__ = 'Bruce Flynn, SSEC'
__version__ = '$Revision: 1.10 $'
#$Source: /cvsroot/TOOLS/dev/metobs/python/mytime/metobs/mytime.py,v $
__docformat__ = 'restructuredtext en'
from datetime import datetime
from datetime import timedelta
from datetime import tzinfo
from datetime import datetime, timedelta, tzinfo
import time as _time
import re
from calendar import timegm
......@@ -266,10 +260,12 @@ def gen_days(start, end=None):
yield cur
cur = next_day(cur)
def to_epoch(dtval):
"""Get epoch time as float from a datetime.
"""
return _time.mktime(dtval.utctimetuple())
return timegm(dtval.utctimetuple())
def days_between(start, end):
"""Return a generator for days between [start, end).
......@@ -338,3 +334,30 @@ def parse_uuid1(uuid):
t = t - 0x01b21dd213814000 # convert from seconds since
t = t / 1e7 # nanoseconds to seconds
return seconds_to_datetime(t)
# Originally in metobs.data.time:
def trunc_datetime(dt, interval):
"""Truncate a datetime to the nearest time on interval with basetime
of epoch.
>>> v = trunc_datetime(datetime(2014, 5, 20, 0, 0, 4), timedelta(seconds=5)
>>> assert v == datetime(2014, 5, 20, 0, 0, 0)
>>> v = trunc_datetime(datetime(2014, 5, 20, 0, 0, 1), 5)
>>> assert v == datetime(2014, 5, 20, 0, 0, 0)
"""
if not isinstance(interval, timedelta):
interval = timedelta(seconds=interval)
utime = to_epoch(dt)
return datetime.utcfromtimestamp(utime - utime % interval.total_seconds())
def hhmm_to_offset(hhmm):
"""Convert a string time, possibly with missing hours and minutes, to an
offset of seconds.
"""
hhmm = '{:04d}'.format(int(hhmm))
return timedelta(hours=int(hhmm[0:2]),
minutes=int(hhmm[2:])).total_seconds()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment