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