Compare commits

...

2 commits

Author SHA1 Message Date
XANTRONIX Industrial
fc81fa9b83 Implement ArchiveProduct class 2025-02-15 17:10:42 -05:00
XANTRONIX Industrial
cef61dcc97 Use datetime.__str__(), not isoformat()
Use datetime.__str__(), not isoformat() to conform to the native
timestamp format of SQLite3
2025-02-15 17:10:00 -05:00
2 changed files with 110 additions and 6 deletions

View file

@ -1,7 +1,12 @@
import os
import re
import enum
import datetime
from nexrad.s3 import S3Bucket
from nexrad.db import Database
from nexrad.s3 import S3Bucket, S3_KEY_RE
from nexrad.coord import COORD_SYSTEM
from nexrad.radar import RADAR_RANGE
class ArchiveDateError(Exception):
def __init__(self, supplied: str, missing: str):
@ -9,7 +14,101 @@ class ArchiveDateError(Exception):
self.missing = missing
def __str__(self):
return "Date {self.supplied} was supplied, but required {self.missing} is missing"
return "Archive {self.supplied} was supplied, but required {self.missing} is missing"
class ArchiveProductType(enum.Enum):
DEFAULT = 1
V03 = 3
class ArchiveProduct():
__slots__ = 'typeof', 'radar', 'timestamp',
typeof: ArchiveProductType
radar: str
timestamp: datetime.datetime
def __parts__(self):
return [
"%04d" % (self.timestamp.year),
"%02d" % (self.timestamp.month),
"%02d" % (self.timestamp.day),
self.radar,
"%4s%04d%02d%02d_%02d%02d%02d" % (
self.radar,
self.timestamp.year, self.timestamp.month, self.timestamp.day,
self.timestamp.hour, self.timestamp.minute, self.timestamp.second
)
]
def __str__(self):
ret = '/'.join(self.__parts__())
if self.typeof == ArchiveProductType.V03:
ret += "_V03"
ret += ".gz"
return ret
def __path__(self):
parts = self.__parts__()
ret = os.path.join(*parts)
if self.typeof == ArchiveProductType.V03:
ret += "_V03"
ret += ".gz"
return ret
@staticmethod
def from_s3_key(key: str):
product = ArchiveProduct()
match = S3_KEY_RE.match(key)
product.timestamp = datetime.datetime(
year = int(match[6]),
month = int(match[7]),
day = int(match[8]),
hour = int(match[9]),
minute = int(match[10]),
second = int(match[11]),
tzinfo = datetime.UTC
)
product.radar = match[4]
product.typeof = ArchiveProductType.V03 \
if key[-7:] == '_V03.gz' else ArchiveProductType.DEFAULT
return product
def is_downloaded(self, path: str):
return os.path.isfile(os.path.join(path, self.__path__()))
def is_reported(self, db: Database):
sql = """select count((
select ST_Distance(MakeLine(report.coord_start, report.coord_end),
radar.coord,
true) as distance
from
nexrad_storm_report as report,
nexrad_radar as radar
where
distance <= :radius
and :timestamp between report.timestamp_start and report.timestamp_end
and radar.call = :call)) as num
"""
st = db.execute(sql, {
'radius': RADAR_RANGE,
'timestamp': self.timestamp,
'call': self.radar
})
result = st.fetchone()
return result['num'] == 1
class Archive():
path: str
@ -94,3 +193,10 @@ class Archive():
parts.pop()
parts.pop()
def each_downloaded_product(self,
year: int=None,
month: int=None,
day: int=None):
for key in self.each_downloaded_key(year, month, day):
yield ArchiveProduct.from_s3_key(key)

View file

@ -199,14 +199,12 @@ class StormReport(DatabaseTable):
})
if timestamp is not None:
clauses.append("timestamp_start >= :timestamp and timestamp_end <= :timestamp")
clauses.append(":timestamp between timestamp_start and timestamp_end")
values.update({
'timestamp': timestamp.isoformat()
'timestamp': str(timestamp)
})
values.append(timestamp.isoformat())
sql = "select " + ", ".join(columns) + " from nexrad_storm_report"
if len(clauses) > 0: