diff --git a/aosstower/model.py b/aosstower/model.py index 610a71f0d44d5e2d0a21ec9a8f4a4d2c2a3a6787..57f4f35cce5089880014ff29d5a5d6ef1d388350 100644 --- a/aosstower/model.py +++ b/aosstower/model.py @@ -1,11 +1,11 @@ -from collections import OrderedDict +import os from datetime import datetime, timedelta import rrdtool import numpy as np from .time import to_unix_timestamp -from .wind import mean_wind_vector_degrees +from .wind import wind_vector_degrees def dewpoint(tempC, relhum): @@ -19,23 +19,39 @@ def dewpoint(tempC, relhum): 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))) + dp = (1.0 / (1.0 / (273.15 + tempC) - gasconst * np.log((0.0 + relhum) / 100) / (latheat - tempC * 2397.5))) return np.minimum(dp - 273.15, tempC) class RrdModel(object): - keys = ['air_temp', 'rh', 'dewpoint', + keys = ('air_temp', 'rh', 'dewpoint', 'wind_speed', 'winddir_north', 'winddir_east', 'pressure', 'precip', 'accum_precip', 'solar_flux', - 'altimeter'] + 'altimeter') def __init__(self, filepath): self._filepath = filepath + self._averages = tuple() + + @property + def averaging_intervals(self): + """Lazy load averaging intervals from database. + """ + if not self._averages: + averages = [] + info = rrdtool.info(self._filepath) + for key in info.keys(): + if key.startswith('rra') and key.endswith('pdp_per_row'): + averages.append(int(info[key]*info['step'])) + averages.sort() + self._averages = tuple(averages) + return self._averages def initialize(self, start=None): + assert not os.path.exists(self._filepath) + start = start or datetime.now() - timedelta(days=365) secs = to_unix_timestamp(start) rrdtool.create(self._filepath, @@ -63,8 +79,17 @@ class RrdModel(object): values = '{:d}:{}'.format(to_unix_timestamp(stamp), values) print values + def add_record(self, record): + pass + def get_slice(self, start, end, names=None, average=5): + """Get a slice of data from the database. + :param start: Start time as datetime + :param end: Inclusive end time as datetime + :param names: Names to query for, defaults to all available, see ``keys`` + :param average: Averaging interval supported by the database, see ``averaging_intervals``. + """ names = names or self.keys[:] if isinstance(start, datetime): @@ -100,7 +125,7 @@ class RrdModel(object): elif name == 'wind_dir': east = src_data[:,self.keys.index('winddir_east')].astype(np.float64) north = src_data[:,self.keys.index('winddir_north')].astype(np.float64) - dst_data[:,dst_idx] = mean_wind_vector_degrees(east, north) + dst_data[:,dst_idx] = wind_vector_degrees(east, north) times = np.array([np.arange(start, end + average, average)]) return np.concatenate((times.T, dst_data), axis=1) diff --git a/aosstower/record.py b/aosstower/record.py index 699ff7a0075681ebfe5aafa6f7f33a953008f52b..7fc84061a643e819d7aa51cdb94e06a59239caa7 100644 --- a/aosstower/record.py +++ b/aosstower/record.py @@ -3,7 +3,7 @@ from datetime import datetime, timedelta import numpy -from .time import hhmm_to_secs +from .time import hhmm_to_offset symbols = { 'TIME': {'type': numpy.int32}, @@ -47,15 +47,16 @@ def parse_v0_record(line): if len(parts) != 32: msg = "Expected 32 line parts, got {:d}".format(len(parts)) raise LineParseError(msg) - raw_data = {k:v for k, v in zip(parts[0::2], parts[1::2])} + raw_data = {k: v for k, v in zip(parts[0::2], parts[1::2])} time_str = raw_data['TIME'] try: unix_time = int(time_str) except ValueError as err: msg = "Could not parse unix time from {}".format(time_str) LineParseError.raise_wrapped(err, msg) - stamp = datetime.utcfromtimestamp(unix_time) - return stamp, raw_data + else: + stamp = datetime.utcfromtimestamp(unix_time) + return stamp, raw_data class RecordV1(dict): @@ -113,7 +114,7 @@ class RecordV1(dict): year = int(self['year']) doy = int(self['doy']) dt = datetime.strptime('{:d}.{:03d}'.format(int(year), int(doy)), '%Y.%j') - secs = hhmm_to_secs(self['hhmm']) + secs = hhmm_to_offset(self['hhmm']) secs += float(self['sec']) secs -= (secs % 5) dt += timedelta(seconds=secs) diff --git a/aosstower/tests/test_time.py b/aosstower/tests/test_time.py index 98ec2b80eab87c4ba16fc05ea76135ae595c7bcf..d679204509a9b06bc5319d200b1557ca2da0e862 100644 --- a/aosstower/tests/test_time.py +++ b/aosstower/tests/test_time.py @@ -7,9 +7,9 @@ def test_to_unix_timestamp(): def test_hhmm_to_secs(): - from aosstower.time import hhmm_to_secs + from aosstower.time import hhmm_to_offset - assert hhmm_to_secs('2400') == 86400, "Can't handle > 23:59" - assert hhmm_to_secs('2401') == 86460, "Can't handle > 23:59" - assert hhmm_to_secs('0') == 0, "Can't handle short times" - assert hhmm_to_secs('001') == 60, "Can't handle leading 0" + assert hhmm_to_offset('2400') == 86400, "Can't handle > 23:59" + assert hhmm_to_offset('2401') == 86460, "Can't handle > 23:59" + assert hhmm_to_offset('0') == 0, "Can't handle short times" + assert hhmm_to_offset('001') == 60, "Can't handle leading 0" diff --git a/aosstower/time.py b/aosstower/time.py index 9d26c1ef2377162579501ef28abf03b78fcf8279..3a2e0cfd47e731ece907e7c4e2a2af0918c6c0cb 100644 --- a/aosstower/time.py +++ b/aosstower/time.py @@ -7,8 +7,10 @@ def to_unix_timestamp(dt): return int(timegm(dt.utctimetuple())) -def hhmm_to_secs(hhmm): - val = int(hhmm) +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() diff --git a/scripts/rrd_fill.py b/scripts/rrd_fill.py index 8633e73b9850a089c81f03aa278c86b47d80fd9c..1be7898a540b8732e2ef1853d346eb3edb71c68f 100755 --- a/scripts/rrd_fill.py +++ b/scripts/rrd_fill.py @@ -33,7 +33,7 @@ if __name__ == '__main__': windspd = float(record['wind_speed']) winddir = float(record['wind_dir']) - u_e, u_n, spd = wind.mean_wind_vector_components(windspd, winddir) + u_e, u_n, spd = wind.wind_vector_components(windspd, winddir) record['winddir_east'] = u_e record['winddir_north'] = u_n record['wind_speed'] = spd