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
3838import os
5858
5959urllib3 .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
6278def 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
0 commit comments