Initial implementation of IGRA ingest
This commit is contained in:
parent
c37f8643b4
commit
011d66d2d1
7 changed files with 115 additions and 20 deletions
|
@ -9,7 +9,11 @@ RUN mkdir -p /var/opt/xmet/lib/xmet
|
||||||
RUN mkdir -p /var/opt/xmet/bin
|
RUN mkdir -p /var/opt/xmet/bin
|
||||||
RUN mkdir -p /var/lib/xmet
|
RUN mkdir -p /var/lib/xmet
|
||||||
|
|
||||||
COPY db/xmet.sql doc/radars.tsv doc/wfo.tsv /tmp
|
COPY db/xmet.sql \
|
||||||
|
doc/radars.tsv \
|
||||||
|
doc/wfo.tsv \
|
||||||
|
doc/igra2-station-list.txt /tmp
|
||||||
|
|
||||||
COPY lib/xmet/*.py /var/opt/xmet/lib/xmet
|
COPY lib/xmet/*.py /var/opt/xmet/lib/xmet
|
||||||
COPY bin/xmet-nexrad-archive bin/xmet-db-init /var/opt/xmet/bin
|
COPY bin/xmet-nexrad-archive bin/xmet-db-init /var/opt/xmet/bin
|
||||||
|
|
||||||
|
@ -20,6 +24,7 @@ RUN sqlite3 -init /tmp/xmet.sql /var/lib/xmet/xmet.db .quit
|
||||||
RUN /var/opt/xmet/bin/xmet-db-init \
|
RUN /var/opt/xmet/bin/xmet-db-init \
|
||||||
/var/lib/xmet/xmet.db \
|
/var/lib/xmet/xmet.db \
|
||||||
/tmp/radars.tsv \
|
/tmp/radars.tsv \
|
||||||
/tmp/wfo.tsv
|
/tmp/wfo.tsv \
|
||||||
|
/tmp/igra2-station-list.txt
|
||||||
|
|
||||||
ENTRYPOINT ["/var/opt/xmet/bin/xmet-nexrad-archive", "/var/lib/xmet/xmet.db"]
|
ENTRYPOINT ["/var/opt/xmet/bin/xmet-nexrad-archive", "/var/lib/xmet/xmet.db"]
|
||||||
|
|
|
@ -5,6 +5,7 @@ import argparse
|
||||||
from xmet.db import Database
|
from xmet.db import Database
|
||||||
from xmet.radar import Radar
|
from xmet.radar import Radar
|
||||||
from xmet.wfo import WFO
|
from xmet.wfo import WFO
|
||||||
|
from xmet.igra import IGRAStation
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description = 'Initialize NEXRAD radar site database table'
|
description = 'Initialize NEXRAD radar site database table'
|
||||||
|
@ -13,6 +14,7 @@ parser = argparse.ArgumentParser(
|
||||||
parser.add_argument('db', help='Path to SQLite3 database')
|
parser.add_argument('db', help='Path to SQLite3 database')
|
||||||
parser.add_argument('radars-tsv', help='Path to NEXRAD radar station TSV file')
|
parser.add_argument('radars-tsv', help='Path to NEXRAD radar station TSV file')
|
||||||
parser.add_argument('wfo-tsv', help='Path to forecast office TSV file')
|
parser.add_argument('wfo-tsv', help='Path to forecast office TSV file')
|
||||||
|
parser.add_argument('igra-stations', help='Path to IGRA station list')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
@ -26,4 +28,7 @@ for radar in Radar.each_from_tsv(getattr(args, 'radars-tsv')):
|
||||||
for wfo in WFO.each_from_tsv(getattr(args, 'wfo-tsv')):
|
for wfo in WFO.each_from_tsv(getattr(args, 'wfo-tsv')):
|
||||||
db.add(wfo)
|
db.add(wfo)
|
||||||
|
|
||||||
|
for station in IGRAStation.each_from_file(getattr(args, 'igra-stations')):
|
||||||
|
db.add(station)
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
20
bin/xmet-igra-ingest
Executable file
20
bin/xmet-igra-ingest
Executable file
|
@ -0,0 +1,20 @@
|
||||||
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from xmet.db import Database
|
||||||
|
from xmet.igra import IGRAReader
|
||||||
|
|
||||||
|
db = Database.connect(sys.argv[1])
|
||||||
|
db.execute('begin transaction')
|
||||||
|
|
||||||
|
for path in sys.argv[2:]:
|
||||||
|
with open(path, 'r') as fh:
|
||||||
|
for sounding in IGRAReader.each_from_file(path):
|
||||||
|
db.add(sounding)
|
||||||
|
|
||||||
|
for sample in sounding.samples:
|
||||||
|
sample.sounding_id = sounding.id
|
||||||
|
db.add(sample)
|
||||||
|
|
||||||
|
db.commit()
|
|
@ -119,7 +119,7 @@ select AddGeometryColumn('xmet_sounding', 'coord', 4326, 'POINT'),
|
||||||
create table xmet_sounding_sample (
|
create table xmet_sounding_sample (
|
||||||
id INTEGER PRIMARY KEY NOT NULL,
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
sounding_id INTEGER NOT NULL,
|
sounding_id INTEGER NOT NULL,
|
||||||
elapsed INTEGER NOT NULL,
|
elapsed INTEGER,
|
||||||
pressure FLOAT,
|
pressure FLOAT,
|
||||||
pressure_qa TEXT NOT NULL,
|
pressure_qa TEXT NOT NULL,
|
||||||
height FLOAT,
|
height FLOAT,
|
||||||
|
|
|
@ -127,9 +127,6 @@ class Database():
|
||||||
def value_placeholders(self, table, obj) -> list:
|
def value_placeholders(self, table, obj) -> list:
|
||||||
ci = getattr(table, '__columns_write__', None)
|
ci = getattr(table, '__columns_write__', None)
|
||||||
|
|
||||||
if ci is None:
|
|
||||||
return [f':{c}' for c in table.__columns__]
|
|
||||||
else:
|
|
||||||
ret = list()
|
ret = list()
|
||||||
|
|
||||||
for c in table.__columns__:
|
for c in table.__columns__:
|
||||||
|
@ -138,7 +135,7 @@ class Database():
|
||||||
if v is None:
|
if v is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if c in ci:
|
if ci is not None and c in ci:
|
||||||
ret.append(ci[c])
|
ret.append(ci[c])
|
||||||
else:
|
else:
|
||||||
ret.append(f':{c}')
|
ret.append(f':{c}')
|
||||||
|
|
|
@ -150,6 +150,19 @@ class IGRAReader():
|
||||||
|
|
||||||
return sounding
|
return sounding
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def each_from_file(path: str):
|
||||||
|
with open(path, 'r') as fh:
|
||||||
|
reader = IGRAReader(fh)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
sounding = reader.read()
|
||||||
|
|
||||||
|
if sounding is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
yield sounding
|
||||||
|
|
||||||
def cols(text: str, start: int, end: int):
|
def cols(text: str, start: int, end: int):
|
||||||
a = start - 1
|
a = start - 1
|
||||||
b = end
|
b = end
|
||||||
|
@ -214,3 +227,14 @@ class IGRAStation(DatabaseTable):
|
||||||
station.location = shapely.Point(lon, lat)
|
station.location = shapely.Point(lon, lat)
|
||||||
|
|
||||||
return station
|
return station
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def each_from_file(path: str):
|
||||||
|
with open(path, 'r') as fh:
|
||||||
|
while True:
|
||||||
|
line = fh.readline()
|
||||||
|
|
||||||
|
if line == '' or line is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
yield IGRAStation.parse_station(line)
|
||||||
|
|
|
@ -1,16 +1,28 @@
|
||||||
import datetime
|
import datetime
|
||||||
import shapely
|
import shapely
|
||||||
|
|
||||||
|
from xmet.db import DatabaseTable
|
||||||
|
from xmet.coord import COORD_SYSTEM
|
||||||
|
|
||||||
LAPSE_RATE_DRY = 9.8 # degrees C per 1000m
|
LAPSE_RATE_DRY = 9.8 # degrees C per 1000m
|
||||||
LAPSE_RATE_MOIST = 4.0
|
LAPSE_RATE_MOIST = 4.0
|
||||||
|
|
||||||
class SoundingSample():
|
class SoundingSample(DatabaseTable):
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
'id', 'sounding_id', 'elapsed', 'pressure', 'pressure_qa',
|
'id', 'sounding_id', 'elapsed', 'pressure', 'pressure_qa',
|
||||||
'height', 'height_qa', 'temp', 'temp_qa', 'humidity',
|
'height', 'height_qa', 'temp', 'temp_qa', 'humidity',
|
||||||
'dewpoint', 'wind_dir', 'wind_speed'
|
'dewpoint', 'wind_dir', 'wind_speed'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
__table__ = 'xmet_sounding_sample'
|
||||||
|
__key__ = 'id'
|
||||||
|
|
||||||
|
__columns__ = (
|
||||||
|
'id', 'sounding_id', 'elapsed', 'pressure', 'pressure_qa',
|
||||||
|
'height', 'height_qa', 'temp', 'temp_qa', 'humidity',
|
||||||
|
'dewpoint', 'wind_dir', 'wind_speed'
|
||||||
|
)
|
||||||
|
|
||||||
id: int
|
id: int
|
||||||
sounding_id: int
|
sounding_id: int
|
||||||
elapsed: int
|
elapsed: int
|
||||||
|
@ -25,6 +37,10 @@ class SoundingSample():
|
||||||
wind_dir: float
|
wind_dir: float
|
||||||
wind_speed: float
|
wind_speed: float
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.id = None
|
||||||
|
|
||||||
def vapor_pressure(self) -> float:
|
def vapor_pressure(self) -> float:
|
||||||
return 6.11 * 10 * (
|
return 6.11 * 10 * (
|
||||||
(7.5 * self.dewpoint) / (237.3 * self.dewpoint)
|
(7.5 * self.dewpoint) / (237.3 * self.dewpoint)
|
||||||
|
@ -65,6 +81,30 @@ class Sounding():
|
||||||
'data_source_pressure', 'data_source_other', 'samples', 'coord'
|
'data_source_pressure', 'data_source_other', 'samples', 'coord'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
__table__ = 'xmet_sounding'
|
||||||
|
__key__ = 'id'
|
||||||
|
|
||||||
|
__columns__ = (
|
||||||
|
'id', 'station', 'timestamp_observed', 'timestamp_released',
|
||||||
|
'data_source_pressure', 'data_source_other', 'coord'
|
||||||
|
)
|
||||||
|
|
||||||
|
__columns_read__ = {
|
||||||
|
'coord': 'ST_AsText(coord) as coord'
|
||||||
|
}
|
||||||
|
|
||||||
|
__values_read__ = {
|
||||||
|
'coord': shapely.from_wkt
|
||||||
|
}
|
||||||
|
|
||||||
|
__columns_write__ = {
|
||||||
|
'coord': 'ST_GeomFromText(:coord, {crs})'.format(crs=COORD_SYSTEM)
|
||||||
|
}
|
||||||
|
|
||||||
|
__values_write__ = {
|
||||||
|
'coord': lambda v: {'coord': shapely.to_wkt(v)}
|
||||||
|
}
|
||||||
|
|
||||||
id: int
|
id: int
|
||||||
station: str
|
station: str
|
||||||
timestamp_observed: datetime.datetime
|
timestamp_observed: datetime.datetime
|
||||||
|
@ -73,3 +113,7 @@ class Sounding():
|
||||||
data_source_other: str
|
data_source_other: str
|
||||||
coord: shapely.Point
|
coord: shapely.Point
|
||||||
samples: list[SoundingSample]
|
samples: list[SoundingSample]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.id = None
|
||||||
|
|
Loading…
Add table
Reference in a new issue