def get_bitmap_of_weather_conditions(m: Metar.Metar, weather_desc: WeatherCondDescription): bitmap = [0] * weather_desc.total_conditions if m.present_weather() != "": current_conditions = m.present_weather().split("; ") for cond in current_conditions: bitmap[weather_desc.weather_cond_dict[cond]] = 1 return bitmap
def from_metar_to_list_of_features(airport: str, timestamp: datetime, m: Metar.Metar) -> List: result = [ airport, timestamp, m.code, m.mod, # m.station_id, m.wind_speed.value("mps") if m.wind_speed else None, m.wind_gust.value("mps") if m.wind_gust else None, m.wind_dir.value() if m.wind_dir else None, m.wind_dir_from.value() if m.wind_dir_from else None, m.wind_dir_to.value() if m.wind_dir_to else None, m.vis.value("m") if m.vis else None, m.max_vis.value("m") if m.max_vis else None, m.max_vis_dir.value() if m.max_vis_dir else None, m.runway[0][1].value("m") if m.runway else None, m.runway[0][2].value("m") if m.runway else None, m.temp.value("C") if m.temp else None, m.dewpt.value("C") if m.dewpt else None, m.press.value("HPA") if m.press else None, str(m.sky_conditions("; ")), # str(m.weather), str(m.present_weather()), # str(m.recent), # str(m.sky), # str(m.windshear), # str(m._trend), # str(m._trend_groups), # str(m._remarks), # str(m._unparsed_groups), # str(m._unparsed_remarks), ] return result
def internal_metar(metar_text: str) -> Dict[str, Any]: """ Convert the output of the Metar parser into a dictionary which could be queried in a document database :param metar_text: N :return: a Dict with decoded METAR information """ document = {} wxd = Metar(metar_text) document["station"] = wxd.station_id if wxd.type: document["type"] = wxd.type if wxd.time: document["time"] = wxd.time.isoformat()+'Z' if wxd.temp: document["temp"] = wxd.temp.value(units="F") if wxd.dewpt: document["dewpt"] = wxd.dewpt.value(units="F") if "temp" in document and "dewpt" in document: document["humidity"] = rel_humidity(document["temp"], document["dewpt"]) if wxd.wind_speed: document["wind_speed"] = wxd.wind_speed.value(units="mph") if wxd.wind_dir: document["wind_dir"] = wxd.wind_dir.value() if wxd.vis: document["visibility"] = wxd.vis.value(units="sm") if wxd.press: document["pressure"] = wxd.press.value(units="mb") if wxd.sky: document["sky"] = wxd.sky_conditions() if wxd.press_sea_level: document["pressure"] = wxd.press_sea_level.value("mb") document["code"] = wxd.code return document
def process_metar(mstr, now): """ Do the METAR Processing """ mtr = None while mtr is None: try: mtr = Metar(mstr, now.month, now.year) except MetarParserError as exp: try: msg = str(exp) except Exception as exp: return None if msg.find("day is out of range for month") > 0 and now.day == 1: now -= datetime.timedelta(days=1) continue tokens = ERROR_RE.findall(str(exp)) orig_mstr = mstr if tokens: for token in tokens[0].split(): mstr = mstr.replace(" %s" % (token, ), "") if orig_mstr == mstr: print("Can't fix badly formatted metar: " + mstr) return None else: print("MetarParserError: "+msg) print(" --> now: %s month: %s, year: %s" % (now, now.month, now.year)) sys.exit() return None except Exception, exp: print("Double Fail: %s %s" % (mstr, exp)) return None
def parse_page(page): lines = filter(clean_line, page.split("\n")) records = map(get_date_and_standard_metar, lines) data = { "observation_time": [], "temperature": [], "dew_point": [], "pressure": [], "humidity": [], } for observation_time, raw_metar in records: try: m = Metar(raw_metar) except ParserError as err: logger.error( "Error parsing METAR: %s - %s", observation_time, raw_metar, exc_info=True, ) continue temperature = m.temp.value() dew_point = m.dewpt.value() data["observation_time"].append(observation_time) data["temperature"].append(temperature) data["dew_point"].append(dew_point) data["pressure"].append(m.press.value()) data["humidity"].append(humidity(temperature, dew_point)) return pd.DataFrame.from_dict(data)
def parse_page(page): lines = filter(clean_line, page.split('\n')) records = map(get_date_and_standard_metar, lines) data = { 'observation_time': [], 'temperature': [], 'dew_point': [], 'pressure': [], 'humidity': [], } for observation_time, raw_metar in records: try: m = Metar(raw_metar) except ParserError as err: logger.error( 'Error parsing METAR: %s - %s', observation_time, raw_metar, exc_info=True, ) continue temperature = m.temp.value() dew_point = m.dewpt.value() data['observation_time'].append(observation_time) data['temperature'].append(temperature) data['dew_point'].append(dew_point) data['pressure'].append(m.press.value()) data['humidity'].append(humidity(temperature, dew_point)) return pd.DataFrame.from_dict(data)
def process_metar(mstr, now): """ Do the METAR Processing """ mtr = None while mtr is None: try: mtr = Metar(mstr, now.month, now.year) except MetarParserError as exp: try: msg = str(exp) except Exception as exp: return None tokens = ERROR_RE.findall(str(exp)) orig_mstr = mstr if tokens: for token in tokens[0].split(): mstr = mstr.replace(" %s" % (token, ), "") if orig_mstr == mstr: print("Can't fix badly formatted metar: " + mstr) return None else: print("MetarParserError: " + msg) return None except Exception, exp: print("Double Fail: %s %s" % (mstr, exp)) return None
def main(): """Go Main Go""" pgconn = get_dbconn("asos") icursor = pgconn.cursor() icursor.execute("SET TIME ZONE 'UTC'") icursor2 = pgconn.cursor() sts = datetime.datetime(2011, 1, 1) ets = datetime.datetime(2012, 1, 1) interval = datetime.timedelta(days=1) now = sts while now < ets: icursor.execute(""" select valid, station, metar from t%s where metar is not null and valid >= '%s' and valid < '%s' """ % (now.year, now.strftime("%Y-%m-%d"), (now+interval).strftime("%Y-%m-%d"))) total = 0 for row in icursor: try: mtr = Metar(row[2], row[0].month, row[0].year) except Exception as _exp: continue sql = 'update t%s SET ' % (now.year,) if mtr.max_temp_6hr: sql += "max_tmpf_6hr = %s," % (mtr.max_temp_6hr.value("F"),) if mtr.min_temp_6hr: sql += "min_tmpf_6hr = %s," % (mtr.min_temp_6hr.value("F"),) if mtr.max_temp_24hr: sql += "max_tmpf_24hr = %s," % (mtr.max_temp_24hr.value("F"),) if mtr.min_temp_24hr: sql += "min_tmpf_24hr = %s," % (mtr.min_temp_24hr.value("F"),) if mtr.precip_3hr: sql += "p03i = %s," % (mtr.precip_3hr.value("IN"),) if mtr.precip_6hr: sql += "p06i = %s," % (mtr.precip_6hr.value("IN"),) if mtr.precip_24hr: sql += "p24i = %s," % (mtr.precip_24hr.value("IN"),) if mtr.press_sea_level: sql += "mslp = %s," % (mtr.press_sea_level.value("MB"),) if mtr.weather: pwx = [] for x in mtr.weather: pwx.append(("").join([a for a in x if a is not None])) sql += "presentwx = '%s'," % ((",".join(pwx))[:24], ) if sql == "update t%s SET " % (now.year,): continue sql = "%s WHERE station = '%s' and valid = '%s'" % (sql[:-1], row[1], row[0]) # print sql icursor2.execute(sql) total += 1 if total % 1000 == 0: print('Done total: %s now: %s' % (total, now)) pgconn.commit() now += interval icursor2.close() pgconn.commit() pgconn.close()
def test_set_weather_from_metar(metar, out, weather_test_file, out_file): in_metar = Metar(metar) with Miz(weather_test_file) as miz: _randomize_weather(miz.mission) miz.zip(out_file) emiz.weather.mizfile.set_weather_from_metar(metar, out_file, out_file) out_metar = Metar( emiz.weather.mizfile.get_metar_from_mission(out_file, 'UGTB')) assert in_metar.temp.value() == out_metar.temp.value() assert int( in_metar.wind_speed.value('MPS')) == out_metar.wind_speed.value('MPS') assert in_metar.wind_dir.value() == out_metar.wind_dir.value() with Miz(out_file) as miz: assert miz.mission.weather.wind_at_ground_level_dir == emiz.weather.utils.reverse_direction( in_metar.wind_dir.value()) assert miz.mission.weather.wind_at2000_speed >= miz.mission.weather.wind_at_ground_level_speed assert miz.mission.weather.wind_at8000_speed >= miz.mission.weather.wind_at_ground_level_speed for key in out: if isinstance(out[key], tuple): assert out[key][0] <= getattr(miz.mission.weather, key) <= out[key][1] else: assert getattr(miz.mission.weather, key) == out[key], key
def weather_today_job(): # request data from NOAA server (METAR of Nancy-Essey Airport) r = requests.get( 'http://tgftp.nws.noaa.gov/data/observations/metar/stations/LFSN.TXT', timeout=10.0, headers={'User-Agent': USER_AGENT}) # check error if r.status_code == 200: # extract METAR message metar_msg = r.content.decode().split('\n')[1] # METAR parse obs = Metar(metar_msg) # init and populate d_today dict d_today = {} # message date and time if obs.time: d_today['update_iso'] = obs.time.strftime('%Y-%m-%dT%H:%M:%SZ') d_today['update_fr'] = dt_utc_to_local( obs.time).strftime('%H:%M %d/%m') # current temperature if obs.temp: d_today['temp'] = round(obs.temp.value('C')) # current dew point if obs.dewpt: d_today['dewpt'] = round(obs.dewpt.value('C')) # current pressure if obs.press: d_today['press'] = round(obs.press.value('hpa')) # current wind speed if obs.wind_speed: d_today['w_speed'] = round(obs.wind_speed.value('KMH')) # current wind gust if obs.wind_gust: d_today['w_gust'] = round(obs.wind_gust.value('KMH')) # current wind direction if obs.wind_dir: # replace 'W'est by 'O'uest d_today['w_dir'] = obs.wind_dir.compass().replace('W', 'O') # weather status str d_today['descr'] = 'n/a' # store to redis DB.main.set_as_json('json:weather:today:nancy', d_today, ex=2 * 3600)
def add_ground_wind(airport_code, aloft_wind): """ Add the wind information at the ground for the given station code. """ raw = requests.get(BASE_ADDR.format(airport_code.upper())) if raw.status_code == 404: raw = requests.get(BASE_ADDR.format('K' + airport_code.upper())) if raw.status_code == 404: raise ValueError('The given station code is invalid.') ground_wind = {'altitude': 0, 'direction': 0, 'speed': 0} for line in raw.text.split('\n'): if airport_code.upper() in line: line = line.strip() observation = Metar(line) ground_wind['direction'] = observation.wind_dir.value() ground_wind['speed'] = observation.wind_speed.value(units='KT') return [ground_wind] + aloft_wind
def process_data(self): with warnings.catch_warnings(): warnings.simplefilter("ignore") try: self.log.info("Processing Metar data...") with open( os.path.join(os.path.dirname(__file__), "../metar/stations.json")) as in_file: icao = json.load(in_file) now = arrow.utcnow() hour = now.hour minute = now.minute if minute < 45: current_cycle = hour else: current_cycle = hour + 1 % 24 stations = {} for cycle in (current_cycle - 1, current_cycle): file = f"http://tgftp.nws.noaa.gov/data/observations/metar/cycles/{cycle:02d}Z.TXT" self.log.info(f"Processing '{file}' ...") request = requests.get(file, stream=True, timeout=(self.connect_timeout, self.read_timeout)) for line in request.iter_lines(): if line: data = line.decode("iso-8859-1") try: # Is this line a date with format "2017/05/12 23:55" ? arrow.get(data, "YYYY/MM/DD HH:mm") continue # Catch also ValueError because https://github.com/crsmithdev/arrow/issues/535 except (arrow.parser.ParserError, ValueError): try: metar = Metar(data, strict=False) # wind_dir could be NONE if 'dir' is 'VRB' if metar.wind_speed: if metar.station_id not in stations: stations[metar.station_id] = {} key = arrow.get( metar.time).int_timestamp stations[metar.station_id][key] = metar except Exception as e: self.log.warning( f"Error while parsing METAR data: {e}") continue for metar_id in stations: metar = next(iter(stations[metar_id].values())) try: name, short_name, default_name, lat, lon, altitude, tz = ( None, None, None, None, None, None, None, ) checkwx_key = f"metar/checkwx/{metar.station_id}" if not self.redis.exists(checkwx_key): try: self.log.info("Calling api.checkwx.com...") request = requests.get( f"https://api.checkwx.com/station/{metar.station_id}", headers={ "Accept": "application/json", "X-API-Key": self.checkwx_api_key }, timeout=(self.connect_timeout, self.read_timeout), ) if request.status_code == 401: raise UsageLimitException( request.json()["errors"][0]["message"]) elif request.status_code == 429: raise UsageLimitException( "api.checkwx.com rate limit exceeded") try: checkwx_data = request.json()["data"][0] if "icao" not in checkwx_data: raise ProviderException( "Invalid CheckWX data") except (ValueError, KeyError): checkwx_json = request.json() messages = [] if type(checkwx_json["data"]) is list: messages.extend(checkwx_json["data"]) else: messages.append(checkwx_json["data"]) raise ProviderException( f'CheckWX API error: {",".join(messages)}' ) self.add_redis_key( checkwx_key, { "data": json.dumps(checkwx_data), "date": arrow.now().format( "YYYY-MM-DD HH:mm:ssZZ"), }, self.checkwx_cache_duration, ) except TimeoutError as e: raise e except UsageLimitException as e: self.add_redis_key( checkwx_key, { "error": repr(e), "date": arrow.now().format( "YYYY-MM-DD HH:mm:ssZZ"), }, self.usage_limit_cache_duration, ) except Exception as e: if not isinstance(e, ProviderException): self.log.exception( "Error while getting CheckWX data") else: self.log.warning( f"Error while getting CheckWX data: {e}" ) self.add_redis_key( checkwx_key, { "error": repr(e), "date": arrow.now().format( "YYYY-MM-DD HH:mm:ssZZ"), }, self.checkwx_cache_duration, ) if not self.redis.hexists(checkwx_key, "error"): checkwx_data = json.loads( self.redis.hget(checkwx_key, "data")) station_type = checkwx_data.get("type", None) if station_type: name = f'{checkwx_data["name"]} {station_type}' else: name = checkwx_data["name"] city = checkwx_data.get("city", None) if city: if station_type: short_name = f"{city} {station_type}" else: short_name = city else: default_name = checkwx_data["name"] lat = checkwx_data["latitude"]["decimal"] lon = checkwx_data["longitude"]["decimal"] try: tz = checkwx_data["timezone"]["tzid"] except KeyError: raise ProviderException( "Unable to get the timezone") elevation = checkwx_data.get("elevation", None) altitude = None if elevation: if "meters" in elevation: altitude = Q_(elevation["meters"], ureg.meters) elif "feet" in elevation: altitude = Q_(elevation["feet"], ureg.feet) if metar.station_id in icao: lat = lat or icao[metar.station_id]["lat"] lon = lon or icao[metar.station_id]["lon"] default_name = default_name or icao[ metar.station_id]["name"] station = self.save_station( metar.station_id, short_name, name, lat, lon, StationStatus.GREEN, altitude=altitude, tz=tz, url=os.path.join( self.provider_url, f"site?id={metar.station_id}&db=metar"), default_name=default_name or f"{metar.station_id} Airport", lookup_name=f"{metar.station_id} Airport ICAO", ) station_id = station["_id"] if metar.station_id not in icao: self.log.warning( f"Missing '{metar.station_id}' ICAO in database. Is it '{station['name']}'?" ) measures_collection = self.measures_collection( station_id) new_measures = [] for key in stations[metar_id]: metar = stations[metar_id][key] if not self.has_measure(measures_collection, key): temp = self.get_quantity( metar.temp, self.temperature_units) dew_point = self.get_quantity( metar.dewpt, self.temperature_units) humidity = self.compute_humidity( dew_point, temp) measure = self.create_measure( station, key, self.get_direction(metar.wind_dir), self.get_quantity(metar.wind_speed, self.speed_units), self.get_quantity( metar.wind_gust or metar.wind_speed, self.speed_units), temperature=temp, humidity=humidity, pressure=Pressure( qfe=None, qnh=self.get_quantity( metar.press, self.pressure_units), qff=self.get_quantity( metar.press_sea_level, self.pressure_units), ), ) new_measures.append(measure) self.insert_new_measures(measures_collection, station, new_measures) except ProviderException as e: self.log.warning( f"Error while processing station '{metar_id}': {e}" ) except Exception as e: self.log.exception( f"Error while processing station '{metar_id}': {e}" ) except Exception as e: self.log.exception(f"Error while processing Metar: {e}") self.log.info("Done !")
def process_metar(mstr, now): """ Do the METAR Processing """ mtr = None while mtr is None: try: mtr = Metar(mstr, now.month, now.year) except MetarParserError as exp: try: msg = str(exp) except Exception as exp: return None if msg.find("day is out of range for month") > 0 and now.day == 1: now -= datetime.timedelta(days=1) continue tokens = ERROR_RE.findall(str(exp)) orig_mstr = mstr if tokens: for token in tokens[0].split(): mstr = mstr.replace(" %s" % (token, ), "") if orig_mstr == mstr: print("Can't fix badly formatted metar: " + mstr) return None else: print("MetarParserError: " + msg) print(" --> now: %s month: %s, year: %s" % (now, now.month, now.year)) sys.exit() return None except Exception as exp: print("Double Fail: %s %s" % (mstr, exp)) return None if mtr is None or mtr.time is None: return None ob = OB() ob.metar = mstr[:254] gts = datetime.datetime( mtr.time.year, mtr.time.month, mtr.time.day, mtr.time.hour, mtr.time.minute, ) gts = gts.replace(tzinfo=pytz.UTC) # When processing data on the last day of the month, we get GMT times # for the first of this month if gts.day == 1 and now.day > 10: tm = now + datetime.timedelta(days=1) gts = gts.replace(year=tm.year, month=tm.month, day=tm.day) ob.valid = gts if mtr.temp: ob.tmpf = mtr.temp.value("F") if mtr.dewpt: ob.dwpf = mtr.dewpt.value("F") if mtr.wind_speed: ob.sknt = mtr.wind_speed.value("KT") if mtr.wind_gust: ob.gust = mtr.wind_gust.value("KT") if mtr.wind_dir and mtr.wind_dir.value() != "VRB": ob.drct = mtr.wind_dir.value() if mtr.vis: ob.vsby = mtr.vis.value("SM") if mtr.press: ob.alti = mtr.press.value("IN") if mtr.press_sea_level: ob.mslp = mtr.press_sea_level.value("MB") if mtr.precip_1hr: ob.p01i = mtr.precip_1hr.value("IN") # Do something with sky coverage for i in range(len(mtr.sky)): (c, h, _) = mtr.sky[i] setattr(ob, "skyc%s" % (i + 1), c) if h is not None: setattr(ob, "skyl%s" % (i + 1), h.value("FT")) if mtr.max_temp_6hr: ob.max_tmpf_6hr = mtr.max_temp_6hr.value("F") if mtr.min_temp_6hr: ob.min_tmpf_6hr = mtr.min_temp_6hr.value("F") if mtr.max_temp_24hr: ob.max_tmpf_24hr = mtr.max_temp_24hr.value("F") if mtr.min_temp_24hr: ob.min_tmpf_6hr = mtr.min_temp_24hr.value("F") if mtr.precip_3hr: ob.p03i = mtr.precip_3hr.value("IN") if mtr.precip_6hr: ob.p06i = mtr.precip_6hr.value("IN") if mtr.precip_24hr: ob.p24i = mtr.precip_24hr.value("IN") # Presentwx if mtr.weather: pwx = [] for x in mtr.weather: pwx.append(("").join([a for a in x if a is not None])) ob.wxcodes = pwx return ob
#code='METAR OMDW 220000Z 09006KT 2500 BR NSC 26/25 Q1004 BECMG 0800 BCFG' #code='METAR LCEN 302350Z 28008KT CAVOK 17/09 Q1016 NOSIG' #code='SPECI CZCP 302353Z AUTO 11016KT 2 3/4SM OVC005 OVC015 OVC020 M03/ A2989 RMK ICG PAST HR' #code='METAR VANP 302340Z 00000KT 3500 HZ NSC 21/18 Q1014 TEMPO 3000 HZ/BR' #code='METAR SEGU 010000Z 22009KT 9999 SCT023 BKN100 26/20 Q1009 RMK A2983 NOSIG' #code='METAR SEQM 010000Z 12007KT CAVOK 16/03 Q1024 NOSIG RMK A3024' #code='SPECI CZCP 302355Z AUTO 11017KT 2 3/4SM OVC004 OVC020 M03/ A2989 RMK ICG PAST HR' #code='METAR LCEN 302350Z 28008KT CAVOK 17/09 Q1016 NOSIG' #code='METAR VABB 310040Z 08008KT 3000 HZ NSC 25/18 Q1011 NOSIG=' #code='METAR COR VABB 310040Z 08008KT 3000 HZ NSC 25/18 Q1011 NOSIG=' code='METAR LEMD 222200Z 35007KT 310V020 CAVOK 27/10 Q1015 NOSIG=' print "-----------------------------------------------------------------------" print "METAR: ",code print "-----------------------------------------------------------------------" # Initialize a Metar object with the coded report obs = Metar(code) print obs.string() #print obs.string().split('\n') metar_header = {} metar_header['ICAO_code'] = obs.station_id metar_header['origin_time'] = obs.time.isoformat(' ') metar_header['origin_date'] = obs.time.day metar_header['origin_hours'] = obs.time.isoformat()[11:13] metar_header['origin_minutes'] = obs.time.isoformat()[14:16] print metar_header
def process_data(self): try: self.log.info('Processing Metar data...') with open(os.path.join(os.path.dirname(__file__), 'metar/stations.json')) as in_file: icao = json.load(in_file) now = arrow.utcnow() hour = now.hour minute = now.minute if minute < 45: current_cycle = hour else: current_cycle = hour + 1 % 24 stations = {} for cycle in (current_cycle-1, current_cycle): file = f'http://tgftp.nws.noaa.gov/data/observations/metar/cycles/{cycle:02d}Z.TXT' self.log.info(f"Processing '{file}' ...") request = requests.get(file, stream=True, timeout=(self.connect_timeout, self.read_timeout)) for line in request.iter_lines(): if line: data = line.decode('iso-8859-1') try: # Is this line a date with format "2017/05/12 23:55" ? arrow.get(data, 'YYYY/MM/DD HH:mm') continue # Catch also ValueError because https://github.com/crsmithdev/arrow/issues/535 except (arrow.parser.ParserError, ValueError): try: metar = Metar(data, strict=False) # wind_dir could be NONE if 'dir' is 'VRB' if metar.wind_speed: if metar.station_id not in stations: stations[metar.station_id] = {} key = arrow.get(metar.time).timestamp stations[metar.station_id][key] = metar except Exception as e: self.log.warn(f'Error while parsing METAR data: {e}') continue for metar_id in stations: metar = next(iter(stations[metar_id].values())) try: name, short_name, default_name, lat, lon, altitude, tz = None, None, None, None, None, None, None checkwx_key = f'metar/checkwx/{metar.station_id}' if not self.redis.exists(checkwx_key): try: self.log.info('Calling api.checkwx.com...') request = requests.get( f'https://api.checkwx.com/station/{metar.station_id}', headers={'Accept': 'application/json', 'X-API-Key': self.checkwx_api_key}, timeout=(self.connect_timeout, self.read_timeout) ) if request.status_code == 401: raise UsageLimitException(request.json()['errors'][0]['message']) elif request.status_code == 429: raise UsageLimitException('api.checkwx.com rate limit exceeded') try: checkwx_data = request.json()['data'][0] if 'icao' not in checkwx_data: raise ProviderException('Invalid CheckWX data') except (ValueError, KeyError): checkwx_json = request.json() messages = [] if type(checkwx_json['data']) is list: messages.extend(checkwx_json['data']) else: messages.append(checkwx_json['data']) raise ProviderException(f'CheckWX API error: {",".join(messages)}') self.add_redis_key(checkwx_key, { 'data': json.dumps(checkwx_data), 'date': arrow.now().format('YYYY-MM-DD HH:mm:ssZZ'), }, self.checkwx_cache_duration) except TimeoutError as e: raise e except UsageLimitException as e: self.add_redis_key(checkwx_key, { 'error': repr(e), 'date': arrow.now().format('YYYY-MM-DD HH:mm:ssZZ'), }, self.usage_limit_cache_duration) except Exception as e: if not isinstance(e, ProviderException): self.log.exception('Error while getting CheckWX data') else: self.log.warn(f'Error while getting CheckWX data: {e}') self.add_redis_key(checkwx_key, { 'error': repr(e), 'date': arrow.now().format('YYYY-MM-DD HH:mm:ssZZ'), }, self.checkwx_cache_duration) if not self.redis.hexists(checkwx_key, 'error'): checkwx_data = json.loads(self.redis.hget(checkwx_key, 'data')) station_type = checkwx_data.get('type', None) if station_type: name = f'{checkwx_data["name"]} {station_type}' else: name = checkwx_data['name'] city = checkwx_data.get('city', None) if city: if station_type: short_name = f'{city} {station_type}' else: short_name = city else: default_name = checkwx_data['name'] lat = checkwx_data['latitude']['decimal'] lon = checkwx_data['longitude']['decimal'] tz = checkwx_data['timezone']['tzid'] elevation = checkwx_data.get('elevation', None) altitude = None if elevation: if 'meters' in elevation: altitude = Q_(elevation['meters'], ureg.meters) elif 'feet' in elevation: altitude = Q_(elevation['feet'], ureg.feet) if metar.station_id in icao: lat = lat or icao[metar.station_id]['lat'] lon = lon or icao[metar.station_id]['lon'] default_name = default_name or icao[metar.station_id]['name'] station = self.save_station( metar.station_id, short_name, name, lat, lon, Status.GREEN, altitude=altitude, tz=tz, url=os.path.join(self.provider_url, f'site?id={metar.station_id}&db=metar'), default_name=default_name or f'{metar.station_id} Airport', lookup_name=f'{metar.station_id} Airport ICAO') station_id = station['_id'] if metar.station_id not in icao: self.log.warn(f"Missing '{metar.station_id}' ICAO in database. Is it '{station['name']}'?") measures_collection = self.measures_collection(station_id) new_measures = [] for key in stations[metar_id]: metar = stations[metar_id][key] if not self.has_measure(measures_collection, key): temp = self.get_quantity(metar.temp, self.temperature_units) dew_point = self.get_quantity(metar.dewpt, self.temperature_units) humidity = self.compute_humidity(dew_point, temp) measure = self.create_measure( station, key, self.get_direction(metar.wind_dir), self.get_quantity(metar.wind_speed, self.speed_units), self.get_quantity(metar.wind_gust or metar.wind_speed, self.speed_units), temperature=temp, humidity=humidity, pressure=Pressure(qfe=None, qnh=self.get_quantity(metar.press, self.pressure_units), qff=self.get_quantity(metar.press_sea_level, self.pressure_units)) ) new_measures.append(measure) self.insert_new_measures(measures_collection, station, new_measures) except ProviderException as e: self.log.warn(f"Error while processing station '{metar_id}': {e}") except Exception as e: self.log.exception(f"Error while processing station '{metar_id}': {e}") except Exception as e: self.log.exception(f'Error while processing Metar: {e}') self.log.info('Done !')
#code='METAR COR VABB 310040Z 08008KT 3000 HZ NSC 25/18 Q1011 NOSIG=' #code='METAR LEMD 222200Z 35007KT 310V020 CAVOK 27/10 Q1015 NOSIG=' #code='METAR UMMM 151230Z 08004MPS 9999 FEW025 OVC100 07/00 Q1041 R12/CLRD60 NOSIG RMK QFE760' #code='METAR UMMS 151230Z 01002MPS 010V070 CAVOK 09/00 Q1042 R13/CLRD// NOSIG' # code='METAR EYKA 151250Z 10007KT 060V130 CAVOK 09/00 Q1042 R08/090095' #code='METAR UMMS 151230Z 01002MPS 010V070 CAVOK 09/00 Q1042 R88CLRD95 NOSIG' #code='METAR UMMS 151230Z 01002MPS 010V070 CAVOK 09/00 Q1042 R24CLRD93 NOSIG' #code='METAR UMMS 151230Z 01002MPS 010V070 CAVOK 09/00 Q1042 R99/421594 NOSIG' #code='METAR UMMS 151230Z 01002MPS 010V070 CAVOK 09/00 Q1042 R99/SNOCLO NOSIG' #code='METAR LTAJ 151220Z 24012KT 9999 SCT040 BKN100 14/04 Q1017 TEMPO Q1019 TEMPO 27015G25KT -TSRA' code='SPECI KBTV 151208Z 36007KT 1SM R15/P6000FT -SN BR BKN006 OVC019 M01/M03 A2965' print "-----------------------------------------------------------------------" print "METAR: ",code print "-----------------------------------------------------------------------" # Initialize a Metar object with the coded report obs = Metar(code) print obs.string() print obs.json() #print obs.string().split('\n') metar_header = {} metar_header['ICAO_code'] = obs.station_id metar_header['origin_time'] = obs.time.isoformat(' ') metar_header['origin_date'] = obs.time.day metar_header['origin_hours'] = obs.time.isoformat()[11:13] metar_header['origin_minutes'] = obs.time.isoformat()[14:16] print metar_header
def __init__(self, text, **kwargs): """Wrapper""" Metar.__init__(self, text, **kwargs) self.iemid = None self.network = None
def process_metar(mstr, now): """ Do the METAR Processing """ mtr = None while mtr is None: try: mtr = Metar(mstr, now.month, now.year) except MetarParserError as exp: try: msg = str(exp) except Exception as exp: return None tokens = ERROR_RE.findall(str(exp)) orig_mstr = mstr if tokens: for token in tokens[0].split(): mstr = mstr.replace(" %s" % (token, ), "") if orig_mstr == mstr: print("Can't fix badly formatted metar: " + mstr) return None else: print("MetarParserError: "+msg) return None except Exception as exp: print("Double Fail: %s %s" % (mstr, exp)) return None if mtr is None or mtr.time is None: return None ob = OB() ob.metar = mstr[:254] ob.valid = now if mtr.temp: ob.tmpf = mtr.temp.value("F") if mtr.dewpt: ob.dwpf = mtr.dewpt.value("F") if mtr.wind_speed: ob.sknt = mtr.wind_speed.value("KT") if mtr.wind_gust: ob.gust = mtr.wind_gust.value("KT") if mtr.wind_dir and mtr.wind_dir.value() != "VRB": ob.drct = mtr.wind_dir.value() if mtr.vis: ob.vsby = mtr.vis.value("SM") # see pull request #38 if mtr.press and mtr.press != mtr.press_sea_level: ob.alti = mtr.press.value("IN") if mtr.press_sea_level: ob.mslp = mtr.press_sea_level.value("MB") if mtr.precip_1hr: ob.p01i = mtr.precip_1hr.value("IN") # Do something with sky coverage for i in range(len(mtr.sky)): (c, h, _) = mtr.sky[i] setattr(ob, 'skyc%s' % (i+1), c) if h is not None: setattr(ob, 'skyl%s' % (i+1), h.value("FT")) if mtr.max_temp_6hr: ob.max_tmpf_6hr = mtr.max_temp_6hr.value("F") if mtr.min_temp_6hr: ob.min_tmpf_6hr = mtr.min_temp_6hr.value("F") if mtr.max_temp_24hr: ob.max_tmpf_24hr = mtr.max_temp_24hr.value("F") if mtr.min_temp_24hr: ob.min_tmpf_6hr = mtr.min_temp_24hr.value("F") if mtr.precip_3hr: ob.p03i = mtr.precip_3hr.value("IN") if mtr.precip_6hr: ob.p06i = mtr.precip_6hr.value("IN") if mtr.precip_24hr: ob.p24i = mtr.precip_24hr.value("IN") # Presentwx if mtr.weather: pwx = [] for x in mtr.weather: pwx.append(("").join([a for a in x if a is not None])) ob.presentwx = (",".join(pwx))[:24] return ob
#code = 'METAR SVVA 092200Z 36004KT 9999 -DZ BKN016' #code = 'LEMD 111230Z 35006KT 250V030 CAVOK 28/03 Q1019 NOSIG' #code='METAR SVVA 092200Z 36004KT 9999 -DZ BKN016 30/21 Q1011' #code = 'METAR CBBC 120000Z AUTO 32005KT 9SM OVC060 19/05 A3032 RMK SLP269' #code = 'KMMU 112345Z 25007KT 10SM SKC 24/21 A2986' code = 'METAR CYLK 120000Z 25008KT 15SM BKN080 08/01 A2995 RMK AC5 9.0/3.5/0/NIL/LAST OBS/NXT 121400Z SLP152' print "-----------------------------------------------------------------------" print "METAR: ",code print "-----------------------------------------------------------------------" # Initialize a Metar object with the coded report obs = Metar(code) # Print the individual data # The 'station_id' attribute is a string. print "station: %s" % obs.station_id if obs.type: print "type: %s" % obs.report_type() # The 'time' attribute is a datetime object if obs.time: print "time: %s" % obs.time.ctime() # The 'temp' and 'dewpt' attributes are temperature objects if obs.temp:
def loopRun(self): # Get sim data. self.getPyuipcData() # Get best suitable Airport. self.getAirport() # Handle if no airport found. if self.airport is None: self.logger.info( 'No airport found, sleeping for {} seconds...'.format( self.SLEEP_TIME)) return self.SLEEP_TIME else: self.logger.info('Airport: {}.'.format(self.airport)) # Get whazzup file if not self.debug: self.getWhazzupText() else: self.getWhazzupTextDebug() # Read whazzup text and get a station. self.parseWhazzupText() # Check if station online. if self.atisRaw is not None: self.logger.info('Station found, decoding Atis.') else: # Actions, if no station online. self.logger.info('No station online, using metar only.') with warnings.catch_warnings(): warnings.simplefilter("ignore") self.metar = Metar(self.getAirportMetar(), strict=False) self.parseVoiceMetar() # Parse atis voice with metar only. self.atisVoice = '{}, {}.'.format( self.airportInfos[self.airport][3], self.metarVoice) # Read the metar. self.readVoice() return self.SLEEP_TIME # Parse ATIS. # Information. self.getInfoIdentifier() self.parseVoiceInformation() # Metar. if not self.ivac2: self.parseMetar(self.atisRaw[2].strip()) else: for ar in self.atisRaw: if ar.startswith('METAR'): self.parseMetar(ar.replace('METAR ', '').strip()) break self.parseVoiceMetar() # Runways / TRL / TA self.parseRawRwy() self.parseVoiceRwy() # comment. self.parseVoiceComment() # Compose complete atis voice string. self.atisVoice = '{} {} {} {} Information {}, out.'.format( self.informationVoice, self.rwyVoice, self.commentVoice, self.metarVoice, self.informationIdentifier) # Read the string. self.readVoice() # After successful reading. return 0
def process_metar(mstr, now): """ Do the METAR Processing """ mtr = None while mtr is None: try: mtr = Metar(mstr, now.month, now.year) except MetarParserError as exp: try: msg = str(exp) except Exception as exp: return None tokens = ERROR_RE.findall(str(exp)) orig_mstr = mstr if tokens: for token in tokens[0].split(): mstr = mstr.replace(" %s" % (token, ), "") if orig_mstr == mstr: print("Can't fix badly formatted metar: " + mstr) return None else: print("MetarParserError: " + msg) return None except Exception as exp: print("Double Fail: %s %s" % (mstr, exp)) return None if mtr is None or mtr.time is None: return None ob = OB() ob.metar = mstr[:254] ob.valid = now if mtr.temp: ob.tmpf = mtr.temp.value("F") if mtr.dewpt: ob.dwpf = mtr.dewpt.value("F") if mtr.wind_speed: ob.sknt = mtr.wind_speed.value("KT") if mtr.wind_gust: ob.gust = mtr.wind_gust.value("KT") # Calc some stuff if ob.tmpf is not None and ob.dwpf is not None: ob.relh = relative_humidity_from_dewpoint( ob.tmpf * units('degF'), ob.dwpf * units('degF')).to(units('percent')).magnitude if ob.sknt is not None: ob.feel = apparent_temperature(ob.tmpf * units('degF'), ob.relh * units('percent'), ob.sknt * units('knots')).to( units('degF')).magnitude if mtr.wind_dir and mtr.wind_dir.value() != "VRB": ob.drct = mtr.wind_dir.value() if mtr.vis: ob.vsby = mtr.vis.value("SM") # see pull request #38 if mtr.press and mtr.press != mtr.press_sea_level: ob.alti = mtr.press.value("IN") if mtr.press_sea_level: ob.mslp = mtr.press_sea_level.value("MB") if mtr.precip_1hr: ob.p01i = mtr.precip_1hr.value("IN") # Do something with sky coverage for i in range(len(mtr.sky)): (c, h, _) = mtr.sky[i] setattr(ob, 'skyc%s' % (i + 1), c) if h is not None: setattr(ob, 'skyl%s' % (i + 1), h.value("FT")) if mtr.max_temp_6hr: ob.max_tmpf_6hr = mtr.max_temp_6hr.value("F") if mtr.min_temp_6hr: ob.min_tmpf_6hr = mtr.min_temp_6hr.value("F") if mtr.max_temp_24hr: ob.max_tmpf_24hr = mtr.max_temp_24hr.value("F") if mtr.min_temp_24hr: ob.min_tmpf_6hr = mtr.min_temp_24hr.value("F") if mtr.precip_3hr: ob.p03i = mtr.precip_3hr.value("IN") if mtr.precip_6hr: ob.p06i = mtr.precip_6hr.value("IN") if mtr.precip_24hr: ob.p24i = mtr.precip_24hr.value("IN") # Presentwx if mtr.weather: pwx = [] for wx in mtr.weather: val = "".join([a for a in wx if a is not None]) if val == "" or val == len(val) * "/": continue pwx.append(val) ob.wxcodes = pwx return ob
def parseMetar(self, metarString): with warnings.catch_warnings(): warnings.simplefilter("ignore") self.metar = Metar(metarString, strict=False)
class VoiceAtis(object): STATION_SUFFIXES = ['TWR', 'APP', 'GND', 'DEL', 'DEP'] SPEECH_RATE = 150 SLEEP_TIME = 3 # s RADIO_RANGE = 180 # nm OFFSETS = [ (0x034E, 'H'), # com1freq (0x3118, 'H'), # com2freq (0x3122, 'b'), # radioActive (0x0560, 'l'), # ac Latitude (0x0568, 'l'), # ac Longitude ] WHAZZUP_URL = 'http://api.ivao.aero/getdata/whazzup/whazzup.txt.gz' WHAZZUP_METAR_URL = 'http://wx.ivao.aero/metar.php' OUR_AIRPORTS_URL = 'http://ourairports.com/data/' COM1_FREQUENCY_DEBUG = 199.99 # EDDS # COM2_FREQUENCY_DEBUG = 126.12 # LAT_DEBUG = 48.687 # LON_DEBUG = 9.205 # EDDM # COM2_FREQUENCY_DEBUG = 123.12 # LAT_DEBUG = 48.353 # LON_DEBUG = 11.786 # LIRF COM2_FREQUENCY_DEBUG = 121.85 LAT_DEBUG = 41.8 LON_DEBUG = 12.2 # LIBR COM2_FREQUENCY_DEBUG = 121.85 LAT_DEBUG = 41.8 LON_DEBUG = 12.2 WHAZZUP_TEXT_DEBUG = r'H:\My Documents\Sonstiges\voiceAtis\whazzup_1.txt' ## Setup the VoiceAtis object. # Inits logger. # Downloads airport data. def __init__(self, **optional): #TODO: Remove the debug code when tested properly. #TODO: Improve logged messages. #TODO: Create GUI. # Process optional arguments. self.debug = optional.get('Debug', debug) # Get file path. self.rootDir = os.path.dirname( os.path.dirname(os.path.abspath(__file__))) # Init logging. self.logger = VaLogger(os.path.join(self.rootDir, 'voiceAtis', 'logs')) # First log message. self.logger.info('voiceAtis started') # Read file with airport frequencies and coordinates. self.logger.info('Downloading airport data. This may take some time.') self.getAirportData() self.logger.info('Finished downloading airport data.') # Show debug Info #TODO: Remove for release. if self.debug: self.logger.info('Debug mode on.') self.logger.setLevel(ConsoleLevel='debug') ## Establishs pyuipc connection. # Return 'True' on success or if pyuipc not installed. # Return 'False' on fail. def connectPyuipc(self): try: self.pyuipcConnection = pyuipc.open(0) self.pyuipcOffsets = pyuipc.prepare_data(self.OFFSETS) self.logger.info('FSUIPC connection established.') return True except NameError: self.pyuipcConnection = None self.logger.warning( 'Error using PYUIPC, running voiceAtis without it.') return True except: self.logger.warning( 'FSUIPC: No simulator detected. Start you simulator first!') return False ## Runs an infinite loop. # i.E. for use without GUI. def runLoop(self): # Establish pyuipc connection result = False while not result: result = self.connectPyuipc() if not result: self.logger.info('Retrying in 20 seconds.') time.sleep(20) # Infinite loop. try: while True: timeSleep = self.loopRun() time.sleep(timeSleep) except KeyboardInterrupt: # Actions at Keyboard Interrupt. self.logger.info('Loop interrupted by user.') if pyuipcImported: self.pyuipc.close() ## One cyle of a loop. # Returns the requested sleep time. def loopRun(self): # Get sim data. self.getPyuipcData() # Get best suitable Airport. self.getAirport() # Handle if no airport found. if self.airport is None: self.logger.info( 'No airport found, sleeping for {} seconds...'.format( self.SLEEP_TIME)) return self.SLEEP_TIME else: self.logger.info('Airport: {}.'.format(self.airport)) # Get whazzup file if not self.debug: self.getWhazzupText() else: self.getWhazzupTextDebug() # Read whazzup text and get a station. self.parseWhazzupText() # Check if station online. if self.atisRaw is not None: self.logger.info('Station found, decoding Atis.') else: # Actions, if no station online. self.logger.info('No station online, using metar only.') with warnings.catch_warnings(): warnings.simplefilter("ignore") self.metar = Metar(self.getAirportMetar(), strict=False) self.parseVoiceMetar() # Parse atis voice with metar only. self.atisVoice = '{}, {}.'.format( self.airportInfos[self.airport][3], self.metarVoice) # Read the metar. self.readVoice() return self.SLEEP_TIME # Parse ATIS. # Information. self.getInfoIdentifier() self.parseVoiceInformation() # Metar. if not self.ivac2: self.parseMetar(self.atisRaw[2].strip()) else: for ar in self.atisRaw: if ar.startswith('METAR'): self.parseMetar(ar.replace('METAR ', '').strip()) break self.parseVoiceMetar() # Runways / TRL / TA self.parseRawRwy() self.parseVoiceRwy() # comment. self.parseVoiceComment() # Compose complete atis voice string. self.atisVoice = '{} {} {} {} Information {}, out.'.format( self.informationVoice, self.rwyVoice, self.commentVoice, self.metarVoice, self.informationIdentifier) # Read the string. self.readVoice() # After successful reading. return 0 ## Downloads and reads the whazzup from IVAO def getWhazzupText(self): urllib.urlretrieve(self.WHAZZUP_URL, 'whazzup.txt.gz') with gzip.open('whazzup.txt.gz', 'rb') as f: self.whazzupText = f.read().decode('iso-8859-15') os.remove('whazzup.txt.gz') ## Reads a whazzup file on disk. # For debug purposes. def getWhazzupTextDebug(self): with open(self.WHAZZUP_TEXT_DEBUG) as whazzupFile: self.whazzupText = whazzupFile.read() pass ## Find a station of the airport and read the ATIS string. def parseWhazzupText(self): # Find an open station for st in self.STATION_SUFFIXES: matchObj = re.search('{}\w*?_{}'.format(self.airport, st), self.whazzupText) if matchObj is not None: break if matchObj is not None: # Extract ATIS. lineStart = matchObj.start() lineEnd = self.whazzupText.find('\n', matchObj.start()) stationInfo = self.whazzupText[lineStart:lineEnd].split(':') self.ivac2 = bool(int(stationInfo[39][0]) - 1) self.atisTextRaw = stationInfo[35].encode('iso-8859-15') self.atisRaw = stationInfo[35].encode('iso-8859-15').split('^§') else: self.atisRaw = None def parseMetar(self, metarString): with warnings.catch_warnings(): warnings.simplefilter("ignore") self.metar = Metar(metarString, strict=False) ## Parse runway and transition data. # Get active runways for arrival and departure. # Get transistion level and altitude. def parseRawRwy(self): self.rwyInformation = [None, None, None, None] if not self.ivac2: strSplit = self.atisRaw[3].split(' / ') for sp in strSplit: # ARR. if sp[0:3] == 'ARR': self.rwyInformation[0] = [] arr = sp.replace('ARR RWY ', '').strip() starts = [] for ma in re.finditer('\d{2}[RLC]?', arr): starts.append(ma.start()) for st in range(len(starts)): if st < len(starts) - 1: rwy = arr[starts[st]:starts[st + 1]] else: rwy = arr[starts[st]:] curRwy = [rwy[0:2], None, None, None] if 'L' in rwy: curRwy[1] = 'Left' if 'C' in rwy: curRwy[2] = 'Center' if 'R' in rwy: curRwy[3] = 'Right' self.rwyInformation[0].append(curRwy) # DEP. elif sp[0:3] == 'DEP': self.rwyInformation[1] = [] dep = sp.replace('DEP RWY ', '').strip() starts = [] for ma in re.finditer('\d{2}[RLC]?', dep): starts.append(ma.start()) for st in range(len(starts)): if st < len(starts) - 1: rwy = dep[starts[st]:starts[st + 1]] else: rwy = dep[starts[st]:] curRwy = [rwy[0:2], None, None, None] if 'L' in rwy: curRwy[1] = 'Left' if 'C' in rwy: curRwy[2] = 'Center' if 'R' in rwy: curRwy[3] = 'Right' self.rwyInformation[1].append(curRwy) # TRL/TA elif sp[0:3] == 'TRL': self.rwyInformation[2] = sp.strip().replace('TRL FL', '') elif sp[0:2] == 'TA': self.rwyInformation[3] = sp.strip().replace('TA ', '').replace( 'FT', '') # Ivac 2 else: for ar in self.atisRaw: if ar.startswith('TA'): trlTaSplit = ar.split(' / ') self.rwyInformation[3] = trlTaSplit[0].replace('TA ', '') self.rwyInformation[2] = trlTaSplit[1].replace('TRL', '') elif ar.startswith('ARR'): curRwy = [ar[8:10], None, None, None] if 'L' in ar[8:]: curRwy[1] = 'Left' if 'C' in ar[8:]: curRwy[2] = 'Center' if 'R' in ar[8:]: curRwy[3] = 'Right' if self.rwyInformation[0] is None: self.rwyInformation[0] = [curRwy] else: self.rwyInformation[0].append(curRwy) elif ar.startswith('DEP'): curRwy = [ar[8:10], None, None, None] if 'L' in ar[8:]: curRwy[1] = 'Left' if 'C' in ar[8:]: curRwy[2] = 'Center' if 'R' in ar[8:]: curRwy[3] = 'Right' if self.rwyInformation[1] is None: self.rwyInformation[1] = [curRwy] else: self.rwyInformation[1].append(curRwy) ## Generate a string of the metar for voice generation. def parseVoiceMetar(self): self.metarVoice = 'Met report' # Time hours = parseVoiceInt('{:02d}'.format(self.metar._hour)) minutes = parseVoiceInt('{:02d}'.format(self.metar._min)) self.metarVoice = '{} time {} {}'.format(self.metarVoice, hours, minutes) # Wind if self.metar.wind_speed._value != 0: if self.metar.wind_dir is not None: self.metarVoice = '{}, wind {}, {}'.format( self.metarVoice, parseVoiceString(self.metar.wind_dir.string()), parseVoiceString(self.metar.wind_speed.string())) else: self.metarVoice = '{}, wind variable, {}'.format( self.metarVoice, parseVoiceString(self.metar.wind_speed.string())) else: self.metarVoice = '{}, wind calm'.format( self.metarVoice, self.metar.wind_dir.string(), self.metar.wind_speed.string()) if self.metar.wind_gust is not None: self.metarVoice = '{}, maximum {}'.format( self.metarVoice, parseVoiceString(self.metar.wind_gust.string())) if self.metar.wind_dir_from is not None: self.metarVoice = '{}, variable between {} and {}'.format( self.metarVoice, parseVoiceString(self.metar.wind_dir_from.string()), parseVoiceString(self.metar.wind_dir_to.string())) # Visibility. #TODO: implement directions self.metarVoice = '{}, visibility {}'.format(self.metarVoice, self.metar.vis.string()) # runway visual range rvr = self.metar.runway_visual_range().replace(';', ',') if rvr: rvrNew = '' lastEnd = 0 rvrPattern = re.compile('[0123]\d[LCR]?(?=,)') for ma in rvrPattern.finditer(rvr): rwyRaw = rvr[ma.start():ma.end()] rwyStr = parseVoiceInt(rwyRaw[0:2]) if len(rwyRaw) > 2: if rwyRaw[2] == 'L': rwyStr = '{} left'.format(rwyStr) elif rwyRaw[2] == 'C': rwyStr = '{} center'.format(rwyStr) elif rwyRaw[2] == 'R': rwyStr = '{} right'.format(rwyStr) rvrNew = '{}{}{}'.format(rvrNew, rvr[lastEnd:ma.start()], rwyStr) lastEnd = ma.end() rvrNew = '{}{}'.format(rvrNew, rvr[lastEnd:]) self.metarVoice = '{}, visual range {}'.format( self.metarVoice, rvrNew) # weather phenomena if self.metar.weather: self.metarVoice = '{}, {}'.format( self.metarVoice, self.metar.present_weather().replace(';', ',')) # clouds if self.metar.sky: self.metarVoice = '{}, {}'.format( self.metarVoice, self.metar.sky_conditions(',').replace(',', ', ').replace( 'a few', 'few')) elif 'CAVOK' in self.metar.code: self.metarVoice = '{}, clouds and visibility ok'.format( self.metarVoice) # runway condition #TODO: Implement runway conditions # Not implemented in python-metar # temperature tempValue = parseVoiceInt(str(int(self.metar.temp._value))) if self.metar.temp._units == 'C': tempUnit = 'degree Celsius' else: tempUnit = 'degree Fahrenheit' self.metarVoice = '{}, temperature {} {}'.format( self.metarVoice, tempValue, tempUnit) # dew point dewptValue = parseVoiceInt(str(int(self.metar.dewpt._value))) if self.metar.dewpt._units == 'C': dewptUnit = 'degree Celsius' else: dewptUnit = 'degree Fahrenheit' self.metarVoice = '{}, dew point {} {}'.format(self.metarVoice, dewptValue, dewptUnit) # QNH if self.metar.press._units == 'MB': pressValue = parseVoiceInt(str(int(self.metar.press._value))) self.metarVoice = '{}, Q N H {} hectopascal'.format( self.metarVoice, pressValue) else: self.metarVoice = '{}, Altimeter {}'.format( self.metarVoice, parseVoiceString(self.metar.press.string())) #TODO: implement trend self.metarVoice = '{},'.format(self.metarVoice) ## Generate a string of the information identifier for voice generation. def parseVoiceInformation(self): if not self.ivac2: timeMatch = re.search(r'\d{4}z', self.atisRaw[1]) startInd = timeMatch.start() endInd = timeMatch.end() - 1 timeStr = parseVoiceInt(self.atisRaw[1][startInd:endInd]) self.informationVoice = '{} {}.'.format( self.atisRaw[1][0:startInd - 1], timeStr) else: information = self.atisRaw[1].split(' ') airport = information[0] airport = self.airportInfos[airport][3] time = parseVoiceInt(information[4][0:4]) self.informationVoice = '{} Information {} recorded at {}.'.format( airport, self.informationIdentifier, time) ## Generate a string of the runway information for voice generation. def parseVoiceRwy(self): self.rwyVoice = '' # ARR. if self.rwyInformation[0] is not None: self.rwyVoice = '{}Arrival runway '.format(self.rwyVoice) for arr in self.rwyInformation[0]: if arr[1:4].count(None) == 3: self.rwyVoice = '{}{} and '.format(self.rwyVoice, parseVoiceInt(arr[0])) else: for si in arr[1:4]: if si is not None: self.rwyVoice = '{}{} {} and '.format( self.rwyVoice, parseVoiceInt(arr[0]), si) self.rwyVoice = '{},'.format(self.rwyVoice[0:-5]) # DEP. if self.rwyInformation[1] is not None: self.rwyVoice = '{} Departure runway '.format(self.rwyVoice) for dep in self.rwyInformation[1]: if dep[1:4].count(None) == 3: self.rwyVoice = '{}{} and '.format(self.rwyVoice, parseVoiceInt(dep[0])) else: for si in dep[1:4]: if si is not None: self.rwyVoice = '{}{} {} and '.format( self.rwyVoice, parseVoiceInt(dep[0]), si) self.rwyVoice = '{}, '.format(self.rwyVoice[0:-5]) # TRL if self.rwyInformation[2] is not None: self.rwyVoice = '{}Transition level {}, '.format( self.rwyVoice, parseVoiceInt(self.rwyInformation[2])) # TA if self.rwyInformation[3] is not None: self.rwyVoice = '{}Transition altitude {} feet,'.format( self.rwyVoice, self.rwyInformation[3]) ## Generate a string of ATIS comment for voice generation. def parseVoiceComment(self): if not self.ivac2: self.commentVoice = '{},'.format(parseVoiceString(self.atisRaw[4])) else: self.commentVoice = '' ## Reads the atis string using voice generation. def readVoice(self): # Init currently Reading with None. self.currentlyReading = None self.logger.debug('Voice Text is: {}'.format(self.atisVoice)) if pyttsxImported: # Set properties currently reading self.currentlyReading = self.airport # Init voice engine. self.engine = pyttsx.init() # Set properties. voices = self.engine.getProperty('voices') for vo in voices: if 'english' in vo.name.lower(): self.engine.setProperty('voice', vo.id) self.logger.debug('Using voice: {}'.format(vo.name)) break self.engine.setProperty('rate', self.SPEECH_RATE) # Start listener and loop. self.engine.connect('started-word', self.onWord) # Say complete ATIS self.engine.say(self.atisVoice) self.logger.info('Start reading.') self.engine.runAndWait() self.logger.info('Reading finished.') self.engine = None else: self.logger.warning( 'Speech engine not initalized, no reading. Sleeping for {} seconds...' .format(self.SLEEP_TIME)) time.sleep(self.SLEEP_TIME) ## Callback for stop of reading. # Stops reading if frequency change/com deactivation/out of range. def onWord(self, name, location, length): # @UnusedVariable self.getPyuipcData() self.getAirport() if self.airport != self.currentlyReading: self.engine.stop() self.currentlyReading = None ## Reads current frequency and COM status. def getPyuipcData(self): if pyuipcImported: results = pyuipc.read(self.pyuipcOffsets) # frequency hexCode = hex(results[0])[2:] self.com1frequency = float('1{}.{}'.format(hexCode[0:2], hexCode[2:])) hexCode = hex(results[1])[2:] self.com2frequency = float('1{}.{}'.format(hexCode[0:2], hexCode[2:])) # radio active #TODO: Test accuracy of this data (with various planes and sims) radioActiveBits = list(map(int, '{0:08b}'.format(results[2]))) if radioActiveBits[2]: self.com1active = True self.com2active = True elif radioActiveBits[0]: self.com1active = True self.com2active = False elif radioActiveBits[1]: self.com1active = False self.com2active = True else: self.com1active = False self.com2active = False # lat lon self.lat = results[3] * (90.0 / (10001750.0 * 65536.0 * 65536.0)) self.lon = results[4] * (360.0 / (65536.0 * 65536.0 * 65536.0 * 65536.0)) else: self.com1frequency = self.COM1_FREQUENCY_DEBUG self.com2frequency = self.COM2_FREQUENCY_DEBUG self.com1active = True self.com2active = True self.lat = self.LAT_DEBUG self.lon = self.LON_DEBUG # Logging. if self.com1active: com1activeStr = 'active' else: com1activeStr = 'inactive' if self.com2active: com2activeStr = 'active' else: com2activeStr = 'inactive' self.logger.debug('COM 1: {} ({}), COM 2: {} ({})'.format( self.com1frequency, com1activeStr, self.com2frequency, com2activeStr)) # self.logger.debug('COM 1 active: {}, COM 2 active: {}'.format(self.com1active,self.com2active)) ## Determine if there is an airport aplicable for ATIS reading. def getAirport(self): self.airport = None frequencies = [] if self.com1active: frequencies.append(self.com1frequency) if self.com2active: frequencies.append(self.com2frequency) if frequencies: distanceMin = self.RADIO_RANGE + 1 for ap in self.airportInfos: distance = gcDistanceNm(self.lat, self.lon, self.airportInfos[ap][1], self.airportInfos[ap][2]) if ( floor(self.airportInfos[ap][0] * 100) / 100 ) in frequencies and distance < self.RADIO_RANGE and distance < distanceMin: distanceMin = distance self.airport = ap ## Read data of airports from a given file. def getAirportDataFile(self, apFile): # Check if file exists. if not os.path.isfile(apFile): self.logger.warning('No such file: {}'.format(apFile)) return # Read the file. with open(apFile) as aptInfoFile: for li in aptInfoFile: lineSplit = re.split('[,;]', li) if not li.startswith('#') and len(lineSplit) == 5: self.airportInfos[lineSplit[0].strip()] = (float( lineSplit[1]), float(lineSplit[2]), float( lineSplit[3]), lineSplit[4].replace('\n', '')) ## Read data of airports from http://ourairports.com. def getAirportDataWeb(self): airportFreqs = {} # Read the file with frequency. with closing( urllib2.urlopen(self.OUR_AIRPORTS_URL + 'airport-frequencies.csv', timeout=5)) as apFreqFile: for li in apFreqFile: lineSplit = li.split(',') if lineSplit[3] == '"ATIS"': airportFreqs[lineSplit[2].replace('"', '')] = float( lineSplit[-1].replace('\n', '')) # Read the file with other aiport data. # Add frequency and write them to self. airportInfos. with closing(urllib2.urlopen(self.OUR_AIRPORTS_URL + 'airports.csv')) as apFile: for li in apFile: lineSplit = re.split((",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"), li) apCode = lineSplit[1].replace('"', '') if apCode in airportFreqs and len(apCode) <= 4: apFreq = airportFreqs[apCode] if 100.0 < apFreq < 140.0: self.airportInfos[apCode] = [ apFreq, float(lineSplit[4]), float(lineSplit[5]), lineSplit[3].replace('"', '') ] ## Reads airportData from two sources. def getAirportData(self): self.airportInfos = {} try: # Try to read airport data from web. self.getAirportDataWeb() self.getAirportDataFile( os.path.join(self.rootDir, 'airports_add.info')) collectedFromWeb = True except: # If this fails, use the airports from airports.info. self.logger.warning( 'Unable to get airport data from web. Using airports.info. Error: {}' .format(sys.exc_info()[0])) self.airportInfos = {} collectedFromWeb = False try: self.getAirportDataFile( os.path.join(self.rootDir, 'airports.info')) except: self.logger.error( 'Unable to read airport data from airports.info!') # Sort airportInfos and write them to a file for future use if collected from web. if collectedFromWeb: apInfoPath = os.path.join(self.rootDir, 'airports.info') apList = self.airportInfos.keys() apList.sort() with open(apInfoPath, 'w') as apDataFile: for ap in apList: apDataFile.write( '{:>4}; {:6.2f}; {:11.6f}; {:11.6f}; {}\n'.format( ap, self.airportInfos[ap][0], self.airportInfos[ap][1], self.airportInfos[ap][2], self.airportInfos[ap][3])) ## Determines the info identifier of the loaded ATIS. def getInfoIdentifier(self): if not self.ivac2: informationPos = re.search('information ', self.atisRaw[1]).end() informationSplit = self.atisRaw[1][informationPos:].split(' ') self.informationIdentifier = informationSplit[0] else: self.informationIdentifier = CHAR_TABLE[re.findall( r'(?<=ATIS )[A-Z](?= \d{4})', self.atisRaw[1])[0]] ## Retrieves the metar of an airport independet of an ATIS. def getAirportMetar(self): if not debug: urllib.urlretrieve(self.WHAZZUP_METAR_URL, 'whazzup_metar.txt') with open('whazzup_metar.txt', 'r') as metarFile: metarText = metarFile.read() if not debug: os.remove('whazzup_metar.txt') metarStart = metarText.find(self.airport) metarEnd = metarText.find('\n', metarStart) return metarText[metarStart:metarEnd]
def __init__(self, metar_code, month=None, year=None, utc_delta=None): LOGGER.debug(f'creating METAR from: {metar_code}') Metar.__init__(self, metar_code, month, year, utc_delta) self.press = CustomPressure(self.press.value('mb'))