Initial implementation of IGRA data parser
This commit is contained in:
		
							parent
							
								
									85c4dc0411
								
							
						
					
					
						commit
						bd50af98b6
					
				
					 1 changed files with 149 additions and 0 deletions
				
			
		
							
								
								
									
										149
									
								
								lib/xmet/igra.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								lib/xmet/igra.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,149 @@ | ||||||
|  | import io | ||||||
|  | import re | ||||||
|  | import datetime | ||||||
|  | import shapely | ||||||
|  | 
 | ||||||
|  | from xmet.coord    import COORD_SYSTEM | ||||||
|  | from xmet.sounding import Sounding, SoundingSample | ||||||
|  | 
 | ||||||
|  | RE_HEADER = re.compile(r''' | ||||||
|  |       ^ (?P<headrec>\#) | ||||||
|  |         (?P<id>[A-Z0-9]{11}) | ||||||
|  |     [ ] (?P<year>\d{4}) | ||||||
|  |     [ ] (?P<month>\d{2}) | ||||||
|  |     [ ] (?P<day>\d{2}) | ||||||
|  |     [ ] (?P<hour>\d{2}) | ||||||
|  |     [ ] (?P<relhour>\d{2}) (?P<relmin>\d{2}) | ||||||
|  |     [ ] (?P<numlev>[0-9\ ]{4}) | ||||||
|  |     [ ] (?P<p_src>.{8}) | ||||||
|  |     [ ] (?P<np_src>.{8}) | ||||||
|  |     [ ] (?P<lat>[0-9 ]{7}) | ||||||
|  |     [ ] (?P<lon>[0-9 ]{8}) | ||||||
|  |     $ | ||||||
|  | ''', re.X) | ||||||
|  | 
 | ||||||
|  | RE_SAMPLE = re.compile(r''' | ||||||
|  |       ^ (?P<lvltyp1>\d) | ||||||
|  |         (?P<lvltyp2>\d) | ||||||
|  |     [ ] (?P<etime>[0-9 \-]{5}) | ||||||
|  |         (?P<press>[0-9 \-]{7}) | ||||||
|  |         (?P<pflag>[AB ]) | ||||||
|  |         (?P<gph>[0-9 \-]{5}) | ||||||
|  |         (?P<zflag>[AB ]) | ||||||
|  |         (?P<temp>[0-9 \-]{5}) | ||||||
|  |         (?P<tflag>[AB ]) | ||||||
|  |         (?P<rh>[0-9 \-]{5}) | ||||||
|  |     [ ] (?P<dpdp>[0-9 \-]{5}) | ||||||
|  |     [ ] (?P<wdir>[0-9 \-]{5}) | ||||||
|  |     [ ] (?P<wspd>[0-9 \-]{5}) | ||||||
|  | ''', re.X) | ||||||
|  | 
 | ||||||
|  | def etime_to_seconds(etime: str) -> int: | ||||||
|  |     if etime == '-8888' or etime == '-9999': | ||||||
|  |         return | ||||||
|  | 
 | ||||||
|  |     return 60 * int(etime[0:2]) + int(etime[2:]) | ||||||
|  | 
 | ||||||
|  | def parse_num(num: str, scale: float) -> float: | ||||||
|  |     if num == '-8888' or num == '-9999': | ||||||
|  |         return | ||||||
|  | 
 | ||||||
|  |     return int(num) * scale | ||||||
|  | 
 | ||||||
|  | class IGRAReaderException(Exception): | ||||||
|  |     ... | ||||||
|  | 
 | ||||||
|  | class IGRAReader(): | ||||||
|  |     def __init__(self, fh: io.TextIOBase): | ||||||
|  |         self.fh = fh | ||||||
|  | 
 | ||||||
|  |     def read_sample(self) -> SoundingSample: | ||||||
|  |         line = self.fh.readline() | ||||||
|  | 
 | ||||||
|  |         if line == '': | ||||||
|  |             return | ||||||
|  | 
 | ||||||
|  |         match = RE_SAMPLE.match(line) | ||||||
|  | 
 | ||||||
|  |         if match is None: | ||||||
|  |             raise IGRAReaderException(f"Invalid sample line {line}") | ||||||
|  | 
 | ||||||
|  |         print(f"Got etime '{match['etime']}'") | ||||||
|  |         print(f"Got press '{match['press']}'") | ||||||
|  |         print(f"Got pflag '{match['pflag']}'") | ||||||
|  |         print(f"Got gph '{match['gph']}'") | ||||||
|  |         print(f"Got zflag '{match['zflag']}'") | ||||||
|  |         print(f"Got temp '{match['temp']}'") | ||||||
|  |         print(f"Got rh '{match['rh']}'") | ||||||
|  | 
 | ||||||
|  |         sample = SoundingSample() | ||||||
|  |         sample.elapsed     = etime_to_seconds(match['etime']) | ||||||
|  |         sample.pressure    = parse_num(match['press'], 0.01) | ||||||
|  |         sample.pressure_qa = match['pflag'] | ||||||
|  |         sample.height      = parse_num(match['gph'], 1.0) | ||||||
|  |         sample.height_qa   = match['zflag'] | ||||||
|  |         sample.temp        = parse_num(match['temp'], 0.1) | ||||||
|  |         sample.temp_qa     = match['tflag'] | ||||||
|  |         sample.humidity    = parse_num(match['rh'], 0.1) | ||||||
|  |         sample.dewpoint    = parse_num(match['dpdp'], 0.1) | ||||||
|  |         sample.wind_dir    = parse_num(match['wdir'], 1.0) | ||||||
|  |         sample.wind_speed  = parse_num(match['wspd'], 0.1) | ||||||
|  | 
 | ||||||
|  |         return sample | ||||||
|  | 
 | ||||||
|  |     def read(self) -> Sounding: | ||||||
|  |         line = self.fh.readline() | ||||||
|  | 
 | ||||||
|  |         if line == '': | ||||||
|  |             return | ||||||
|  | 
 | ||||||
|  |         line = line.rstrip() | ||||||
|  | 
 | ||||||
|  |         match = RE_HEADER.match(line) | ||||||
|  | 
 | ||||||
|  |         if match is None: | ||||||
|  |             raise IGRAReaderException(f"Invalid record line {line}") | ||||||
|  | 
 | ||||||
|  |         sounding = Sounding() | ||||||
|  |         sounding.station = match['id'] | ||||||
|  | 
 | ||||||
|  |         date = datetime.datetime( | ||||||
|  |             year  = int(match['year']), | ||||||
|  |             month = int(match['month']), | ||||||
|  |             day   = int(match['day']) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         timestamp         = date.astimezone(datetime.UTC) | ||||||
|  |         timestamp_release = date.astimezone(datetime.UTC) | ||||||
|  | 
 | ||||||
|  |         if match['hour'] != '99': | ||||||
|  |             timestamp += datetime.timedelta(hours=int(match['hour'])) | ||||||
|  | 
 | ||||||
|  |         timestamp_release += datetime.timedelta(hours=int(match['relhour'])) | ||||||
|  | 
 | ||||||
|  |         if match['relmin'] != '99': | ||||||
|  |             timestamp_release += datetime.timedelta(minutes=int(match['relmin'])) | ||||||
|  | 
 | ||||||
|  |         lat = int(match['lat']) / 1000.0 | ||||||
|  |         lon = int(match['lon']) / 1000.0 | ||||||
|  | 
 | ||||||
|  |         sounding.timestamp_observed   = timestamp | ||||||
|  |         sounding.timestamp_released   = timestamp_release | ||||||
|  |         sounding.data_source_pressure = match['p_src'] | ||||||
|  |         sounding.data_source_other    = match['np_src'] | ||||||
|  |         sounding.coord                = shapely.Point(lon, lat, COORD_SYSTEM) | ||||||
|  |         sounding.samples              = list() | ||||||
|  | 
 | ||||||
|  |         count = int(match['numlev']) | ||||||
|  | 
 | ||||||
|  |         while count > 0: | ||||||
|  |             sample = self.read_sample() | ||||||
|  | 
 | ||||||
|  |             if sample is None: | ||||||
|  |                 break | ||||||
|  | 
 | ||||||
|  |             sounding.samples.append(sample) | ||||||
|  | 
 | ||||||
|  |             count -= 1 | ||||||
|  | 
 | ||||||
|  |         return sounding | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue