Skip to content

Commit 74ee5a1

Browse files
committed
Merge branch 'dev'
2 parents 8ef3e1d + 8edff11 commit 74ee5a1

File tree

9 files changed

+227
-74
lines changed

9 files changed

+227
-74
lines changed

src/pyrad_proc/pyrad/io/__init__.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@
111111
read_ml_ts
112112
read_windmills_data
113113
read_radiosounding_wyoming
114-
read_radiosounding_igra
115-
read_fzl_igra
114+
retrieve_radiosounding_igra
115+
retrieve_fzl
116116
read_meteoswiss_xml_vad
117117
118118
Writing data
@@ -255,10 +255,11 @@
255255
from .read_data_sensor import read_coord_sensors, read_disdro_parsivel # noqa
256256
from .read_data_sensor import read_knmi # noqa
257257
from .read_data_sensor import ( # noqa
258-
read_radiosounding_wyoming,
259-
read_radiosounding_igra,
260-
)
261-
from .read_data_sensor import read_fzl_igra # noqa
258+
retrieve_radiosounding_wyoming, # noqa
259+
retrieve_radiosounding_igra, # noqa
260+
retrieve_radiosounding_mch, # noqa
261+
) # noqa
262+
from .read_data_sensor import retrieve_fzl # noqa
262263

263264
from .read_data_sun import read_sun_hits_multiple_days, read_sun_hits # noqa
264265
from .read_data_sun import read_sun_retrieval, read_solar_flux # noqa

src/pyrad_proc/pyrad/io/read_data_sensor.py

Lines changed: 163 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
read_disdro
3232
read_disdro_parsivel
3333
read_radiosounding_wyoming
34-
read_radiosounding_igra
35-
read_fzl_igra
34+
retrieve_radiosounding_igra
35+
retrieve_fzl
3636
"""
3737

3838
import os
@@ -58,6 +58,22 @@
5858

5959
urllib3.disable_warnings(InsecureRequestWarning)
6060

61+
COLUMNS_SOUNDING = [
62+
"LVLTYPE1",
63+
"LVLTYPE2",
64+
"ETIME",
65+
"PRESS",
66+
"PFLAG",
67+
"GPH",
68+
"ZFLAG",
69+
"TEMP",
70+
"TFLAG",
71+
"RH",
72+
"DPDP",
73+
"WDIR",
74+
"WSPD",
75+
]
76+
6177

6278
def read_windmills_data(fname):
6379
"""
@@ -2517,22 +2533,24 @@ def read_disdro_parsivel(fname):
25172533
return df_disdro
25182534

25192535

2520-
def read_radiosounding_wyoming(station, dtime):
2536+
def retrieve_radiosounding_wyoming(station, dtime):
25212537
"""
2522-
Download radiosounding data from the University of Wyoming website.
2538+
Download radiosounding data from the University of Wyoming website and
2539+
return as DataFrame with IGRA2-standard columns.
25232540
2524-
Parameters:
2525-
station : str
2526-
Radiosounding station 5 digits WMO code (e.g., "72776").
2527-
dtime : datetime.datetime
2528-
Datetime object specifying the desired date and time.
2541+
Parameters
2542+
----------
2543+
station : str
2544+
Radiosounding station 5-digit WMO code (e.g., "72776").
2545+
dtime : datetime.datetime
2546+
Datetime object specifying the desired date and time.
25292547
2530-
Returns:
2531-
pandas.DataFrame: A DataFrame containing the radiosounding data with
2532-
columns: ['PRES', 'HGHT', 'TEMP', 'DWPT', 'RELH', 'MIXR', 'DRCT',
2533-
'SKNT', 'THTA', 'THTE', 'THTV]
2548+
Returns
2549+
-------
2550+
pandas.DataFrame or None
2551+
A DataFrame containing the radiosounding data in IGRA2 format,
2552+
or None if retrieval was not successful.
25342553
"""
2535-
25362554
base_url = "https://weather.uwyo.edu/cgi-bin/sounding"
25372555
query_params = {
25382556
"region": "naconf",
@@ -2545,36 +2563,68 @@ def read_radiosounding_wyoming(station, dtime):
25452563
}
25462564

25472565
try:
2548-
# Try a quick HEAD request to test SSL verification
2566+
# Quick HEAD request to handle SSL verification issues
25492567
requests.head(base_url, timeout=5)
25502568
verify_setting = True
25512569
except requests.exceptions.SSLError:
25522570
verify_setting = False
25532571

25542572
response = requests.get(base_url, params=query_params, verify=verify_setting)
2555-
25562573
if response.status_code != 200:
2574+
warn(f"Failed to retrieve data: HTTP {response.status_code}")
25572575
return None
25582576

2559-
# Extract and parse the data using pandas
2577+
# Extract text block
25602578
start_idx = response.text.find("PRES")
25612579
end_idx = response.text.find("Station information and sounding indices")
2562-
data_str = response.text[start_idx:end_idx][0:-10]
2580+
if start_idx == -1 or end_idx == -1:
2581+
warn("Could not locate sounding data block in response")
2582+
return None
25632583

2584+
data_str = response.text[start_idx:end_idx].strip()
25642585
data_io = StringIO(data_str)
2565-
data_df = pd.read_csv(
2566-
data_io,
2567-
sep=r"\s+",
2568-
header=0,
2569-
skiprows=[1, 2],
2570-
on_bad_lines="warn",
2571-
)
2586+
2587+
# Parse table (skip line with units)
2588+
try:
2589+
data_df = pd.read_csv(
2590+
data_io,
2591+
sep=r"\s+",
2592+
header=0,
2593+
skiprows=[1],
2594+
on_bad_lines="skip",
2595+
)
2596+
except Exception as e:
2597+
warn(f"Parsing error: {e}")
2598+
return None
2599+
2600+
# Convert to numeric where possible
25722601
for col in data_df.columns:
2573-
data_df[col] = pd.to_numeric(data_df[col])
2574-
return data_df
2602+
data_df[col] = pd.to_numeric(data_df[col], errors="coerce")
2603+
2604+
# Map Wyoming columns to IGRA2 columns
2605+
df = pd.DataFrame(
2606+
{
2607+
"LVLTYPE1": "", # Not available from Wyoming
2608+
"LVLTYPE2": "",
2609+
"ETIME": dtime.strftime("%Y%m%d%H"), # assign launch time to all rows
2610+
"PRESS": data_df.get("PRES"),
2611+
"PFLAG": "",
2612+
"GPH": data_df.get("HGHT"),
2613+
"ZFLAG": "",
2614+
"TEMP": data_df.get("TEMP"),
2615+
"TFLAG": "",
2616+
"RH": data_df.get("RELH"),
2617+
"DPDP": data_df.get("DWPT"),
2618+
"WDIR": data_df.get("DRCT"),
2619+
"WSPD": data_df.get("SKNT") * 0.514444, # knots → m/s
2620+
},
2621+
columns=COLUMNS_SOUNDING,
2622+
)
25752623

2624+
return df
25762625

2577-
def read_radiosounding_igra(station, dtime):
2626+
2627+
def retrieve_radiosounding_igra(station, dtime):
25782628
"""
25792629
Download radiosounding data from from the Integrated Global Radiosonde Archive
25802630
(IGRA).
@@ -2594,22 +2644,6 @@ def read_radiosounding_igra(station, dtime):
25942644
https://www.ncei.noaa.gov/data/integrated-global-radiosonde-archive/doc/igra2-data-format.txt
25952645
"""
25962646
print("Downloading sounding from IGRA database...")
2597-
2598-
COLUMNS_SOUNDING = [
2599-
"LVLTYPE1",
2600-
"LVLTYPE2",
2601-
"ETIME",
2602-
"PRESS",
2603-
"PFLAG",
2604-
"GPH",
2605-
"ZFLAG",
2606-
"TEMP",
2607-
"TFLAG",
2608-
"RH",
2609-
"DPDP",
2610-
"WDIR",
2611-
"WSPD",
2612-
]
26132647
COLUMNS_STATION_LIST = [
26142648
"CODE",
26152649
"LATITUDE",
@@ -2752,10 +2786,82 @@ def read_radiosounding_igra(station, dtime):
27522786
return sounding
27532787

27542788

2755-
def read_fzl_igra(station, dtime, dscfg=None):
2789+
def retrieve_radiosounding_mch(station, dtime):
27562790
"""
2757-
Get an estimation of the freezing level height from the
2758-
Integrated Global Radiosonde Archive (IGRA)
2791+
Retrieve sounding data from MCH DWH service and return as pandas DataFrame.
2792+
It requires to set the environment variable $JRETRIEVE_PATH that points to a
2793+
valid jretrieve executable.
2794+
2795+
Parameters:
2796+
station : str
2797+
Radiosounding station 5 digits WMO code (e.g., "72776").
2798+
dtime : datetime.datetime
2799+
Datetime object specifying the desired date and time.
2800+
2801+
Returns:
2802+
pandas.DataFrame: A DataFrame containing the radiosounding data with
2803+
['LVLTYPE1', 'LVLTYPE2','ETIME', 'PRESS', 'PFLAG', 'GPH','ZFLAG', 'TEMP',
2804+
'TFLAG', 'RH','DPDP','WDIR','WSPD']
2805+
2806+
"""
2807+
print("Downloading sounding from MCH DWH database...")
2808+
2809+
dtime_closest = _closest_sounding_time(dtime)
2810+
dtime_closest = dtime_closest.strftime("%Y%m%d%H%M%S")
2811+
2812+
# Variables to retrieve (DWH IDs mapped to IGRA2 columns)
2813+
id_variables = "744,742,745,747,748,743" # p, geopotential, T, Td, ws, wd
2814+
2815+
# Build DWH command
2816+
ret_dwh = (
2817+
f'{os.path.expandvars("$JRETRIEVE_PATH")} -s profile -w 22 --verbose position '
2818+
f"-i int_ind,{station} "
2819+
f"-t {dtime_closest} -p {id_variables}"
2820+
)
2821+
try:
2822+
with os.popen(ret_dwh) as pipefile:
2823+
lines = pipefile.readlines()
2824+
2825+
rows = []
2826+
for line in lines:
2827+
if dtime_closest in line:
2828+
vals = line.split("|")
2829+
# Extract relevant fields by index (adjust if needed)
2830+
rows.append(
2831+
{
2832+
"LVLTYPE1": vals[4].strip(),
2833+
"LVLTYPE2": vals[3].strip(),
2834+
"ETIME": vals[1].strip(),
2835+
"PRESS": float(vals[8].strip()),
2836+
"PFLAG": "", # no flag info available from DWH
2837+
"GPH": float(vals[9].strip()),
2838+
"ZFLAG": "",
2839+
"TEMP": float(vals[10].strip()),
2840+
"TFLAG": "",
2841+
"RH": "", # not retrieved
2842+
"DPDP": float(vals[11].strip()),
2843+
"WDIR": float(vals[13].strip()),
2844+
"WSPD": float(vals[12].strip()),
2845+
}
2846+
)
2847+
2848+
if not rows:
2849+
warn(f"No valid data returned by DWH call. Return: {lines}")
2850+
return None
2851+
2852+
df = pd.DataFrame(rows, columns=COLUMNS_SOUNDING)
2853+
return df
2854+
2855+
except EnvironmentError:
2856+
warn(f"Unable to read DWH data with call {ret_dwh}")
2857+
return None
2858+
2859+
2860+
def retrieve_fzl(station, dtime, dscfg=None, source="IGRA"):
2861+
"""
2862+
Get an estimation of the freezing level height either the
2863+
Integrated Global Radiosonde Archive (IGRA), University of Wyoming archive
2864+
or the MCH digital warehouse (DWH)
27592865
27602866
Parameters:
27612867
station : str
@@ -2765,10 +2871,11 @@ def read_fzl_igra(station, dtime, dscfg=None):
27652871
dscfg : dictionary of dictionaries, Optional
27662872
If provided will try to read the fzl from the pyrad config
27672873
dictionary instead of fetching it from IGRA
2874+
source: str
2875+
either 'IGRA', "WYOMING" or 'MCH'
27682876
Returns:
27692877
fzl : float
2770-
Interpolated freezing level height (a.s.l) obtained
2771-
from the nearest in time IGRA sounding
2878+
Interpolated freezing level height (a.s.l)
27722879
"""
27732880
if dscfg is None:
27742881
dscfg = {}
@@ -2780,7 +2887,13 @@ def read_fzl_igra(station, dtime, dscfg=None):
27802887
except (KeyError, TypeError):
27812888
pass
27822889

2783-
sounding = read_radiosounding_igra(station, dtime)
2890+
if source == "IGRA":
2891+
sounding = retrieve_radiosounding_igra(station, dtime)
2892+
elif source == "MCH":
2893+
sounding = retrieve_radiosounding_mch(station, dtime)
2894+
elif source == "WYOMING":
2895+
sounding = retrieve_radiosounding_wyoming(station, dtime)
2896+
27842897
height = sounding["GPH"]
27852898
temp = sounding["TEMP"]
27862899

src/pyrad_proc/pyrad/proc/process_Doppler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
get_fieldname_pyart,
4747
convert_pydda_to_pyart_grid,
4848
)
49-
from ..io import read_radiosounding_igra
49+
from ..io import retrieve_radiosounding_igra
5050
from .process_grid import process_grid
5151
from ..util import compute_average_vad
5252

@@ -1067,7 +1067,7 @@ def process_dda(procstatus, dscfg, radar_list=None):
10671067
sounding = dscfg.get("sounding", None)
10681068
if sounding:
10691069
dtime = pyart.graph.common.generate_radar_time_begin(radar_list[0])
1070-
sounding_data = read_radiosounding_igra(sounding, dtime)
1070+
sounding_data = retrieve_radiosounding_igra(sounding, dtime)
10711071
else:
10721072
sounding_data = None
10731073

src/pyrad_proc/pyrad/proc/process_calib.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2063,7 +2063,6 @@ def process_sunscan(procstatus, dscfg, radar_list=None):
20632063
ref_time = None
20642064
if "wavelen" in dscfg["global_data"]:
20652065
flx_dt, flx_val = read_solar_flux(dscfg["solarfluxpath"] + "fluxtable.txt")
2066-
20672066
if flx_dt is not None:
20682067
flx_dt_closest, flx_val_closest = get_closest_solar_flux(
20692068
sun_hits["time"], flx_dt, flx_val
@@ -2246,7 +2245,6 @@ def process_sunscan(procstatus, dscfg, radar_list=None):
22462245
flx_dt, flx_val = read_solar_flux(
22472246
dscfg["solarfluxpath"] + "fluxtable.txt"
22482247
)
2249-
22502248
if flx_dt is not None:
22512249
flx_dt_closest, flx_val_closest = get_closest_solar_flux(
22522250
sun_hits["time"], flx_dt, flx_val

0 commit comments

Comments
 (0)