From 48d638d71bdc6628d4041749cfdb59a5b8b63722 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Mon, 7 Nov 2011 23:45:23 -0800 Subject: [PATCH 1/4] more print functions --- CHANGES | 3 + get_report.py | 66 +- metar/Datatypes.py | 826 ++++++++++----------- metar/Metar.py | 1696 ++++++++++++++++++++++---------------------- metar/Station.py | 32 +- misc/getstation.py | 87 +-- parse_metar.py | 99 +-- sample.py | 38 +- setup.py | 4 +- 9 files changed, 1430 insertions(+), 1421 deletions(-) diff --git a/CHANGES b/CHANGES index e1af21e3..102e9f95 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,8 @@ Change Log ========== +metar 1.4.1 +*) fixed indent levels to always be 4 spaces +*) using print() as function instead of command metar 1.4 --------- diff --git a/get_report.py b/get_report.py index 1d5afcb1..f4ecfdc3 100755 --- a/get_report.py +++ b/get_report.py @@ -10,45 +10,47 @@ BASE_URL = "http://weather.noaa.gov/pub/data/observations/metar/stations" def usage(): - program = os.path.basename(sys.argv[0]) - print "Usage: ",program," [ ... ]" - print """Options: - . a four-letter ICAO station code (e.g., "KEWR") -""" - sys.exit(1) + program = os.path.basename(sys.argv[0]) + print("Usage: %s [ ... ]" % program) + options = """Options: + . a four-letter ICAO station code (e.g., "KEWR") + """ + print(options) + sys.exit(1) + stations = [] debug = False try: - opts, stations = getopt.getopt(sys.argv[1:], 'd') - for opt in opts: - if opt[0] == '-d': - debug = True + opts, stations = getopt.getopt(sys.argv[1:], 'd') + for opt in opts: + if opt[0] == '-d': + debug = True except: - usage() - + usage() + if not stations: - usage() + usage() for name in stations: - url = "%s/%s.TXT" % (BASE_URL, name) - if debug: - sys.stderr.write("[ "+url+" ]") - try: - urlh = urllib.urlopen(url) - report = '' - for line in urlh: - if line.startswith(name): - report = line.strip() - obs = Metar.Metar(line) - print obs.string() - break - if not report: - print "No data for ",name,"\n\n" - except Metar.ParserError, err: - print "METAR code: ",line - print string.join(err.args,", "),"\n" - except: - print "Error retrieving",name,"data","\n" + url = "%s/%s.TXT" % (BASE_URL, name) + if debug: + sys.stderr.write("[ "+url+" ]") + try: + urlh = urllib.urlopen(url) + report = '' + for line in urlh: + if line.startswith(name): + report = line.strip() + obs = Metar.Metar(line) + print obs.string() + break + if not report: + print("No data for %s\n\n" % (name,)) + except Metar.ParserError, err: + print("METAR code: "+ line) + print(string.join(err.args,", ") + "\n") + except: + print("Error retrieving %s data\n" % (name,)) diff --git a/metar/Datatypes.py b/metar/Datatypes.py index 73e5391c..5cf180fc 100644 --- a/metar/Datatypes.py +++ b/metar/Datatypes.py @@ -1,7 +1,7 @@ # -# Python classes to represent dimensioned quantities used in weather reports +# Python classes to represent dimensioned quantities used in weather reports # -# Copyright 2004 Tom Pollard +# Copyright 2004 Tom Pollard # import re from math import sin, cos, atan2, sqrt @@ -9,449 +9,449 @@ ## exceptions class UnitsError(Exception): - """Exception raised when unrecognized units are used.""" - pass + """Exception raised when unrecognized units are used.""" + pass ## regexp to match fractions (used by distance class) ## [Note: numerator of fraction must be single digit.] FRACTION_RE = re.compile(r"^((?P\d+)\s*)?(?P\d)/(?P\d+)$") - -## classes representing dimensioned values in METAR reports +## classes representing dimensioned values in METAR reports + class temperature(object): - """A class representing a temperature value.""" - legal_units = [ "F", "C", "K" ] - - def __init__( self, value, units="C" ): - if not units.upper() in temperature.legal_units: - raise UnitsError("unrecognized temperature unit: '"+units+"'") - self._units = units.upper() - try: - self._value = float(value) - except ValueError: - if value.startswith('M'): - self._value = -float(value[1:]) - else: - raise ValueError("temperature must be integer: '"+str(value)+"'") - - def __str__(self): - return self.string() + """A class representing a temperature value.""" + legal_units = [ "F", "C", "K" ] - def value( self, units=None ): - """Return the temperature in the specified units.""" - if units == None: - return self._value - else: - if not units.upper() in temperature.legal_units: - raise UnitsError("unrecognized temperature unit: '"+units+"'") - units = units.upper() - if self._units == "C": - celsius_value = self._value - elif self._units == "F": - celsius_value = (self._value-32.0)/1.8 - elif self._units == "K": - celsius_value = self._value-273.15 - if units == "C": - return celsius_value - elif units == "K": - return 273.15+celsius_value - elif units == "F": - return 32.0+celsius_value*1.8 - - def string( self, units=None ): - """Return a string representation of the temperature, using the given units.""" - if units == None: - units = self._units - else: - if not units.upper() in temperature.legal_units: - raise UnitsError("unrecognized temperature unit: '"+units+"'") - units = units.upper() - val = self.value(units) - if units == "C": - return "%.1f C" % val - elif units == "F": - return "%.1f F" % val - elif units == "K": - return "%.1f K" % val + def __init__( self, value, units="C" ): + if not units.upper() in temperature.legal_units: + raise UnitsError("unrecognized temperature unit: '"+units+"'") + self._units = units.upper() + try: + self._value = float(value) + except ValueError: + if value.startswith('M'): + self._value = -float(value[1:]) + else: + raise ValueError("temperature must be integer: '"+str(value)+"'") -class pressure(object): - """A class representing a barometric pressure value.""" - legal_units = [ "MB", "HPA", "IN" ] - - def __init__( self, value, units="MB" ): - if not units.upper() in pressure.legal_units: - raise UnitsError("unrecognized pressure unit: '"+units+"'") - self._value = float(value) - self._units = units.upper() + def __str__(self): + return self.string() + + def value( self, units=None ): + """Return the temperature in the specified units.""" + if units == None: + return self._value + else: + if not units.upper() in temperature.legal_units: + raise UnitsError("unrecognized temperature unit: '"+units+"'") + units = units.upper() + if self._units == "C": + celsius_value = self._value + elif self._units == "F": + celsius_value = (self._value-32.0)/1.8 + elif self._units == "K": + celsius_value = self._value-273.15 + if units == "C": + return celsius_value + elif units == "K": + return 273.15+celsius_value + elif units == "F": + return 32.0+celsius_value*1.8 + + def string( self, units=None ): + """Return a string representation of the temperature, using the given units.""" + if units == None: + units = self._units + else: + if not units.upper() in temperature.legal_units: + raise UnitsError("unrecognized temperature unit: '"+units+"'") + units = units.upper() + val = self.value(units) + if units == "C": + return "%.1f C" % val + elif units == "F": + return "%.1f F" % val + elif units == "K": + return "%.1f K" % val - def __str__(self): - return self.string() +class pressure(object): + """A class representing a barometric pressure value.""" + legal_units = [ "MB", "HPA", "IN" ] - def value( self, units=None ): - """Return the pressure in the specified units.""" - if units == None: - return self._value - else: - if not units.upper() in pressure.legal_units: - raise UnitsError("unrecognized pressure unit: '"+units+"'") - units = units.upper() - if units == self._units: - return self._value - if self._units == "IN": - mb_value = self._value*33.86398 - else: - mb_value = self._value - if units == "MB" or units == "HPA": - return mb_value - elif units == "IN": - return mb_value/33.86398 - else: - raise UnitsError("unrecognized pressure unit: '"+units+"'") - - def string( self, units=None ): - """Return a string representation of the pressure, using the given units.""" - if not units: - units = self._units - else: - if not units.upper() in pressure.legal_units: - raise UnitsError("unrecognized pressure unit: '"+units+"'") - units = units.upper() - val = self.value(units) - if units == "MB": - return "%.1f mb" % val - elif units == "HPA": - return "%.1f hPa" % val - elif units == "IN": - return "%.2f inches" % val + def __init__( self, value, units="MB" ): + if not units.upper() in pressure.legal_units: + raise UnitsError("unrecognized pressure unit: '"+units+"'") + self._value = float(value) + self._units = units.upper() -class speed(object): - """A class representing a wind speed value.""" - legal_units = [ "KT", "MPS", "KMH", "MPH" ] - legal_gtlt = [ ">", "<" ] - - def __init__( self, value, units=None, gtlt=None ): - if not units: - self._units = "MPS" - else: - if not units.upper() in speed.legal_units: - raise UnitsError("unrecognized speed unit: '"+units+"'") - self._units = units.upper() - if gtlt and not gtlt in speed.legal_gtlt: - raise ValueError("unrecognized greater-than/less-than symbol: '"+gtlt+"'") - self._gtlt = gtlt - self._value = float(value) + def __str__(self): + return self.string() + + def value( self, units=None ): + """Return the pressure in the specified units.""" + if units == None: + return self._value + else: + if not units.upper() in pressure.legal_units: + raise UnitsError("unrecognized pressure unit: '"+units+"'") + units = units.upper() + if units == self._units: + return self._value + if self._units == "IN": + mb_value = self._value*33.86398 + else: + mb_value = self._value + if units == "MB" or units == "HPA": + return mb_value + elif units == "IN": + return mb_value/33.86398 + else: + raise UnitsError("unrecognized pressure unit: '"+units+"'") + + def string( self, units=None ): + """Return a string representation of the pressure, using the given units.""" + if not units: + units = self._units + else: + if not units.upper() in pressure.legal_units: + raise UnitsError("unrecognized pressure unit: '"+units+"'") + units = units.upper() + val = self.value(units) + if units == "MB": + return "%.1f mb" % val + elif units == "HPA": + return "%.1f hPa" % val + elif units == "IN": + return "%.2f inches" % val - def __str__(self): - return self.string() +class speed(object): + """A class representing a wind speed value.""" + legal_units = [ "KT", "MPS", "KMH", "MPH" ] + legal_gtlt = [ ">", "<" ] - def value( self, units=None ): - """Return the pressure in the specified units.""" - if not units: - return self._value - else: - if not units.upper() in speed.legal_units: - raise UnitsError("unrecognized speed unit: '"+units+"'") - units = units.upper() - if units == self._units: - return self._value - if self._units == "KMH": - mps_value = self._value/3.6 - elif self._units == "KT": - mps_value = self._value*0.514444 - elif self._units == "MPH": - mps_value = self._value*0.447000 - else: - mps_value = self._value - if units == "KMH": - return mps_value*3.6 - elif units == "KT": - return mps_value/0.514444 - elif units == "MPH": - return mps_value/0.447000 - elif units == "MPS": - return mps_value - - def string( self, units=None ): - """Return a string representation of the speed in the given units.""" - if not units: - units = self._units - else: - if not units.upper() in speed.legal_units: - raise UnitsError("unrecognized speed unit: '"+units+"'") - units = units.upper() - val = self.value(units) - if units == "KMH": - text = "%.0f km/h" % val - elif units == "KT": - text = "%.0f knots" % val - elif units == "MPH": - text = "%.0f mph" % val - elif units == "MPS": - text = "%.0f mps" % val - if self._gtlt == ">": - text = "greater than "+text - elif self._gtlt == "<": - text = "less than "+text - return text + def __init__( self, value, units=None, gtlt=None ): + if not units: + self._units = "MPS" + else: + if not units.upper() in speed.legal_units: + raise UnitsError("unrecognized speed unit: '"+units+"'") + self._units = units.upper() + if gtlt and not gtlt in speed.legal_gtlt: + raise ValueError("unrecognized greater-than/less-than symbol: '"+gtlt+"'") + self._gtlt = gtlt + self._value = float(value) + + def __str__(self): + return self.string() + + def value( self, units=None ): + """Return the pressure in the specified units.""" + if not units: + return self._value + else: + if not units.upper() in speed.legal_units: + raise UnitsError("unrecognized speed unit: '"+units+"'") + units = units.upper() + if units == self._units: + return self._value + if self._units == "KMH": + mps_value = self._value/3.6 + elif self._units == "KT": + mps_value = self._value*0.514444 + elif self._units == "MPH": + mps_value = self._value*0.447000 + else: + mps_value = self._value + if units == "KMH": + return mps_value*3.6 + elif units == "KT": + return mps_value/0.514444 + elif units == "MPH": + return mps_value/0.447000 + elif units == "MPS": + return mps_value + + def string( self, units=None ): + """Return a string representation of the speed in the given units.""" + if not units: + units = self._units + else: + if not units.upper() in speed.legal_units: + raise UnitsError("unrecognized speed unit: '"+units+"'") + units = units.upper() + val = self.value(units) + if units == "KMH": + text = "%.0f km/h" % val + elif units == "KT": + text = "%.0f knots" % val + elif units == "MPH": + text = "%.0f mph" % val + elif units == "MPS": + text = "%.0f mps" % val + if self._gtlt == ">": + text = "greater than "+text + elif self._gtlt == "<": + text = "less than "+text + return text class distance(object): - """A class representing a distance value.""" - legal_units = [ "SM", "MI", "M", "KM", "FT" ] - legal_gtlt = [ ">", "<" ] - - def __init__( self, value, units=None, gtlt=None ): - if not units: - self._units = "M" - else: - if not units.upper() in distance.legal_units: - raise UnitsError("unrecognized distance unit: '"+units+"'") - self._units = units.upper() + """A class representing a distance value.""" + legal_units = [ "SM", "MI", "M", "KM", "FT" ] + legal_gtlt = [ ">", "<" ] - try: - if value.startswith('M'): - value = value[1:] - gtlt = "<" - elif value.startswith('P'): - value = value[1:] - gtlt = ">" - except: - pass - if gtlt and not gtlt in distance.legal_gtlt: - raise ValueError("unrecognized greater-than/less-than symbol: '"+gtlt+"'") - self._gtlt = gtlt - try: - self._value = float(value) - self._num = None - self._den = None - except ValueError: - mf = FRACTION_RE.match(value) - if not mf: - raise ValueError("distance is not parseable: '"+str(value)+"'") - df = mf.groupdict() - self._num = int(df['num']) - self._den = int(df['den']) - self._value = float(self._num)/float(self._den) - if df['int']: - self._value += float(df['int']) + def __init__( self, value, units=None, gtlt=None ): + if not units: + self._units = "M" + else: + if not units.upper() in distance.legal_units: + raise UnitsError("unrecognized distance unit: '"+units+"'") + self._units = units.upper() + + try: + if value.startswith('M'): + value = value[1:] + gtlt = "<" + elif value.startswith('P'): + value = value[1:] + gtlt = ">" + except: + pass + if gtlt and not gtlt in distance.legal_gtlt: + raise ValueError("unrecognized greater-than/less-than symbol: '"+gtlt+"'") + self._gtlt = gtlt + try: + self._value = float(value) + self._num = None + self._den = None + except ValueError: + mf = FRACTION_RE.match(value) + if not mf: + raise ValueError("distance is not parseable: '"+str(value)+"'") + df = mf.groupdict() + self._num = int(df['num']) + self._den = int(df['den']) + self._value = float(self._num)/float(self._den) + if df['int']: + self._value += float(df['int']) - def __str__(self): - return self.string() - - def value( self, units=None ): - """Return the distance in the specified units.""" - if not units: - return self._value - else: - if not units.upper() in distance.legal_units: - raise UnitsError("unrecognized distance unit: '"+units+"'") - units = units.upper() - if units == self._units: - return self._value - if self._units == "SM" or self._units == "MI": - m_value = self._value*1609.344 - elif self._units == "FT": - m_value = self._value/3.28084 - elif self._units == "KM": - m_value = self._value*1000 - else: - m_value = self._value - if units == "SM" or units == "MI": - return m_value/1609.344 - elif units == "FT": - return m_value*3.28084 - elif units == "KM": - return m_value/1000 - elif units == "M": - return m_value - - def string( self, units=None ): - """Return a string representation of the distance in the given units.""" - if not units: - units = self._units - else: - if not units.upper() in distance.legal_units: - raise UnitsError("unrecognized distance unit: '"+units+"'") - units = units.upper() - if self._num and self._den and units == self._units: - val = int(self._value - self._num/self._den) - if val: - text = "%d %d/%d" % (val, self._num, self._den) - else: - text = "%d/%d" % (self._num, self._den) - else: - if units == "KM": - text = "%.1f" % self.value(units) - else: - text = "%.0f" % self.value(units) - if units == "SM" or units == "MI": - text += " miles" - elif units == "M": - text += " meters" - elif units == "KM": - text += " km" - elif units == "FT": - text += " feet" - if self._gtlt == ">": - text = "greater than "+text - elif self._gtlt == "<": - text = "less than "+text - return text + def __str__(self): + return self.string() + + def value( self, units=None ): + """Return the distance in the specified units.""" + if not units: + return self._value + else: + if not units.upper() in distance.legal_units: + raise UnitsError("unrecognized distance unit: '"+units+"'") + units = units.upper() + if units == self._units: + return self._value + if self._units == "SM" or self._units == "MI": + m_value = self._value*1609.344 + elif self._units == "FT": + m_value = self._value/3.28084 + elif self._units == "KM": + m_value = self._value*1000 + else: + m_value = self._value + if units == "SM" or units == "MI": + return m_value/1609.344 + elif units == "FT": + return m_value*3.28084 + elif units == "KM": + return m_value/1000 + elif units == "M": + return m_value + + def string( self, units=None ): + """Return a string representation of the distance in the given units.""" + if not units: + units = self._units + else: + if not units.upper() in distance.legal_units: + raise UnitsError("unrecognized distance unit: '"+units+"'") + units = units.upper() + if self._num and self._den and units == self._units: + val = int(self._value - self._num/self._den) + if val: + text = "%d %d/%d" % (val, self._num, self._den) + else: + text = "%d/%d" % (self._num, self._den) + else: + if units == "KM": + text = "%.1f" % self.value(units) + else: + text = "%.0f" % self.value(units) + if units == "SM" or units == "MI": + text += " miles" + elif units == "M": + text += " meters" + elif units == "KM": + text += " km" + elif units == "FT": + text += " feet" + if self._gtlt == ">": + text = "greater than "+text + elif self._gtlt == "<": + text = "less than "+text + return text class direction(object): - """A class representing a compass direction.""" - - compass_dirs = { "N": 0.0, "NNE": 22.5, "NE": 45.0, "ENE": 67.5, - "E": 90.0, "ESE":112.5, "SE":135.0, "SSE":157.5, - "S":180.0, "SSW":202.5, "SW":225.0, "WSW":247.5, - "W":270.0, "WNW":292.5, "NW":315.0, "NNW":337.5 } + """A class representing a compass direction.""" + + compass_dirs = { "N": 0.0, "NNE": 22.5, "NE": 45.0, "ENE": 67.5, + "E": 90.0, "ESE":112.5, "SE":135.0, "SSE":157.5, + "S":180.0, "SSW":202.5, "SW":225.0, "WSW":247.5, + "W":270.0, "WNW":292.5, "NW":315.0, "NNW":337.5 } - def __init__( self, d ): - if direction.compass_dirs.has_key(d): - self._compass = d - self._degrees = direction.compass_dirs[d] - else: - self._compass = None - value = float(d) - if value < 0.0 or value > 360.0: - raise ValueError("direction must be 0..360: '"+str(value)+"'") - self._degrees = value + def __init__( self, d ): + if direction.compass_dirs.has_key(d): + self._compass = d + self._degrees = direction.compass_dirs[d] + else: + self._compass = None + value = float(d) + if value < 0.0 or value > 360.0: + raise ValueError("direction must be 0..360: '"+str(value)+"'") + self._degrees = value - def __str__(self): - return self.string() - - def value( self ): - """Return the numerical direction, in degrees.""" - return self._degrees - - def string( self ): - """Return a string representation of the numerical direction.""" - return "%.0f degrees" % self._degrees - - def compass( self ): - """Return the compass direction, e.g., "N", "ESE", etc.).""" - if not self._compass: - degrees = 22.5 * round(self._degrees/22.5) - if degrees == 360.0: - self._compass = "N" - else: - for name, d in direction.compass_dirs.iteritems(): - if d == degrees: - self._compass = name - break - return self._compass + def __str__(self): + return self.string() + + def value( self ): + """Return the numerical direction, in degrees.""" + return self._degrees + + def string( self ): + """Return a string representation of the numerical direction.""" + return "%.0f degrees" % self._degrees + + def compass( self ): + """Return the compass direction, e.g., "N", "ESE", etc.).""" + if not self._compass: + degrees = 22.5 * round(self._degrees/22.5) + if degrees == 360.0: + self._compass = "N" + else: + for name, d in direction.compass_dirs.iteritems(): + if d == degrees: + self._compass = name + break + return self._compass class precipitation(object): - """A class representing a precipitation value.""" - legal_units = [ "IN", "CM" ] - legal_gtlt = [ ">", "<" ] - - def __init__( self, value, units=None, gtlt=None ): - if not units: - self._units = "IN" - else: - if not units.upper() in precipitation.legal_units: - raise UnitsError("unrecognized precipitation unit: '"+units+"'") - self._units = units.upper() + """A class representing a precipitation value.""" + legal_units = [ "IN", "CM" ] + legal_gtlt = [ ">", "<" ] - try: - if value.startswith('M'): - value = value[1:] - gtlt = "<" - elif value.startswith('P'): - value = value[1:] - gtlt = ">" - except: - pass - if gtlt and not gtlt in precipitation.legal_gtlt: - raise ValueError("unrecognized greater-than/less-than symbol: '"+gtlt+"'") - self._gtlt = gtlt - self._value = float(value) + def __init__( self, value, units=None, gtlt=None ): + if not units: + self._units = "IN" + else: + if not units.upper() in precipitation.legal_units: + raise UnitsError("unrecognized precipitation unit: '"+units+"'") + self._units = units.upper() + + try: + if value.startswith('M'): + value = value[1:] + gtlt = "<" + elif value.startswith('P'): + value = value[1:] + gtlt = ">" + except: + pass + if gtlt and not gtlt in precipitation.legal_gtlt: + raise ValueError("unrecognized greater-than/less-than symbol: '"+gtlt+"'") + self._gtlt = gtlt + self._value = float(value) - def __str__(self): - return self.string() - - def value( self, units=None ): - """Return the precipitation in the specified units.""" - if not units: - return self._value - else: - if not units.upper() in precipitation.legal_units: - raise UnitsError("unrecognized precipitation unit: '"+units+"'") - units = units.upper() - if units == self._units: - return self._value - if self._units == "CM": - i_value = self._value*2.54 - else: - i_value = self._value - if units == "CM": - return i_value*2.54 - else: - return i_value - - def string( self, units=None ): - """Return a string representation of the precipitation in the given units.""" - if not units: - units = self._units - else: - if not units.upper() in precipitation.legal_units: - raise UnitsError("unrecognized precipitation unit: '"+units+"'") - units = units.upper() - text = "%.2f" % self.value(units) - if units == "CM": - text += "cm" - else: - text += "in" - if self._gtlt == ">": - text = "greater than "+text - elif self._gtlt == "<": - text = "less than "+text - return text + def __str__(self): + return self.string() + + def value( self, units=None ): + """Return the precipitation in the specified units.""" + if not units: + return self._value + else: + if not units.upper() in precipitation.legal_units: + raise UnitsError("unrecognized precipitation unit: '"+units+"'") + units = units.upper() + if units == self._units: + return self._value + if self._units == "CM": + i_value = self._value*2.54 + else: + i_value = self._value + if units == "CM": + return i_value*2.54 + else: + return i_value + + def string( self, units=None ): + """Return a string representation of the precipitation in the given units.""" + if not units: + units = self._units + else: + if not units.upper() in precipitation.legal_units: + raise UnitsError("unrecognized precipitation unit: '"+units+"'") + units = units.upper() + text = "%.2f" % self.value(units) + if units == "CM": + text += "cm" + else: + text += "in" + if self._gtlt == ">": + text = "greater than "+text + elif self._gtlt == "<": + text = "less than "+text + return text class position(object): - """A class representing a location on the earth's surface.""" - - def __init__( self, latitude=None, longitude=None ): - self.latitude = latitude - self.longitude = longitude + """A class representing a location on the earth's surface.""" + + def __init__( self, latitude=None, longitude=None ): + self.latitude = latitude + self.longitude = longitude - def __str__(self): - return self.string() - - def getdistance( self, position2 ): - """ - Calculate the great-circle distance to another location using the Haversine - formula. See - and - """ - earth_radius = 637100.0 - lat1 = self.latitude - long1 = self.longitude - lat2 = position2.latitude - long2 = position2.longitude - a = sin(0.5(lat2-lat1)) + cos(lat1)*cos(lat2)*sin(0.5*(long2-long1)**2) - c = 2.0*atan(sqrt(a)*sqrt(1.0-a)) - d = distance(earth_radius*c,"M") - return d + def __str__(self): + return self.string() + + def getdistance( self, position2 ): + """ + Calculate the great-circle distance to another location using the Haversine + formula. See + and + """ + earth_radius = 637100.0 + lat1 = self.latitude + long1 = self.longitude + lat2 = position2.latitude + long2 = position2.longitude + a = sin(0.5(lat2-lat1)) + cos(lat1)*cos(lat2)*sin(0.5*(long2-long1)**2) + c = 2.0*atan(sqrt(a)*sqrt(1.0-a)) + d = distance(earth_radius*c,"M") + return d - def getdirection( self, position2 ): - """ - Calculate the initial direction to another location. (The direction - typically changes as you trace the great circle path to that location.) - See . - """ - lat1 = self.latitude - long1 = self.longitude - lat2 = position2.latitude - long2 = position2.longitude - s = -sin(long1-long2)*cos(lat2) - c = cos(lat1)*sin(lat2) - sin(lat1)*cos(lat2)*cos(long1-long2) - d = atan2(s,c)*180.0/math.pi - if d < 0.0: d += 360.0 - return direction(d) + def getdirection( self, position2 ): + """ + Calculate the initial direction to another location. (The direction + typically changes as you trace the great circle path to that location.) + See . + """ + lat1 = self.latitude + long1 = self.longitude + lat2 = position2.latitude + long2 = position2.longitude + s = -sin(long1-long2)*cos(lat2) + c = cos(lat1)*sin(lat2) - sin(lat1)*cos(lat2)*cos(long1-long2) + d = atan2(s,c)*180.0/math.pi + if d < 0.0: d += 360.0 + return direction(d) diff --git a/metar/Metar.py b/metar/Metar.py index eccc9995..384a63ee 100755 --- a/metar/Metar.py +++ b/metar/Metar.py @@ -310,877 +310,877 @@ def _unparsedGroup( self, d ): debug = False class Metar(object): - """METAR (aviation meteorology report)""" - - def __init__( self, metarcode, month=None, year=None, utcdelta=None): - """Parse raw METAR code.""" - self.code = metarcode # original METAR code - self.type = 'METAR' # METAR (routine) or SPECI (special) - self.mod = "AUTO" # AUTO (automatic) or COR (corrected) - self.station_id = None # 4-character ICAO station code - self.time = None # observation time [datetime] - self.cycle = None # observation cycle (0-23) [int] - self.wind_dir = None # wind direction [direction] - self.wind_speed = None # wind speed [speed] - self.wind_gust = None # wind gust speed [speed] - self.wind_dir_from = None # beginning of range for win dir [direction] - self.wind_dir_to = None # end of range for wind dir [direction] - self.vis = None # visibility [distance] - self.vis_dir = None # visibility direction [direction] - self.max_vis = None # visibility [distance] - self.max_vis_dir = None # visibility direction [direction] - self.temp = None # temperature (C) [temperature] - self.dewpt = None # dew point (C) [temperature] - self.press = None # barometric pressure [pressure] - self.runway = [] # runway visibility (list of tuples) - self.weather = [] # present weather (list of tuples) - self.recent = [] # recent weather (list of tuples) - self.sky = [] # sky conditions (list of tuples) - self.windshear = [] # runways w/ wind shear (list of strings) - self.wind_speed_peak = None # peak wind speed in last hour - self.wind_dir_peak = None # direction of peak wind speed in last hour - self.peak_wind_time = None # time of peak wind observation [datetime] - self.wind_shift_time = None # time of wind shift [datetime] - self.max_temp_6hr = None # max temp in last 6 hours - self.min_temp_6hr = None # min temp in last 6 hours - self.max_temp_24hr = None # max temp in last 24 hours - self.min_temp_24hr = None # min temp in last 24 hours - self.press_sea_level = None # sea-level pressure - self.precip_1hr = None # precipitation over the last hour - self.precip_3hr = None # precipitation over the last 3 hours - self.precip_6hr = None # precipitation over the last 6 hours - self.precip_24hr = None # precipitation over the last 24 hours - self._trend = False # trend groups present (bool) - self._trend_groups = [] # trend forecast groups - self._remarks = [] # remarks (list of strings) - self._unparsed_groups = [] - self._unparsed_remarks = [] - - self._now = datetime.datetime.utcnow() - if utcdelta: - self._utcdelta = utcdelta - else: - self._utcdelta = datetime.datetime.now() - self._now + """METAR (aviation meteorology report)""" + + def __init__( self, metarcode, month=None, year=None, utcdelta=None): + """Parse raw METAR code.""" + self.code = metarcode # original METAR code + self.type = 'METAR' # METAR (routine) or SPECI (special) + self.mod = "AUTO" # AUTO (automatic) or COR (corrected) + self.station_id = None # 4-character ICAO station code + self.time = None # observation time [datetime] + self.cycle = None # observation cycle (0-23) [int] + self.wind_dir = None # wind direction [direction] + self.wind_speed = None # wind speed [speed] + self.wind_gust = None # wind gust speed [speed] + self.wind_dir_from = None # beginning of range for win dir [direction] + self.wind_dir_to = None # end of range for wind dir [direction] + self.vis = None # visibility [distance] + self.vis_dir = None # visibility direction [direction] + self.max_vis = None # visibility [distance] + self.max_vis_dir = None # visibility direction [direction] + self.temp = None # temperature (C) [temperature] + self.dewpt = None # dew point (C) [temperature] + self.press = None # barometric pressure [pressure] + self.runway = [] # runway visibility (list of tuples) + self.weather = [] # present weather (list of tuples) + self.recent = [] # recent weather (list of tuples) + self.sky = [] # sky conditions (list of tuples) + self.windshear = [] # runways w/ wind shear (list of strings) + self.wind_speed_peak = None # peak wind speed in last hour + self.wind_dir_peak = None # direction of peak wind speed in last hour + self.peak_wind_time = None # time of peak wind observation [datetime] + self.wind_shift_time = None # time of wind shift [datetime] + self.max_temp_6hr = None # max temp in last 6 hours + self.min_temp_6hr = None # min temp in last 6 hours + self.max_temp_24hr = None # max temp in last 24 hours + self.min_temp_24hr = None # min temp in last 24 hours + self.press_sea_level = None # sea-level pressure + self.precip_1hr = None # precipitation over the last hour + self.precip_3hr = None # precipitation over the last 3 hours + self.precip_6hr = None # precipitation over the last 6 hours + self.precip_24hr = None # precipitation over the last 24 hours + self._trend = False # trend groups present (bool) + self._trend_groups = [] # trend forecast groups + self._remarks = [] # remarks (list of strings) + self._unparsed_groups = [] + self._unparsed_remarks = [] + + self._now = datetime.datetime.utcnow() + if utcdelta: + self._utcdelta = utcdelta + else: + self._utcdelta = datetime.datetime.now() - self._now - self._month = month - self._year = year - - code = self.code+" " # (the regexps all expect trailing spaces...) - try: - ngroup = len(Metar.handlers) - igroup = 0 - ifailed = -1 - while igroup < ngroup and code: - pattern, handler, repeatable = Metar.handlers[igroup] - if debug: print handler.__name__,":",code - m = pattern.match(code) - while m: - ifailed = -1 - if debug: _report_match(handler,m.group()) - handler(self,m.groupdict()) - code = code[m.end():] - if self._trend: - code = self._do_trend_handlers(code) - if not repeatable: break - - if debug: print handler.__name__,":",code - m = pattern.match(code) - if not m and ifailed < 0: - ifailed = igroup - igroup += 1 - if igroup == ngroup and not m: - # print "** it's not a main-body group **" - pattern, handler = (UNPARSED_RE, _unparsedGroup) - if debug: print handler.__name__,":",code - m = pattern.match(code) - if debug: _report_match(handler,m.group()) - handler(self,m.groupdict()) - code = code[m.end():] - igroup = ifailed - ifailed = -2 # if it's still -2 when we run out of main-body - # groups, we'll try parsing this group as a remark - if pattern == REMARK_RE or self.press: - while code: - for pattern, handler in Metar.remark_handlers: - if debug: print handler.__name__,":",code - m = pattern.match(code) - if m: - if debug: _report_match(handler,m.group()) - handler(self,m.groupdict()) - code = pattern.sub("",code,1) - break + self._month = month + self._year = year + + code = self.code+" " # (the regexps all expect trailing spaces...) + try: + ngroup = len(Metar.handlers) + igroup = 0 + ifailed = -1 + while igroup < ngroup and code: + pattern, handler, repeatable = Metar.handlers[igroup] + if debug: print handler.__name__,":",code + m = pattern.match(code) + while m: + ifailed = -1 + if debug: _report_match(handler,m.group()) + handler(self,m.groupdict()) + code = code[m.end():] + if self._trend: + code = self._do_trend_handlers(code) + if not repeatable: break + + if debug: print handler.__name__,":",code + m = pattern.match(code) + if not m and ifailed < 0: + ifailed = igroup + igroup += 1 + if igroup == ngroup and not m: + # print "** it's not a main-body group **" + pattern, handler = (UNPARSED_RE, _unparsedGroup) + if debug: print handler.__name__,":",code + m = pattern.match(code) + if debug: _report_match(handler,m.group()) + handler(self,m.groupdict()) + code = code[m.end():] + igroup = ifailed + ifailed = -2 # if it's still -2 when we run out of main-body + # groups, we'll try parsing this group as a remark + if pattern == REMARK_RE or self.press: + while code: + for pattern, handler in Metar.remark_handlers: + if debug: print handler.__name__,":",code + m = pattern.match(code) + if m: + if debug: _report_match(handler,m.group()) + handler(self,m.groupdict()) + code = pattern.sub("",code,1) + break - except Exception, err: - raise ParserError(handler.__name__+" failed while processing '"+code+"'\n"+string.join(err.args)) - raise err - if self._unparsed_groups: - code = ' '.join(self._unparsed_groups) - raise ParserError("Unparsed groups in body: "+code) + except Exception, err: + raise ParserError(handler.__name__+" failed while processing '"+code+"'\n"+string.join(err.args)) + raise err + if self._unparsed_groups: + code = ' '.join(self._unparsed_groups) + raise ParserError("Unparsed groups in body: "+code) - def _do_trend_handlers(self, code): - for pattern, handler, repeatable in Metar.trend_handlers: - if debug: print handler.__name__,":",code - m = pattern.match(code) - while m: - if debug: _report_match(handler, m.group()) - self._trend_groups.append(string.strip(m.group())) - handler(self,m.groupdict()) - code = code[m.end():] - if not repeatable: break - m = pattern.match(code) - return code - - def __str__(self): - return self.string() + def _do_trend_handlers(self, code): + for pattern, handler, repeatable in Metar.trend_handlers: + if debug: print handler.__name__,":",code + m = pattern.match(code) + while m: + if debug: _report_match(handler, m.group()) + self._trend_groups.append(string.strip(m.group())) + handler(self,m.groupdict()) + code = code[m.end():] + if not repeatable: break + m = pattern.match(code) + return code + + def __str__(self): + return self.string() - def _handleType( self, d ): - """ - Parse the code-type group. - - The following attributes are set: - type [string] - """ - self.type = d['type'] - - def _handleStation( self, d ): - """ - Parse the station id group. - - The following attributes are set: - station_id [string] - """ - self.station_id = d['station'] - - def _handleModifier( self, d ): - """ - Parse the report-modifier group. - - The following attributes are set: - mod [string] - """ - mod = d['mod'] - if mod == 'CORR': mod = 'COR' - if mod == 'NIL' or mod == 'FINO': mod = 'NO DATA' - self.mod = mod - - def _handleTime( self, d ): - """ - Parse the observation-time group. - - The following attributes are set: - time [datetime] - cycle [int] - _day [int] - _hour [int] - _min [int] - """ - self._day = int(d['day']) - if not self._month: - self._month = self._now.month - if self._day > self._now.day: - if self._month == 1: - self._month = 12 - else: - self._month = self._month - 1 - if not self._year: - self._year = self._now.year - if self._month > self._now.month: - self._year = self._year - 1 - elif self._month == self._now.month and self._day > self._now.day: - self._year = self._year - 1 - self._hour = int(d['hour']) - self._min = int(d['min']) - self.time = datetime.datetime(self._year, self._month, self._day, - self._hour, self._min) - if self._min < 45: - self.cycle = self._hour - else: - self.cycle = self._hour+1 - - def _handleWind( self, d ): - """ - Parse the wind and variable-wind groups. - - The following attributes are set: - wind_dir [direction] - wind_speed [speed] - wind_gust [speed] - wind_dir_from [int] - wind_dir_to [int] - """ - wind_dir = d['dir'].replace('O','0') - if wind_dir != "VRB" and wind_dir != "///" and wind_dir != "MMM": - self.wind_dir = direction(wind_dir) - wind_speed = d['speed'].replace('O','0') - units = d['units'] - if units == 'KTS' or units == 'K' or units == 'T' or units == 'LT': - units = 'KT' - if wind_speed.startswith("P"): - self.wind_speed = speed(wind_speed[1:], units, ">") - elif not MISSING_RE.match(wind_speed): - self.wind_speed = speed(wind_speed, units) - if d['gust']: - wind_gust = d['gust'] - if wind_gust.startswith("P"): - self.wind_gust = speed(wind_gust[1:], units, ">") - elif not MISSING_RE.match(wind_gust): - self.wind_gust = speed(wind_gust, units) - if d['varfrom']: - self.wind_dir_from = direction(d['varfrom']) - self.wind_dir_to = direction(d['varto']) - - def _handleVisibility( self, d ): - """ - Parse the minimum and maximum visibility groups. - - The following attributes are set: - vis [distance] - vis_dir [direction] - max_vis [distance] - max_vis_dir [direction] - """ - vis = d['vis'] - vis_less = None - vis_dir = None - vis_units = "M" - vis_dist = "10000" - if d['dist'] and d['dist'] != '////': - vis_dist = d['dist'] - if d['dir'] and d['dir'] != 'NDV': - vis_dir = d['dir'] - elif d['distu']: - vis_dist = d['distu'] - if d['units'] and d['units'] != "U": - vis_units = d['units'] - if vis_dist == "9999": - vis_dist = "10000" - vis_less = ">" - if self.vis: - if vis_dir: - self.max_vis_dir = direction(vis_dir) - self.max_vis = distance(vis_dist, vis_units, vis_less) - else: - if vis_dir: - self.vis_dir = direction(vis_dir) - self.vis = distance(vis_dist, vis_units, vis_less) - - def _handleRunway( self, d ): - """ - Parse a runway visual range group. - - The following attributes are set: - range [list of tuples] - . name [string] - . low [distance] - . high [distance] - """ - if d['name']: - name = d['name'] - low = distance(d['low']) - if d['high']: - high = distance(d['high']) - else: - high = low - self.runway.append((name,low,high)) - - def _handleWeather( self, d ): - """ - Parse a present-weather group. - - The following attributes are set: - weather [list of tuples] - . intensity [string] - . description [string] - . precipitation [string] - . obscuration [string] - . other [string] - """ - inteni = d['int'] - if not inteni and d['int2']: - inteni = d['int2'] - desci = d['desc'] - preci = d['prec'] - obsci = d['obsc'] - otheri = d['other'] - self.weather.append((inteni,desci,preci,obsci,otheri)) - - def _handleSky( self, d ): - """ - Parse a sky-conditions group. - - The following attributes are set: - sky [list of tuples] - . cover [string] - . height [distance] - . cloud [string] - """ - height = d['height'] - if not height or height == "///": - height = None - else: - height = height.replace('O','0') - height = distance(int(height)*100,"FT") - cover = d['cover'] - if cover == 'SCK' or cover == 'SKC' or cover == 'CL': cover = 'CLR' - if cover == '0VC': cover = 'OVC' - cloud = d['cloud'] - if cloud == '///': cloud = "" - self.sky.append((cover,height,cloud)) - - def _handleTemp( self, d ): - """ - Parse a temperature-dewpoint group. - - The following attributes are set: - temp temperature (Celsius) [float] - dewpt dew point (Celsius) [float] - """ - temp = d['temp'] - dewpt = d['dewpt'] - if temp and temp != "//" and temp != "XX" and temp != "MM" : - self.temp = temperature(temp) - if dewpt and dewpt != "//" and dewpt != "XX" and dewpt != "MM" : - self.dewpt = temperature(dewpt) - - def _handlePressure( self, d ): - """ - Parse an altimeter-pressure group. - - The following attributes are set: - press [int] - """ - press = d['press'] - if press != '////': - press = float(press.replace('O','0')) - if d['unit']: - if d['unit'] == 'A' or (d['unit2'] and d['unit2'] == 'INS'): - self.press = pressure(press/100,'IN') - elif d['unit'] == 'SLP': - if press < 500: - press = press/10 + 1000 - else: - press = press/10 + 900 - self.press = pressure(press,'MB') - self._remarks.append("sea-level pressure %.1fhPa" % press) - else: - self.press = pressure(press,'MB') - elif press > 2500: - self.press = pressure(press/100,'IN') - else: - self.press = pressure(press,'MB') - - def _handleRecent( self, d ): - """ - Parse a recent-weather group. - - The following attributes are set: - weather [list of tuples] - . intensity [string] - . description [string] - . precipitation [string] - . obscuration [string] - . other [string] - """ - desci = d['desc'] - preci = d['prec'] - obsci = d['obsc'] - otheri = d['other'] - self.recent.append(("",desci,preci,obsci,otheri)) - - def _handleWindShear( self, d ): - """ - Parse wind-shear groups. - - The following attributes are set: - windshear [list of strings] - """ - if d['name']: - self.windshear.append(d['name']) - else: - self.windshear.append("ALL") - - def _handleColor( self, d ): - """ - Parse (and ignore) the color groups. - - The following attributes are set: - trend [list of strings] - """ - pass + def _handleType( self, d ): + """ + Parse the code-type group. + + The following attributes are set: + type [string] + """ + self.type = d['type'] + + def _handleStation( self, d ): + """ + Parse the station id group. + + The following attributes are set: + station_id [string] + """ + self.station_id = d['station'] + + def _handleModifier( self, d ): + """ + Parse the report-modifier group. + + The following attributes are set: + mod [string] + """ + mod = d['mod'] + if mod == 'CORR': mod = 'COR' + if mod == 'NIL' or mod == 'FINO': mod = 'NO DATA' + self.mod = mod + + def _handleTime( self, d ): + """ + Parse the observation-time group. + + The following attributes are set: + time [datetime] + cycle [int] + _day [int] + _hour [int] + _min [int] + """ + self._day = int(d['day']) + if not self._month: + self._month = self._now.month + if self._day > self._now.day: + if self._month == 1: + self._month = 12 + else: + self._month = self._month - 1 + if not self._year: + self._year = self._now.year + if self._month > self._now.month: + self._year = self._year - 1 + elif self._month == self._now.month and self._day > self._now.day: + self._year = self._year - 1 + self._hour = int(d['hour']) + self._min = int(d['min']) + self.time = datetime.datetime(self._year, self._month, self._day, + self._hour, self._min) + if self._min < 45: + self.cycle = self._hour + else: + self.cycle = self._hour+1 + + def _handleWind( self, d ): + """ + Parse the wind and variable-wind groups. + + The following attributes are set: + wind_dir [direction] + wind_speed [speed] + wind_gust [speed] + wind_dir_from [int] + wind_dir_to [int] + """ + wind_dir = d['dir'].replace('O','0') + if wind_dir != "VRB" and wind_dir != "///" and wind_dir != "MMM": + self.wind_dir = direction(wind_dir) + wind_speed = d['speed'].replace('O','0') + units = d['units'] + if units == 'KTS' or units == 'K' or units == 'T' or units == 'LT': + units = 'KT' + if wind_speed.startswith("P"): + self.wind_speed = speed(wind_speed[1:], units, ">") + elif not MISSING_RE.match(wind_speed): + self.wind_speed = speed(wind_speed, units) + if d['gust']: + wind_gust = d['gust'] + if wind_gust.startswith("P"): + self.wind_gust = speed(wind_gust[1:], units, ">") + elif not MISSING_RE.match(wind_gust): + self.wind_gust = speed(wind_gust, units) + if d['varfrom']: + self.wind_dir_from = direction(d['varfrom']) + self.wind_dir_to = direction(d['varto']) + + def _handleVisibility( self, d ): + """ + Parse the minimum and maximum visibility groups. + + The following attributes are set: + vis [distance] + vis_dir [direction] + max_vis [distance] + max_vis_dir [direction] + """ + vis = d['vis'] + vis_less = None + vis_dir = None + vis_units = "M" + vis_dist = "10000" + if d['dist'] and d['dist'] != '////': + vis_dist = d['dist'] + if d['dir'] and d['dir'] != 'NDV': + vis_dir = d['dir'] + elif d['distu']: + vis_dist = d['distu'] + if d['units'] and d['units'] != "U": + vis_units = d['units'] + if vis_dist == "9999": + vis_dist = "10000" + vis_less = ">" + if self.vis: + if vis_dir: + self.max_vis_dir = direction(vis_dir) + self.max_vis = distance(vis_dist, vis_units, vis_less) + else: + if vis_dir: + self.vis_dir = direction(vis_dir) + self.vis = distance(vis_dist, vis_units, vis_less) + + def _handleRunway( self, d ): + """ + Parse a runway visual range group. + + The following attributes are set: + range [list of tuples] + . name [string] + . low [distance] + . high [distance] + """ + if d['name']: + name = d['name'] + low = distance(d['low']) + if d['high']: + high = distance(d['high']) + else: + high = low + self.runway.append((name,low,high)) + + def _handleWeather( self, d ): + """ + Parse a present-weather group. + + The following attributes are set: + weather [list of tuples] + . intensity [string] + . description [string] + . precipitation [string] + . obscuration [string] + . other [string] + """ + inteni = d['int'] + if not inteni and d['int2']: + inteni = d['int2'] + desci = d['desc'] + preci = d['prec'] + obsci = d['obsc'] + otheri = d['other'] + self.weather.append((inteni,desci,preci,obsci,otheri)) + + def _handleSky( self, d ): + """ + Parse a sky-conditions group. + + The following attributes are set: + sky [list of tuples] + . cover [string] + . height [distance] + . cloud [string] + """ + height = d['height'] + if not height or height == "///": + height = None + else: + height = height.replace('O','0') + height = distance(int(height)*100,"FT") + cover = d['cover'] + if cover == 'SCK' or cover == 'SKC' or cover == 'CL': cover = 'CLR' + if cover == '0VC': cover = 'OVC' + cloud = d['cloud'] + if cloud == '///': cloud = "" + self.sky.append((cover,height,cloud)) + + def _handleTemp( self, d ): + """ + Parse a temperature-dewpoint group. + + The following attributes are set: + temp temperature (Celsius) [float] + dewpt dew point (Celsius) [float] + """ + temp = d['temp'] + dewpt = d['dewpt'] + if temp and temp != "//" and temp != "XX" and temp != "MM" : + self.temp = temperature(temp) + if dewpt and dewpt != "//" and dewpt != "XX" and dewpt != "MM" : + self.dewpt = temperature(dewpt) + + def _handlePressure( self, d ): + """ + Parse an altimeter-pressure group. + + The following attributes are set: + press [int] + """ + press = d['press'] + if press != '////': + press = float(press.replace('O','0')) + if d['unit']: + if d['unit'] == 'A' or (d['unit2'] and d['unit2'] == 'INS'): + self.press = pressure(press/100,'IN') + elif d['unit'] == 'SLP': + if press < 500: + press = press/10 + 1000 + else: + press = press/10 + 900 + self.press = pressure(press,'MB') + self._remarks.append("sea-level pressure %.1fhPa" % press) + else: + self.press = pressure(press,'MB') + elif press > 2500: + self.press = pressure(press/100,'IN') + else: + self.press = pressure(press,'MB') + + def _handleRecent( self, d ): + """ + Parse a recent-weather group. + + The following attributes are set: + weather [list of tuples] + . intensity [string] + . description [string] + . precipitation [string] + . obscuration [string] + . other [string] + """ + desci = d['desc'] + preci = d['prec'] + obsci = d['obsc'] + otheri = d['other'] + self.recent.append(("",desci,preci,obsci,otheri)) + + def _handleWindShear( self, d ): + """ + Parse wind-shear groups. + + The following attributes are set: + windshear [list of strings] + """ + if d['name']: + self.windshear.append(d['name']) + else: + self.windshear.append("ALL") + + def _handleColor( self, d ): + """ + Parse (and ignore) the color groups. + + The following attributes are set: + trend [list of strings] + """ + pass - def _handleRunwayState( self, d ): - """ - Parse (and ignore) the runway state. + def _handleRunwayState( self, d ): + """ + Parse (and ignore) the runway state. - The following attributes are set: - """ - pass + The following attributes are set: + """ + pass - def _handleTrend( self, d ): - """ - Parse (and ignore) the trend groups. - """ - if d.has_key('trend'): - self._trend_groups.append(d['trend']) - self._trend = True - - def _startRemarks( self, d ): - """ - Found the start of the remarks section. - """ - self._remarks = [] - - def _handleSealvlPressRemark( self, d ): - """ - Parse the sea-level pressure remark group. - """ - value = float(d['press'])/10.0 - if value < 50: - value += 1000 - else: - value += 900 - if not self.press: - self.press = pressure(value,"MB") - self.press_sea_level = pressure(value,"MB") - - def _handlePrecip24hrRemark( self, d ): - """ - Parse a 3-, 6- or 24-hour cumulative preciptation remark group. - """ - value = float(d['precip'])/100.0 - if d['type'] == "6": - if self.cycle == 3 or self.cycle == 9 or self.cycle == 15 or self.cycle == 21: - self.precip_3hr = precipitation(value,"IN") - else: - self.precip_6hr = precipitation(value,"IN") - else: - self.precip_24hr = precipitation(value,"IN") - - def _handlePrecip1hrRemark( self, d ): - """Parse an hourly precipitation remark group.""" - value = float(d['precip'])/100.0 - self.precip_1hr = precipitation(value,"IN") - - def _handleTemp1hrRemark( self, d ): - """ - Parse a temperature & dewpoint remark group. - - These values replace the temp and dewpt from the body of the report. - """ - value = float(d['temp'])/10.0 - if d['tsign'] == "1": value = -value - self.temp = temperature(value) - if d['dewpt']: - value2 = float(d['dewpt'])/10.0 - if d['dsign'] == "1": value2 = -value2 - self.dewpt = temperature(value2) - - def _handleTemp6hrRemark( self, d ): - """ - Parse a 6-hour maximum or minimum temperature remark group. - """ - value = float(d['temp'])/10.0 - if d['sign'] == "1": value = -value - if d['type'] == "1": - self.max_temp_6hr = temperature(value,"C") - else: - self.min_temp_6hr = temperature(value,"C") - - def _handleTemp24hrRemark( self, d ): - """ - Parse a 24-hour maximum/minimum temperature remark group. - """ - value = float(d['maxt'])/10.0 - if d['smaxt'] == "1": value = -value - value2 = float(d['mint'])/10.0 - if d['smint'] == "1": value2 = -value2 - self.max_temp_24hr = temperature(value,"C") - self.min_temp_24hr = temperature(value2,"C") - - def _handlePress3hrRemark( self, d ): - """ - Parse a pressure-tendency remark group. - """ - value = float(d['press'])/10.0 - descrip = PRESSURE_TENDENCY[d['tend']] - self._remarks.append("3-hr pressure change %.1fhPa, %s" % (value,descrip)) - - def _handlePeakWindRemark( self, d ): - """ - Parse a peak wind remark group. - """ - peak_dir = int(d['dir']) - peak_speed = int(d['speed']) - self.wind_speed_peak = speed(peak_speed, "KT") - self.wind_dir_peak = direction(peak_dir) - peak_min = int(d['min']) - if d['hour']: - peak_hour = int(d['hour']) - else: - peak_hour = self._hour - self.peak_wind_time = datetime.datetime(self._year, self._month, self._day, - peak_hour, peak_min) - if self.peak_wind_time > self.time: - if peak_hour > self._hour: - self.peak_wind_time -= datetime.timedelta(hours=24) - else: - self.peak_wind_time -= datetime.timedelta(hours=1) - self._remarks.append("peak wind %dkt from %d degrees at %d:%02d" % \ - (peak_speed, peak_dir, peak_hour, peak_min)) - - def _handleWindShiftRemark( self, d ): - """ - Parse a wind shift remark group. - """ - if d['hour']: - wshft_hour = int(d['hour']) - else: - wshft_hour = self._hour - wshft_min = int(d['min']) - self.wind_shift_time = datetime.datetime(self._year, self._month, self._day, - wshft_hour, wshft_min) - if self.wind_shift_time > self.time: - if wshft_hour > self._hour: - self.wind_shift_time -= datetime.timedelta(hours=24) - else: - self.wind_shift_time -= datetime.timedelta(hours=1) - text = "wind shift at %d:%02d" % (wshft_hour, wshft_min) - if d['front']: - text += " (front)" - self._remarks.append(text) - - def _handleLightningRemark( self, d ): - """ - Parse a lightning observation remark group. - """ - parts = [] - if d['freq']: - parts.append(LIGHTNING_FREQUENCY[d['freq']]) - parts.append("lightning") - if d['type']: - ltg_types = [] - group = d['type'] - while group: - ltg_types.append(LIGHTNING_TYPE[group[:2]]) - group = group[2:] - parts.append("("+string.join(ltg_types,",")+")") - if d['loc']: - parts.append(xlate_loc(d['loc'])) - self._remarks.append(string.join(parts," ")) - - def _handleTSLocRemark( self, d ): - """ - Parse a thunderstorm location remark group. - """ - text = "thunderstorm" - if d['loc']: - text += " "+xlate_loc(d['loc']) - if d['dir']: - text += " moving %s" % d['dir'] - self._remarks.append(text) - - def _handleAutoRemark( self, d ): - """ - Parse an automatic station remark group. - """ - if d['type'] == "1": - self._remarks.append("Automated station") - elif d['type'] == "2": - self._remarks.append("Automated station (type 2)") - - def _unparsedRemark( self, d ): - """ - Handle otherwise unparseable remark groups. - """ - self._unparsed_remarks.append(d['group']) - - ## the list of handler functions to use (in order) to process a METAR report + def _handleTrend( self, d ): + """ + Parse (and ignore) the trend groups. + """ + if d.has_key('trend'): + self._trend_groups.append(d['trend']) + self._trend = True + + def _startRemarks( self, d ): + """ + Found the start of the remarks section. + """ + self._remarks = [] + + def _handleSealvlPressRemark( self, d ): + """ + Parse the sea-level pressure remark group. + """ + value = float(d['press'])/10.0 + if value < 50: + value += 1000 + else: + value += 900 + if not self.press: + self.press = pressure(value,"MB") + self.press_sea_level = pressure(value,"MB") + + def _handlePrecip24hrRemark( self, d ): + """ + Parse a 3-, 6- or 24-hour cumulative preciptation remark group. + """ + value = float(d['precip'])/100.0 + if d['type'] == "6": + if self.cycle == 3 or self.cycle == 9 or self.cycle == 15 or self.cycle == 21: + self.precip_3hr = precipitation(value,"IN") + else: + self.precip_6hr = precipitation(value,"IN") + else: + self.precip_24hr = precipitation(value,"IN") + + def _handlePrecip1hrRemark( self, d ): + """Parse an hourly precipitation remark group.""" + value = float(d['precip'])/100.0 + self.precip_1hr = precipitation(value,"IN") + + def _handleTemp1hrRemark( self, d ): + """ + Parse a temperature & dewpoint remark group. + + These values replace the temp and dewpt from the body of the report. + """ + value = float(d['temp'])/10.0 + if d['tsign'] == "1": value = -value + self.temp = temperature(value) + if d['dewpt']: + value2 = float(d['dewpt'])/10.0 + if d['dsign'] == "1": value2 = -value2 + self.dewpt = temperature(value2) + + def _handleTemp6hrRemark( self, d ): + """ + Parse a 6-hour maximum or minimum temperature remark group. + """ + value = float(d['temp'])/10.0 + if d['sign'] == "1": value = -value + if d['type'] == "1": + self.max_temp_6hr = temperature(value,"C") + else: + self.min_temp_6hr = temperature(value,"C") + + def _handleTemp24hrRemark( self, d ): + """ + Parse a 24-hour maximum/minimum temperature remark group. + """ + value = float(d['maxt'])/10.0 + if d['smaxt'] == "1": value = -value + value2 = float(d['mint'])/10.0 + if d['smint'] == "1": value2 = -value2 + self.max_temp_24hr = temperature(value,"C") + self.min_temp_24hr = temperature(value2,"C") + + def _handlePress3hrRemark( self, d ): + """ + Parse a pressure-tendency remark group. + """ + value = float(d['press'])/10.0 + descrip = PRESSURE_TENDENCY[d['tend']] + self._remarks.append("3-hr pressure change %.1fhPa, %s" % (value,descrip)) + + def _handlePeakWindRemark( self, d ): + """ + Parse a peak wind remark group. + """ + peak_dir = int(d['dir']) + peak_speed = int(d['speed']) + self.wind_speed_peak = speed(peak_speed, "KT") + self.wind_dir_peak = direction(peak_dir) + peak_min = int(d['min']) + if d['hour']: + peak_hour = int(d['hour']) + else: + peak_hour = self._hour + self.peak_wind_time = datetime.datetime(self._year, self._month, self._day, + peak_hour, peak_min) + if self.peak_wind_time > self.time: + if peak_hour > self._hour: + self.peak_wind_time -= datetime.timedelta(hours=24) + else: + self.peak_wind_time -= datetime.timedelta(hours=1) + self._remarks.append("peak wind %dkt from %d degrees at %d:%02d" % \ + (peak_speed, peak_dir, peak_hour, peak_min)) + + def _handleWindShiftRemark( self, d ): + """ + Parse a wind shift remark group. + """ + if d['hour']: + wshft_hour = int(d['hour']) + else: + wshft_hour = self._hour + wshft_min = int(d['min']) + self.wind_shift_time = datetime.datetime(self._year, self._month, self._day, + wshft_hour, wshft_min) + if self.wind_shift_time > self.time: + if wshft_hour > self._hour: + self.wind_shift_time -= datetime.timedelta(hours=24) + else: + self.wind_shift_time -= datetime.timedelta(hours=1) + text = "wind shift at %d:%02d" % (wshft_hour, wshft_min) + if d['front']: + text += " (front)" + self._remarks.append(text) + + def _handleLightningRemark( self, d ): + """ + Parse a lightning observation remark group. + """ + parts = [] + if d['freq']: + parts.append(LIGHTNING_FREQUENCY[d['freq']]) + parts.append("lightning") + if d['type']: + ltg_types = [] + group = d['type'] + while group: + ltg_types.append(LIGHTNING_TYPE[group[:2]]) + group = group[2:] + parts.append("("+string.join(ltg_types,",")+")") + if d['loc']: + parts.append(xlate_loc(d['loc'])) + self._remarks.append(string.join(parts," ")) + + def _handleTSLocRemark( self, d ): + """ + Parse a thunderstorm location remark group. + """ + text = "thunderstorm" + if d['loc']: + text += " "+xlate_loc(d['loc']) + if d['dir']: + text += " moving %s" % d['dir'] + self._remarks.append(text) + + def _handleAutoRemark( self, d ): + """ + Parse an automatic station remark group. + """ + if d['type'] == "1": + self._remarks.append("Automated station") + elif d['type'] == "2": + self._remarks.append("Automated station (type 2)") + + def _unparsedRemark( self, d ): + """ + Handle otherwise unparseable remark groups. + """ + self._unparsed_remarks.append(d['group']) + + ## the list of handler functions to use (in order) to process a METAR report - handlers = [ (TYPE_RE, _handleType, False), - (STATION_RE, _handleStation, False), - (TIME_RE, _handleTime, False), - (MODIFIER_RE, _handleModifier, False), - (WIND_RE, _handleWind, False), - (VISIBILITY_RE, _handleVisibility, True), - (RUNWAY_RE, _handleRunway, True), - (WEATHER_RE, _handleWeather, True), - (SKY_RE, _handleSky, True), - (TEMP_RE, _handleTemp, False), - (PRESS_RE, _handlePressure, True), - (RECENT_RE,_handleRecent, True), - (WINDSHEAR_RE, _handleWindShear, True), - (COLOR_RE, _handleColor, True), - (RUNWAYSTATE_RE, _handleRunwayState, True), - (TREND_RE, _handleTrend, False), - (REMARK_RE, _startRemarks, False) ] + handlers = [ (TYPE_RE, _handleType, False), + (STATION_RE, _handleStation, False), + (TIME_RE, _handleTime, False), + (MODIFIER_RE, _handleModifier, False), + (WIND_RE, _handleWind, False), + (VISIBILITY_RE, _handleVisibility, True), + (RUNWAY_RE, _handleRunway, True), + (WEATHER_RE, _handleWeather, True), + (SKY_RE, _handleSky, True), + (TEMP_RE, _handleTemp, False), + (PRESS_RE, _handlePressure, True), + (RECENT_RE,_handleRecent, True), + (WINDSHEAR_RE, _handleWindShear, True), + (COLOR_RE, _handleColor, True), + (RUNWAYSTATE_RE, _handleRunwayState, True), + (TREND_RE, _handleTrend, False), + (REMARK_RE, _startRemarks, False) ] - trend_handlers = [ (TRENDTIME_RE, _handleTrend, True), - (WIND_RE, _handleTrend, True), - (VISIBILITY_RE, _handleTrend, True), - (WEATHER_RE, _handleTrend, True), - (SKY_RE, _handleTrend, True), - (COLOR_RE, _handleTrend, True)] + trend_handlers = [ (TRENDTIME_RE, _handleTrend, True), + (WIND_RE, _handleTrend, True), + (VISIBILITY_RE, _handleTrend, True), + (WEATHER_RE, _handleTrend, True), + (SKY_RE, _handleTrend, True), + (COLOR_RE, _handleTrend, True)] - ## the list of patterns for the various remark groups, - ## paired with the handler functions to use to record the decoded remark. + ## the list of patterns for the various remark groups, + ## paired with the handler functions to use to record the decoded remark. - remark_handlers = [ (AUTO_RE, _handleAutoRemark), - (SEALVL_PRESS_RE, _handleSealvlPressRemark), - (PEAK_WIND_RE, _handlePeakWindRemark), - (WIND_SHIFT_RE, _handleWindShiftRemark), - (LIGHTNING_RE, _handleLightningRemark), - (TS_LOC_RE, _handleTSLocRemark), - (TEMP_1HR_RE, _handleTemp1hrRemark), - (PRECIP_1HR_RE, _handlePrecip1hrRemark), - (PRECIP_24HR_RE, _handlePrecip24hrRemark), - (PRESS_3HR_RE, _handlePress3hrRemark), - (TEMP_6HR_RE, _handleTemp6hrRemark), - (TEMP_24HR_RE, _handleTemp24hrRemark), - (UNPARSED_RE, _unparsedRemark) ] - - ## functions that return text representations of conditions for output + remark_handlers = [ (AUTO_RE, _handleAutoRemark), + (SEALVL_PRESS_RE, _handleSealvlPressRemark), + (PEAK_WIND_RE, _handlePeakWindRemark), + (WIND_SHIFT_RE, _handleWindShiftRemark), + (LIGHTNING_RE, _handleLightningRemark), + (TS_LOC_RE, _handleTSLocRemark), + (TEMP_1HR_RE, _handleTemp1hrRemark), + (PRECIP_1HR_RE, _handlePrecip1hrRemark), + (PRECIP_24HR_RE, _handlePrecip24hrRemark), + (PRESS_3HR_RE, _handlePress3hrRemark), + (TEMP_6HR_RE, _handleTemp6hrRemark), + (TEMP_24HR_RE, _handleTemp24hrRemark), + (UNPARSED_RE, _unparsedRemark) ] + + ## functions that return text representations of conditions for output - def string( self ): - """ - Return a human-readable version of the decoded report. - """ - lines = [] - lines.append("station: %s" % self.station_id) - if self.type: - lines.append("type: %s" % self.report_type()) - if self.time: - lines.append("time: %s" % self.time.ctime()) - if self.temp: - lines.append("temperature: %s" % self.temp.string("C")) - if self.dewpt: - lines.append("dew point: %s" % self.dewpt.string("C")) - if self.wind_speed: - lines.append("wind: %s" % self.wind()) - if self.wind_speed_peak: - lines.append("peak wind: %s" % self.peak_wind()) - if self.wind_shift_time: - lines.append("wind shift: %s" % self.wind_shift()) - if self.vis: - lines.append("visibility: %s" % self.visibility()) - if self.runway: - lines.append("visual range: %s" % self.runway_visual_range()) - if self.press: - lines.append("pressure: %s" % self.press.string("mb")) - if self.weather: - lines.append("weather: %s" % self.present_weather()) - if self.sky: - lines.append("sky: %s" % self.sky_conditions("\n ")) - if self.press_sea_level: - lines.append("sea-level pressure: %s" % self.press_sea_level.string("mb")) - if self.max_temp_6hr: - lines.append("6-hour max temp: %s" % str(self.max_temp_6hr)) - if self.max_temp_6hr: - lines.append("6-hour min temp: %s" % str(self.min_temp_6hr)) - if self.max_temp_24hr: - lines.append("24-hour max temp: %s" % str(self.max_temp_24hr)) - if self.max_temp_24hr: - lines.append("24-hour min temp: %s" % str(self.min_temp_24hr)) - if self.precip_1hr: - lines.append("1-hour precipitation: %s" % str(self.precip_1hr)) - if self.precip_3hr: - lines.append("3-hour precipitation: %s" % str(self.precip_3hr)) - if self.precip_6hr: - lines.append("6-hour precipitation: %s" % str(self.precip_6hr)) - if self.precip_24hr: - lines.append("24-hour precipitation: %s" % str(self.precip_24hr)) - if self._remarks: - lines.append("remarks:") - lines.append("- "+self.remarks("\n- ")) - if self._unparsed_remarks: - lines.append("- "+' '.join(self._unparsed_remarks)) - lines.append("METAR: "+self.code) - return string.join(lines,"\n") + def string(self): + """ + Return a human-readable version of the decoded report. + """ + lines = [] + lines.append("station: %s" % self.station_id) + if self.type: + lines.append("type: %s" % self.report_type()) + if self.time: + lines.append("time: %s" % self.time.ctime()) + if self.temp: + lines.append("temperature: %s" % self.temp.string("C")) + if self.dewpt: + lines.append("dew point: %s" % self.dewpt.string("C")) + if self.wind_speed: + lines.append("wind: %s" % self.wind()) + if self.wind_speed_peak: + lines.append("peak wind: %s" % self.peak_wind()) + if self.wind_shift_time: + lines.append("wind shift: %s" % self.wind_shift()) + if self.vis: + lines.append("visibility: %s" % self.visibility()) + if self.runway: + lines.append("visual range: %s" % self.runway_visual_range()) + if self.press: + lines.append("pressure: %s" % self.press.string("mb")) + if self.weather: + lines.append("weather: %s" % self.present_weather()) + if self.sky: + lines.append("sky: %s" % self.sky_conditions("\n ")) + if self.press_sea_level: + lines.append("sea-level pressure: %s" % self.press_sea_level.string("mb")) + if self.max_temp_6hr: + lines.append("6-hour max temp: %s" % str(self.max_temp_6hr)) + if self.max_temp_6hr: + lines.append("6-hour min temp: %s" % str(self.min_temp_6hr)) + if self.max_temp_24hr: + lines.append("24-hour max temp: %s" % str(self.max_temp_24hr)) + if self.max_temp_24hr: + lines.append("24-hour min temp: %s" % str(self.min_temp_24hr)) + if self.precip_1hr: + lines.append("1-hour precipitation: %s" % str(self.precip_1hr)) + if self.precip_3hr: + lines.append("3-hour precipitation: %s" % str(self.precip_3hr)) + if self.precip_6hr: + lines.append("6-hour precipitation: %s" % str(self.precip_6hr)) + if self.precip_24hr: + lines.append("24-hour precipitation: %s" % str(self.precip_24hr)) + if self._remarks: + lines.append("remarks:") + lines.append("- "+self.remarks("\n- ")) + if self._unparsed_remarks: + lines.append("- "+' '.join(self._unparsed_remarks)) + lines.append("METAR: "+self.code) + return string.join(lines,"\n") - def report_type( self ): - """ - Return a textual description of the report type. - """ - if self.type == None: - text = "unknown report type" - elif REPORT_TYPE.has_key(self.type): - text = REPORT_TYPE[self.type] - else: - text = self.type+" report" - if self.cycle: - text += ", cycle %d" % self.cycle - if self.mod: - if REPORT_TYPE.has_key(self.mod): - text += " (%s)" % REPORT_TYPE[self.mod] - else: - text += " (%s)" % self.mod - return text + def report_type(self): + """ + Return a textual description of the report type. + """ + if self.type == None: + text = "unknown report type" + elif REPORT_TYPE.has_key(self.type): + text = REPORT_TYPE[self.type] + else: + text = self.type+" report" + if self.cycle: + text += ", cycle %d" % self.cycle + if self.mod: + if REPORT_TYPE.has_key(self.mod): + text += " (%s)" % REPORT_TYPE[self.mod] + else: + text += " (%s)" % self.mod + return text - def wind( self, units="KT" ): - """ - Return a textual description of the wind conditions. - - Units may be specified as "MPS", "KT", "KMH", or "MPH". - """ - if self.wind_speed == None: - return "missing" - elif self.wind_speed.value() == 0.0: - text = "calm" - else: - wind_speed = self.wind_speed.string(units) - if not self.wind_dir: - text = "variable at %s" % wind_speed - elif self.wind_dir_from: - text = "%s to %s at %s" % \ - (self.wind_dir_from.compass(), self.wind_dir_to.compass(), wind_speed) - else: - text = "%s at %s" % (self.wind_dir.compass(), wind_speed) - if self.wind_gust: - text += ", gusting to %s" % self.wind_gust.string(units) - return text + def wind(self, units="KT"): + """ + Return a textual description of the wind conditions. + + Units may be specified as "MPS", "KT", "KMH", or "MPH". + """ + if self.wind_speed == None: + return "missing" + elif self.wind_speed.value() == 0.0: + text = "calm" + else: + wind_speed = self.wind_speed.string(units) + if not self.wind_dir: + text = "variable at %s" % wind_speed + elif self.wind_dir_from: + text = "%s to %s at %s" % \ + (self.wind_dir_from.compass(), self.wind_dir_to.compass(), wind_speed) + else: + text = "%s at %s" % (self.wind_dir.compass(), wind_speed) + if self.wind_gust: + text += ", gusting to %s" % self.wind_gust.string(units) + return text - def peak_wind( self, units="KT" ): - """ - Return a textual description of the peak wind conditions. - - Units may be specified as "MPS", "KT", "KMH", or "MPH". - """ - if self.wind_speed_peak == None: - return "missing" - elif self.wind_speed_peak.value() == 0.0: - text = "calm" - else: - wind_speed = self.wind_speed_peak.string(units) - if not self.wind_dir_peak: - text = wind_speed - else: - text = "%s at %s" % (self.wind_dir_peak.compass(), wind_speed) - if not self.peak_wind_time == None: - text += " at %s" % self.peak_wind_time.strftime('%H:%M') - return text + def peak_wind(self, units="KT"): + """ + Return a textual description of the peak wind conditions. + + Units may be specified as "MPS", "KT", "KMH", or "MPH". + """ + if self.wind_speed_peak == None: + return "missing" + elif self.wind_speed_peak.value() == 0.0: + text = "calm" + else: + wind_speed = self.wind_speed_peak.string(units) + if not self.wind_dir_peak: + text = wind_speed + else: + text = "%s at %s" % (self.wind_dir_peak.compass(), wind_speed) + if not self.peak_wind_time == None: + text += " at %s" % self.peak_wind_time.strftime('%H:%M') + return text - def wind_shift( self, units="KT" ): - """ - Return a textual description of the wind shift time - - Units may be specified as "MPS", "KT", "KMH", or "MPH". - """ - if self.wind_shift_time == None: - return "missing" - else: - return self.wind_shift_time.strftime('%H:%M') + def wind_shift(self, units="KT"): + """ + Return a textual description of the wind shift time - def visibility( self, units=None ): - """ - Return a textual description of the visibility. - - Units may be statute miles ("SM") or meters ("M"). - """ - if self.vis == None: - return "missing" - if self.vis_dir: - text = "%s to %s" % (self.vis.string(units), self.vis_dir.compass()) - else: - text = self.vis.string(units) - if self.max_vis: - if self.max_vis_dir: - text += "; %s to %s" % (self.max_vis.string(units), self.max_vis_dir.compass()) - else: - text += "; %s" % self.max_vis.string(units) - return text + Units may be specified as "MPS", "KT", "KMH", or "MPH". + """ + if self.wind_shift_time == None: + return "missing" + else: + return self.wind_shift_time.strftime('%H:%M') + + def visibility(self, units=None): + """ + Return a textual description of the visibility. + + Units may be statute miles ("SM") or meters ("M"). + """ + if self.vis == None: + return "missing" + if self.vis_dir: + text = "%s to %s" % (self.vis.string(units), self.vis_dir.compass()) + else: + text = self.vis.string(units) + if self.max_vis: + if self.max_vis_dir: + text += "; %s to %s" % (self.max_vis.string(units), self.max_vis_dir.compass()) + else: + text += "; %s" % self.max_vis.string(units) + return text - def runway_visual_range( self, units=None ): - """ - Return a textual description of the runway visual range. - """ - lines = [] - for name,low,high in self.runway: - if low != high: - lines.append("on runway %s, from %d to %s" % (name, low.value(units), high.string(units))) - else: - lines.append("on runway %s, %s" % (name, low.string(units))) - return string.join(lines,"; ") + def runway_visual_range(self, units=None): + """ + Return a textual description of the runway visual range. + """ + lines = [] + for name,low,high in self.runway: + if low != high: + lines.append("on runway %s, from %d to %s" % (name, low.value(units), high.string(units))) + else: + lines.append("on runway %s, %s" % (name, low.string(units))) + return string.join(lines,"; ") - def present_weather( self ): - """ - Return a textual description of the present weather. - """ - text_list = [] - for weatheri in self.weather: - (inteni,desci,preci,obsci,otheri) = weatheri - text_parts = [] - code_parts = [] - if inteni: - code_parts.append(inteni) - text_parts.append(WEATHER_INT[inteni]) - if desci: - code_parts.append(desci) - if desci != "SH" or not preci: - text_parts.append(WEATHER_DESC[desci[0:2]]) - if len(desci) == 4: - text_parts.append(WEATHER_DESC[desci[2:]]) - if preci: - code_parts.append(preci) - if len(preci) == 2: - precip_text = WEATHER_PREC[preci] - elif len(preci) == 4: - precip_text = WEATHER_PREC[preci[:2]]+" and " - precip_text += WEATHER_PREC[preci[2:]] - elif len(preci) == 6: - precip_text = WEATHER_PREC[preci[:2]]+", " - precip_text += WEATHER_PREC[preci[2:4]]+" and " - precip_text += WEATHER_PREC[preci[4:]] - if desci == "TS": - text_parts.append("with") - text_parts.append(precip_text) - if desci == "SH": - text_parts.append(WEATHER_DESC[desci]) - if obsci: - code_parts.append(obsci) - text_parts.append(WEATHER_OBSC[obsci]) + def present_weather(self): + """ + Return a textual description of the present weather. + """ + text_list = [] + for weatheri in self.weather: + (inteni,desci,preci,obsci,otheri) = weatheri + text_parts = [] + code_parts = [] + if inteni: + code_parts.append(inteni) + text_parts.append(WEATHER_INT[inteni]) + if desci: + code_parts.append(desci) + if desci != "SH" or not preci: + text_parts.append(WEATHER_DESC[desci[0:2]]) + if len(desci) == 4: + text_parts.append(WEATHER_DESC[desci[2:]]) + if preci: + code_parts.append(preci) + if len(preci) == 2: + precip_text = WEATHER_PREC[preci] + elif len(preci) == 4: + precip_text = WEATHER_PREC[preci[:2]]+" and " + precip_text += WEATHER_PREC[preci[2:]] + elif len(preci) == 6: + precip_text = WEATHER_PREC[preci[:2]]+", " + precip_text += WEATHER_PREC[preci[2:4]]+" and " + precip_text += WEATHER_PREC[preci[4:]] + if desci == "TS": + text_parts.append("with") + text_parts.append(precip_text) + if desci == "SH": + text_parts.append(WEATHER_DESC[desci]) + if obsci: + code_parts.append(obsci) + text_parts.append(WEATHER_OBSC[obsci]) - if otheri: - code_parts.append(otheri) - text_parts.append(WEATHER_OTHER[otheri]) - code = string.join(code_parts) - if WEATHER_SPECIAL.has_key(code): - text_list.append(WEATHER_SPECIAL[code]) - else: - text_list.append(string.join(text_parts," ")) - return string.join(text_list,"; ") + if otheri: + code_parts.append(otheri) + text_parts.append(WEATHER_OTHER[otheri]) + code = string.join(code_parts) + if WEATHER_SPECIAL.has_key(code): + text_list.append(WEATHER_SPECIAL[code]) + else: + text_list.append(string.join(text_parts," ")) + return string.join(text_list,"; ") - def sky_conditions( self, sep="; " ): - """ - Return a textual description of the sky conditions. - """ - text_list = [] - for skyi in self.sky: - (cover,height,cloud) = skyi - if cover == "SKC" or cover == "CLR": - text_list.append(SKY_COVER[cover]) - else: - if cloud: - what = CLOUD_TYPE[cloud] - elif cover != "OVC": - what = "clouds" - else: - what = "" - if cover == "VV": - text_list.append("%s%s, visibility to %s" % + def sky_conditions( self, sep="; " ): + """ + Return a textual description of the sky conditions. + """ + text_list = [] + for skyi in self.sky: + (cover,height,cloud) = skyi + if cover == "SKC" or cover == "CLR": + text_list.append(SKY_COVER[cover]) + else: + if cloud: + what = CLOUD_TYPE[cloud] + elif cover != "OVC": + what = "clouds" + else: + what = "" + if cover == "VV": + text_list.append("%s%s, visibility to %s" % \ (SKY_COVER[cover],what,str(height))) - else: - text_list.append("%s%s at %s" % + else: + text_list.append("%s%s at %s" % (SKY_COVER[cover],what,str(height))) - return string.join(text_list,sep) + return string.join(text_list,sep) - def trend( self ): - """ - Return the trend forecast groups - """ - return " ".join(self._trend_groups) + def trend(self): + """ + Return the trend forecast groups + """ + return " ".join(self._trend_groups) - def remarks( self, sep="; "): - """ - Return the decoded remarks. - """ - return string.join(self._remarks,sep) + def remarks(self, sep="; "): + """ + Return the decoded remarks. + """ + return string.join(self._remarks,sep) diff --git a/metar/Station.py b/metar/Station.py index 374dc19c..a2ba69ef 100755 --- a/metar/Station.py +++ b/metar/Station.py @@ -8,31 +8,31 @@ from datatypes import position, distance, direction class station: - """An object representing a weather station.""" + """An object representing a weather station.""" - def __init__( self, id, city=None, state=None, country=None, latitude=None, longitude=None): - self.id = id - self.city = city - self.state = state - self.country = country - self.position = position(latitude,longitude) - if self.state: - self.name = "%s, %s" % (self.city, self.state) - else: - self.name = self.city + def __init__(self, id, city=None, state=None, + country=None, latitude=None, longitude=None): + self.id = id + self.city = city + self.state = state + self.country = country + self.position = position(latitude,longitude) + if self.state: + self.name = "%s, %s" % (self.city, self.state) + else: + self.name = self.city station_file_name = "nsd_cccc.txt" station_file_url = "http://www.noaa.gov/nsd_cccc.txt" - stations = {} fh = open(station_file_name,'r') for line in fh: - f = line.strip().split(";") - stations[f[0]] = station(f[0],f[3],f[4],f[5],f[7],f[8]) + f = line.strip().split(";") + stations[f[0]] = station(f[0],f[3],f[4],f[5],f[7],f[8]) fh.close() if __name__ == "__main__": - for id in [ 'KEWR', 'KIAD', 'KIWI', 'EKRK' ]: - print id, stations[id].name, stations[id].country + for id in [ 'KEWR', 'KIAD', 'KIWI', 'EKRK' ]: + print id, stations[id].name, stations[id].country diff --git a/misc/getstation.py b/misc/getstation.py index ba6a5558..5a4a3bc5 100755 --- a/misc/getstation.py +++ b/misc/getstation.py @@ -9,15 +9,17 @@ BASE_URL = "http://weather.noaa.gov/pub/data/observations/metar/stations" def usage(): - print "Usage: $0 " + print("Usage: $0 ") + def usage(): - program = os.path.basename(sys.argv[0]) - print "Usage: ",program,"[-p] [ ... ]" - print """Options: - . a four-letter ICAO station code (e.g., "KEWR") - -p ........ send downloaded data to stdout, ratherthan a file. -""" - sys.exit(1) + program = os.path.basename(sys.argv[0]) + print("Usage: %s [-p] [ ... ]" % (program, )) + options = """Options: + . a four-letter ICAO station code (e.g., "KEWR") + -p ........ send downloaded data to stdout, ratherthan a file. + """ + print(options) + sys.exit(1) today = datetime.datetime.utcnow() @@ -26,42 +28,43 @@ def usage(): debug = False try: - opts, stations = getopt.getopt(sys.argv[1:], 'dp') - for opt in opts: - if opt[0] == '-p': - pipe = True - elif opt[0] == '-d': - debug = True + opts, stations = getopt.getopt(sys.argv[1:], 'dp') + for opt in opts: + if opt[0] == '-p': + pipe = True + elif opt[0] == '-d': + debug = True except: - usage() + usage() if not stations: - usage() + usage() for name in stations: - url = "%s/%s.TXT" % (BASE_URL, name) - if debug: sys.stderr.write("[ "+url+" ]") - try: - urlh = urllib.urlopen(url) - for line in urlh: - if line.startswith(name): - report = line.strip() - groups = report.split() - if groups[1].endswith('Z'): - date_str = "%02d%02d%s-%s" % (today.year-2000, today.month, - groups[1][:2], groups[1][2:]) - else: - date_str = "%02d%02d%02d-%02d%02dZ" % (today.year-2000, - today.month, today.day, today.hour, today.minute) - break - except: - print "Error retrieving",name,"data" - continue - if pipe: - print report+"\n" - else: - file = "%s-%s.metar" % (name,date_str) - print "Saving current METAR report for station",name,"in",file - fileh = open(file,'w') - fileh.write(report+"\n") - fileh.close() + url = "%s/%s.TXT" % (BASE_URL, name) + if debug: sys.stderr.write("[ "+url+" ]") + + try: + urlh = urllib.urlopen(url) + for line in urlh: + if line.startswith(name): + report = line.strip() + groups = report.split() + if groups[1].endswith('Z'): + date_str = "%02d%02d%s-%s" % (today.year-2000, today.month, + groups[1][:2], groups[1][2:]) + else: + date_str = "%02d%02d%02d-%02d%02dZ" % (today.year-2000, + today.month, today.day, today.hour, today.minute) + break + except: + print("Error retrieving %s data" % name) + continue + if pipe: + print(report+"\n") + else: + file = "%s-%s.metar" % (name,date_str) + print("Saving current METAR report for station %s in %s" % (name, file)) + fileh = open(file,'w') + fileh.write(report+"\n") + fileh.close() diff --git a/parse_metar.py b/parse_metar.py index 2cdf1113..a5302779 100755 --- a/parse_metar.py +++ b/parse_metar.py @@ -9,9 +9,9 @@ import profile, pstats def usage(): - program = os.path.basename(sys.argv[0]) - print "Usage: ",program,"[-s] []" - print """Options: + program = os.path.basename(sys.argv[0]) + print("Usage: %s [-s] []" % program) + options = """Options: ... a file containing METAR reports to parse -q ....... run "quietly" - just report parsing error. -s ....... run silently. (no output) @@ -21,7 +21,8 @@ def usage(): file is given. For testing purposes, the script can run silently, reporting only when a METAR report can't be fully parsed. """ - sys.exit(1) + print(options) + sys.exit(1) files = [] silent = False @@ -30,59 +31,59 @@ def usage(): prof = False try: - opts, files = getopt.getopt(sys.argv[1:], 'dpqs') - for opt in opts: - if opt[0] == '-s': - silent = True - report = False - elif opt[0] == '-q': - report = False - elif opt[0] == '-d': - debug = True - Metar.debug = True - elif opt[0] == '-p': - prof = True + opts, files = getopt.getopt(sys.argv[1:], 'dpqs') + for opt in opts: + if opt[0] == '-s': + silent = True + report = False + elif opt[0] == '-q': + report = False + elif opt[0] == '-d': + debug = True + Metar.debug = True + elif opt[0] == '-p': + prof = True except: usage() def process_line(line): - """Decode a single input line.""" - line = line.strip() - if len(line) and line[0] in string.uppercase: - try: - obs = Metar.Metar(line) - if report: - print "--------------------" - print obs.string() - except Metar.ParserError, err: - if not silent: - print "--------------------" - print "METAR code: ",line - print string.join(err.args,", ") + """Decode a single input line.""" + line = line.strip() + if len(line) and line[0] in string.uppercase: + try: + obs = Metar.Metar(line) + if report: + print("--------------------") + print(obs.string()) + except Metar.ParserError, err: + if not silent: + print("--------------------") + print("METAR code: %s" % line) + print(string.join(err.args,", ")) def process_files(files): - """Decode METAR lines from the given files.""" - for file in files: - fh = open(file,"r") - for line in fh.readlines(): - process_line(line) + """Decode METAR lines from the given files.""" + for file in files: + fh = open(file,"r") + for line in fh.readlines(): + process_line(line) if files: - if prof: - profile.run('process_files(files)') - else: - process_files(files) + if prof: + profile.run('process_files(files)') + else: + process_files(files) else: - # read lines from stdin - while True: - try: - line = sys.stdin.readline() - if line == "": - break - process_line(line) - except KeyboardInterrupt: - break + # read lines from stdin + while True: + try: + line = sys.stdin.readline() + if line == "": + break + process_line(line) + except KeyboardInterrupt: + break if prof: - ps = pstats.load('metar.prof') - print ps.strip_dirs().sort_stats('time').print_stats() + ps = pstats.load('metar.prof') + print(ps.strip_dirs().sort_stats('time').print_stats()) diff --git a/sample.py b/sample.py index a2a7bfee..9150a147 100755 --- a/sample.py +++ b/sample.py @@ -82,9 +82,9 @@ # A sample METAR report code = "METAR KEWR 111851Z VRB03G19KT 2SM R04R/3000VP6000FT TSRA BR FEW015 BKN040CB BKN065 OVC200 22/22 A2987 RMK AO2 PK WND 29028/1817 WSHFT 1812 TSB05RAB22 SLP114 FRQ LTGICCCCG TS OHD AND NW-N-E MOV NE P0013 T02270215" -print "-----------------------------------------------------------------------" -print "METAR: ",code -print "-----------------------------------------------------------------------" +print("-----------------------------------------------------------------------") +print("METAR: %s" % code) +print("-----------------------------------------------------------------------") # Initialize a Metar object with the coded report obs = Metar.Metar(code) @@ -92,61 +92,61 @@ # Print the individual data # The 'station_id' attribute is a string. -print "station: %s" % obs.station_id +print("station: %s" % obs.station_id) if obs.type: - print "type: %s" % obs.report_type() + print("type: %s" % obs.report_type()) # The 'time' attribute is a datetime object if obs.time: - print "time: %s" % obs.time.ctime() + print("time: %s" % obs.time.ctime()) # The 'temp' and 'dewpt' attributes are temperature objects if obs.temp: - print "temperature: %s" % obs.temp.string("C") + print("temperature: %s" % obs.temp.string("C")) if obs.dewpt: - print "dew point: %s" % obs.dewpt.string("C") + print("dew point: %s" % obs.dewpt.string("C")) # The wind() method returns a string describing wind observations # which may include speed, direction, variability and gusts. if obs.wind_speed: - print "wind: %s" % obs.wind() + print("wind: %s" % obs.wind()) # The peak_wind() method returns a string describing the peak wind # speed and direction. if obs.wind_speed_peak: - print "wind: %s" % obs.peak_wind() + print("wind: %s" % obs.peak_wind()) # The visibility() method summarizes the visibility observation. if obs.vis: - print "visibility: %s" % obs.visibility() + print("visibility: %s" % obs.visibility()) # The runway_visual_range() method summarizes the runway visibility # observations. if obs.runway: - print "visual range: %s" % obs.runway_visual_range() + print("visual range: %s" % obs.runway_visual_range()) # The 'press' attribute is a pressure object. if obs.press: - print "pressure: %s" % obs.press.string("mb") + print("pressure: %s" % obs.press.string("mb")) # The 'precip_1hr' attribute is a precipitation object. if obs.precip_1hr: - print "precipitation: %s" % obs.precip_1hr.string("in") + print("precipitation: %s" % obs.precip_1hr.string("in")) # The present_weather() method summarizes the weather description (rain, etc.) -print "weather: %s" % obs.present_weather() +print("weather: %s" % obs.present_weather()) # The sky_conditions() method summarizes the cloud-cover observations. -print "sky: %s" % obs.sky_conditions("\n ") +print("sky: %s" % obs.sky_conditions("\n ")) # The remarks() method describes the remark groups that were parsed, but # are not available directly as Metar attributes. The precipitation, # min/max temperature and peak wind remarks, for instance, are stored as # attributes and won't be listed here. if obs._remarks: - print "remarks:" - print "- "+obs.remarks("\n- ") + print("remarks:") + print("- "+obs.remarks("\n- ")) -print "-----------------------------------------------------------------------\n" +print("-----------------------------------------------------------------------\n") diff --git a/setup.py b/setup.py index 92f0c0c0..9f5def64 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ setup( name="metar", - version="1.4.0", + version="1.4.1", author="Tom Pollard", author_email="pollard@alum.mit.edu", url="http://python-metar.sourceforge.net/", @@ -45,7 +45,7 @@ download_url="http://sourceforge.net/project/platformdownload.php?group_id=134052", license="MIT", packages=["metar"], - platforms="Python 2.5 and later.", + platforms="Python 2.6 and later.", classifiers=[ "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: MIT License", From cd1f225916f191f238fe0de7163db63184e48cf6 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 8 Nov 2011 11:00:27 -0800 Subject: [PATCH 2/4] fixed case in Datatypes import statement --- .gitignore | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e69de29b From ae9b75d5a50508d70b2f6e4bb6c4c638a47ccd5f Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Wed, 30 May 2012 14:28:11 -0700 Subject: [PATCH 3/4] fixed whitespace --- metar/Datatypes.py | 52 +++++------ metar/Metar.py | 216 ++++++++++++++++++++++----------------------- metar/Station.py | 6 +- metar/__init__.py | 4 +- 4 files changed, 140 insertions(+), 138 deletions(-) diff --git a/metar/Datatypes.py b/metar/Datatypes.py index 5cf180fc..b1df679b 100644 --- a/metar/Datatypes.py +++ b/metar/Datatypes.py @@ -16,13 +16,13 @@ class UnitsError(Exception): ## [Note: numerator of fraction must be single digit.] FRACTION_RE = re.compile(r"^((?P\d+)\s*)?(?P\d)/(?P\d+)$") - + ## classes representing dimensioned values in METAR reports - + class temperature(object): """A class representing a temperature value.""" legal_units = [ "F", "C", "K" ] - + def __init__( self, value, units="C" ): if not units.upper() in temperature.legal_units: raise UnitsError("unrecognized temperature unit: '"+units+"'") @@ -37,7 +37,7 @@ def __init__( self, value, units="C" ): def __str__(self): return self.string() - + def value( self, units=None ): """Return the temperature in the specified units.""" if units == None: @@ -58,7 +58,7 @@ def value( self, units=None ): return 273.15+celsius_value elif units == "F": return 32.0+celsius_value*1.8 - + def string( self, units=None ): """Return a string representation of the temperature, using the given units.""" if units == None: @@ -78,7 +78,7 @@ def string( self, units=None ): class pressure(object): """A class representing a barometric pressure value.""" legal_units = [ "MB", "HPA", "IN" ] - + def __init__( self, value, units="MB" ): if not units.upper() in pressure.legal_units: raise UnitsError("unrecognized pressure unit: '"+units+"'") @@ -87,7 +87,7 @@ def __init__( self, value, units="MB" ): def __str__(self): return self.string() - + def value( self, units=None ): """Return the pressure in the specified units.""" if units == None: @@ -108,7 +108,7 @@ def value( self, units=None ): return mb_value/33.86398 else: raise UnitsError("unrecognized pressure unit: '"+units+"'") - + def string( self, units=None ): """Return a string representation of the pressure, using the given units.""" if not units: @@ -129,7 +129,7 @@ class speed(object): """A class representing a wind speed value.""" legal_units = [ "KT", "MPS", "KMH", "MPH" ] legal_gtlt = [ ">", "<" ] - + def __init__( self, value, units=None, gtlt=None ): if not units: self._units = "MPS" @@ -144,7 +144,7 @@ def __init__( self, value, units=None, gtlt=None ): def __str__(self): return self.string() - + def value( self, units=None ): """Return the pressure in the specified units.""" if not units: @@ -171,7 +171,7 @@ def value( self, units=None ): return mps_value/0.447000 elif units == "MPS": return mps_value - + def string( self, units=None ): """Return a string representation of the speed in the given units.""" if not units: @@ -200,7 +200,7 @@ class distance(object): """A class representing a distance value.""" legal_units = [ "SM", "MI", "M", "KM", "FT" ] legal_gtlt = [ ">", "<" ] - + def __init__( self, value, units=None, gtlt=None ): if not units: self._units = "M" @@ -208,7 +208,7 @@ def __init__( self, value, units=None, gtlt=None ): if not units.upper() in distance.legal_units: raise UnitsError("unrecognized distance unit: '"+units+"'") self._units = units.upper() - + try: if value.startswith('M'): value = value[1:] @@ -238,7 +238,7 @@ def __init__( self, value, units=None, gtlt=None ): def __str__(self): return self.string() - + def value( self, units=None ): """Return the distance in the specified units.""" if not units: @@ -265,7 +265,7 @@ def value( self, units=None ): return m_value/1000 elif units == "M": return m_value - + def string( self, units=None ): """Return a string representation of the distance in the given units.""" if not units: @@ -302,8 +302,8 @@ def string( self, units=None ): class direction(object): """A class representing a compass direction.""" - - compass_dirs = { "N": 0.0, "NNE": 22.5, "NE": 45.0, "ENE": 67.5, + + compass_dirs = { "N": 0.0, "NNE": 22.5, "NE": 45.0, "ENE": 67.5, "E": 90.0, "ESE":112.5, "SE":135.0, "SSE":157.5, "S":180.0, "SSW":202.5, "SW":225.0, "WSW":247.5, "W":270.0, "WNW":292.5, "NW":315.0, "NNW":337.5 } @@ -321,15 +321,15 @@ def __init__( self, d ): def __str__(self): return self.string() - + def value( self ): """Return the numerical direction, in degrees.""" return self._degrees - + def string( self ): """Return a string representation of the numerical direction.""" return "%.0f degrees" % self._degrees - + def compass( self ): """Return the compass direction, e.g., "N", "ESE", etc.).""" if not self._compass: @@ -348,7 +348,7 @@ class precipitation(object): """A class representing a precipitation value.""" legal_units = [ "IN", "CM" ] legal_gtlt = [ ">", "<" ] - + def __init__( self, value, units=None, gtlt=None ): if not units: self._units = "IN" @@ -356,7 +356,7 @@ def __init__( self, value, units=None, gtlt=None ): if not units.upper() in precipitation.legal_units: raise UnitsError("unrecognized precipitation unit: '"+units+"'") self._units = units.upper() - + try: if value.startswith('M'): value = value[1:] @@ -373,7 +373,7 @@ def __init__( self, value, units=None, gtlt=None ): def __str__(self): return self.string() - + def value( self, units=None ): """Return the precipitation in the specified units.""" if not units: @@ -392,7 +392,7 @@ def value( self, units=None ): return i_value*2.54 else: return i_value - + def string( self, units=None ): """Return a string representation of the precipitation in the given units.""" if not units: @@ -415,14 +415,14 @@ def string( self, units=None ): class position(object): """A class representing a location on the earth's surface.""" - + def __init__( self, latitude=None, longitude=None ): self.latitude = latitude self.longitude = longitude def __str__(self): return self.string() - + def getdistance( self, position2 ): """ Calculate the great-circle distance to another location using the Haversine diff --git a/metar/Metar.py b/metar/Metar.py index 384a63ee..f7cde7b1 100755 --- a/metar/Metar.py +++ b/metar/Metar.py @@ -85,11 +85,11 @@ class ParserError(Exception): VISIBILITY_RE = re.compile(r"""^(?P(?P(M|P)?\d\d\d\d|////) (?P[NSEW][EW]? | NDV)? | (?P(M|P)?(\d+|\d\d?/\d\d?|\d+\s+\d/\d)) - (?PSM|KM|M|U) | + (?PSM|KM|M|U) | CAVOK )\s+""", re.VERBOSE) # end patch -RUNWAY_RE = re.compile(r"""^(RVRNO | +RUNWAY_RE = re.compile(r"""^(RVRNO | R(?P\d\d(RR?|LL?|C)?)/ (?P(M|P)?\d\d\d\d) (V(?P(M|P)?\d\d\d\d))? @@ -175,28 +175,28 @@ class ParserError(Exception): LIGHTNING_RE = re.compile(r"""^((?POCNL|FRQ|CONS)\s+)? LTG(?P(IC|CC|CG|CA)*) - ( \s+(?P( OHD | VC | DSNT\s+ | \s+AND\s+ | + ( \s+(?P( OHD | VC | DSNT\s+ | \s+AND\s+ | [NSEW][EW]? (-[NSEW][EW]?)* )+) )?\s+""", re.VERBOSE) - -TS_LOC_RE = re.compile(r"""TS(\s+(?P( OHD | VC | DSNT\s+ | \s+AND\s+ | + +TS_LOC_RE = re.compile(r"""TS(\s+(?P( OHD | VC | DSNT\s+ | \s+AND\s+ | [NSEW][EW]? (-[NSEW][EW]?)* )+))? ( \s+MOV\s+(?P[NSEW][EW]?) )?\s+""", re.VERBOSE) ## translation of weather location codes -loc_terms = [ ("OHD", "overhead"), +loc_terms = [ ("OHD", "overhead"), ("DSNT", "distant"), ("AND", "and"), ("VC", "nearby") ] - + def xlate_loc( loc ): """Substitute English terms for the location codes in the given string.""" for code, english in loc_terms: loc = loc.replace(code,english) return loc - + ## translation of the sky-condition codes into english SKY_COVER = { "SKC":"clear", @@ -209,7 +209,7 @@ def xlate_loc( loc ): "OVC":"overcast", "///":"", "VV":"indefinite ceiling" } - + CLOUD_TYPE = { "TCU":"towering cumulus", "CU":"cumulus", "CB":"cumulonimbus", @@ -219,18 +219,18 @@ def xlate_loc( loc ): "SCSL":"standing lenticular stratocumulus", "CCSL":"standing lenticular cirrocumulus", "ACSL":"standing lenticular altocumulus" } - + ## translation of the present-weather codes into english -WEATHER_INT = { "-":"light", - "+":"heavy", - "-VC":"nearby light", - "+VC":"nearby heavy", +WEATHER_INT = { "-":"light", + "+":"heavy", + "-VC":"nearby light", + "+VC":"nearby heavy", "VC":"nearby" } WEATHER_DESC = { "MI":"shallow", "PR":"partial", - "BC":"patches of", - "DR":"low drifting", + "BC":"patches of", + "DR":"low drifting", "BL":"blowing", "SH":"showers", "TS":"thunderstorm", @@ -264,12 +264,12 @@ def xlate_loc( loc ): COLOR = { "BLU":"blue", "GRN":"green", "WHT":"white" } - + ## translation of various remark codes into English - + PRESSURE_TENDENCY = { "0":"increasing, then decreasing", "1":"increasing more slowly", - "2":"increasing", + "2":"increasing", "3":"increasing more quickly", "4":"steady", "5":"decreasing, then increasing", @@ -298,20 +298,20 @@ def _report_match(handler,match): print handler.__name__," matched '"+match+"'" else: print handler.__name__," didn't match..." - + def _unparsedGroup( self, d ): """ Handle otherwise unparseable main-body groups. """ self._unparsed_groups.append(d['group']) - + ## METAR report objects debug = False class Metar(object): """METAR (aviation meteorology report)""" - + def __init__( self, metarcode, month=None, year=None, utcdelta=None): """Parse raw METAR code.""" self.code = metarcode # original METAR code @@ -355,7 +355,7 @@ def __init__( self, metarcode, month=None, year=None, utcdelta=None): self._remarks = [] # remarks (list of strings) self._unparsed_groups = [] self._unparsed_remarks = [] - + self._now = datetime.datetime.utcnow() if utcdelta: self._utcdelta = utcdelta @@ -364,13 +364,13 @@ def __init__( self, metarcode, month=None, year=None, utcdelta=None): self._month = month self._year = year - + code = self.code+" " # (the regexps all expect trailing spaces...) try: ngroup = len(Metar.handlers) igroup = 0 ifailed = -1 - while igroup < ngroup and code: + while igroup < ngroup and code: pattern, handler, repeatable = Metar.handlers[igroup] if debug: print handler.__name__,":",code m = pattern.match(code) @@ -382,7 +382,7 @@ def __init__( self, metarcode, month=None, year=None, utcdelta=None): if self._trend: code = self._do_trend_handlers(code) if not repeatable: break - + if debug: print handler.__name__,":",code m = pattern.match(code) if not m and ifailed < 0: @@ -429,44 +429,44 @@ def _do_trend_handlers(self, code): if not repeatable: break m = pattern.match(code) return code - + def __str__(self): return self.string() def _handleType( self, d ): """ Parse the code-type group. - + The following attributes are set: type [string] """ - self.type = d['type'] - + self.type = d['type'] + def _handleStation( self, d ): """ Parse the station id group. - + The following attributes are set: station_id [string] """ - self.station_id = d['station'] - + self.station_id = d['station'] + def _handleModifier( self, d ): """ Parse the report-modifier group. - + The following attributes are set: mod [string] """ - mod = d['mod'] + mod = d['mod'] if mod == 'CORR': mod = 'COR' if mod == 'NIL' or mod == 'FINO': mod = 'NO DATA' self.mod = mod - + def _handleTime( self, d ): """ Parse the observation-time group. - + The following attributes are set: time [datetime] cycle [int] @@ -475,9 +475,9 @@ def _handleTime( self, d ): _min [int] """ self._day = int(d['day']) - if not self._month: + if not self._month: self._month = self._now.month - if self._day > self._now.day: + if self._day > self._now.day: if self._month == 1: self._month = 12 else: @@ -496,11 +496,11 @@ def _handleTime( self, d ): self.cycle = self._hour else: self.cycle = self._hour+1 - + def _handleWind( self, d ): """ Parse the wind and variable-wind groups. - + The following attributes are set: wind_dir [direction] wind_speed [speed] @@ -510,10 +510,10 @@ def _handleWind( self, d ): """ wind_dir = d['dir'].replace('O','0') if wind_dir != "VRB" and wind_dir != "///" and wind_dir != "MMM": - self.wind_dir = direction(wind_dir) + self.wind_dir = direction(wind_dir) wind_speed = d['speed'].replace('O','0') units = d['units'] - if units == 'KTS' or units == 'K' or units == 'T' or units == 'LT': + if units == 'KTS' or units == 'K' or units == 'T' or units == 'LT': units = 'KT' if wind_speed.startswith("P"): self.wind_speed = speed(wind_speed[1:], units, ">") @@ -527,12 +527,12 @@ def _handleWind( self, d ): self.wind_gust = speed(wind_gust, units) if d['varfrom']: self.wind_dir_from = direction(d['varfrom']) - self.wind_dir_to = direction(d['varto']) - + self.wind_dir_to = direction(d['varto']) + def _handleVisibility( self, d ): """ Parse the minimum and maximum visibility groups. - + The following attributes are set: vis [distance] vis_dir [direction] @@ -547,7 +547,7 @@ def _handleVisibility( self, d ): if d['dist'] and d['dist'] != '////': vis_dist = d['dist'] if d['dir'] and d['dir'] != 'NDV': - vis_dir = d['dir'] + vis_dir = d['dir'] elif d['distu']: vis_dist = d['distu'] if d['units'] and d['units'] != "U": @@ -563,11 +563,11 @@ def _handleVisibility( self, d ): if vis_dir: self.vis_dir = direction(vis_dir) self.vis = distance(vis_dist, vis_units, vis_less) - + def _handleRunway( self, d ): """ Parse a runway visual range group. - + The following attributes are set: range [list of tuples] . name [string] @@ -582,11 +582,11 @@ def _handleRunway( self, d ): else: high = low self.runway.append((name,low,high)) - + def _handleWeather( self, d ): """ Parse a present-weather group. - + The following attributes are set: weather [list of tuples] . intensity [string] @@ -603,11 +603,11 @@ def _handleWeather( self, d ): obsci = d['obsc'] otheri = d['other'] self.weather.append((inteni,desci,preci,obsci,otheri)) - + def _handleSky( self, d ): """ Parse a sky-conditions group. - + The following attributes are set: sky [list of tuples] . cover [string] @@ -626,11 +626,11 @@ def _handleSky( self, d ): cloud = d['cloud'] if cloud == '///': cloud = "" self.sky.append((cover,height,cloud)) - + def _handleTemp( self, d ): """ Parse a temperature-dewpoint group. - + The following attributes are set: temp temperature (Celsius) [float] dewpt dew point (Celsius) [float] @@ -641,11 +641,11 @@ def _handleTemp( self, d ): self.temp = temperature(temp) if dewpt and dewpt != "//" and dewpt != "XX" and dewpt != "MM" : self.dewpt = temperature(dewpt) - + def _handlePressure( self, d ): """ Parse an altimeter-pressure group. - + The following attributes are set: press [int] """ @@ -668,11 +668,11 @@ def _handlePressure( self, d ): self.press = pressure(press/100,'IN') else: self.press = pressure(press,'MB') - + def _handleRecent( self, d ): """ Parse a recent-weather group. - + The following attributes are set: weather [list of tuples] . intensity [string] @@ -686,11 +686,11 @@ def _handleRecent( self, d ): obsci = d['obsc'] otheri = d['other'] self.recent.append(("",desci,preci,obsci,otheri)) - + def _handleWindShear( self, d ): """ Parse wind-shear groups. - + The following attributes are set: windshear [list of strings] """ @@ -698,16 +698,16 @@ def _handleWindShear( self, d ): self.windshear.append(d['name']) else: self.windshear.append("ALL") - + def _handleColor( self, d ): """ Parse (and ignore) the color groups. - + The following attributes are set: trend [list of strings] """ pass - + def _handleRunwayState( self, d ): """ Parse (and ignore) the runway state. @@ -723,26 +723,26 @@ def _handleTrend( self, d ): if d.has_key('trend'): self._trend_groups.append(d['trend']) self._trend = True - + def _startRemarks( self, d ): """ Found the start of the remarks section. """ self._remarks = [] - + def _handleSealvlPressRemark( self, d ): """ Parse the sea-level pressure remark group. """ value = float(d['press'])/10.0 - if value < 50: + if value < 50: value += 1000 - else: + else: value += 900 if not self.press: self.press = pressure(value,"MB") self.press_sea_level = pressure(value,"MB") - + def _handlePrecip24hrRemark( self, d ): """ Parse a 3-, 6- or 24-hour cumulative preciptation remark group. @@ -755,16 +755,16 @@ def _handlePrecip24hrRemark( self, d ): self.precip_6hr = precipitation(value,"IN") else: self.precip_24hr = precipitation(value,"IN") - + def _handlePrecip1hrRemark( self, d ): """Parse an hourly precipitation remark group.""" value = float(d['precip'])/100.0 self.precip_1hr = precipitation(value,"IN") - + def _handleTemp1hrRemark( self, d ): """ Parse a temperature & dewpoint remark group. - + These values replace the temp and dewpt from the body of the report. """ value = float(d['temp'])/10.0 @@ -774,7 +774,7 @@ def _handleTemp1hrRemark( self, d ): value2 = float(d['dewpt'])/10.0 if d['dsign'] == "1": value2 = -value2 self.dewpt = temperature(value2) - + def _handleTemp6hrRemark( self, d ): """ Parse a 6-hour maximum or minimum temperature remark group. @@ -785,7 +785,7 @@ def _handleTemp6hrRemark( self, d ): self.max_temp_6hr = temperature(value,"C") else: self.min_temp_6hr = temperature(value,"C") - + def _handleTemp24hrRemark( self, d ): """ Parse a 24-hour maximum/minimum temperature remark group. @@ -796,7 +796,7 @@ def _handleTemp24hrRemark( self, d ): if d['smint'] == "1": value2 = -value2 self.max_temp_24hr = temperature(value,"C") self.min_temp_24hr = temperature(value2,"C") - + def _handlePress3hrRemark( self, d ): """ Parse a pressure-tendency remark group. @@ -804,7 +804,7 @@ def _handlePress3hrRemark( self, d ): value = float(d['press'])/10.0 descrip = PRESSURE_TENDENCY[d['tend']] self._remarks.append("3-hr pressure change %.1fhPa, %s" % (value,descrip)) - + def _handlePeakWindRemark( self, d ): """ Parse a peak wind remark group. @@ -827,7 +827,7 @@ def _handlePeakWindRemark( self, d ): self.peak_wind_time -= datetime.timedelta(hours=1) self._remarks.append("peak wind %dkt from %d degrees at %d:%02d" % \ (peak_speed, peak_dir, peak_hour, peak_min)) - + def _handleWindShiftRemark( self, d ): """ Parse a wind shift remark group. @@ -848,7 +848,7 @@ def _handleWindShiftRemark( self, d ): if d['front']: text += " (front)" self._remarks.append(text) - + def _handleLightningRemark( self, d ): """ Parse a lightning observation remark group. @@ -856,7 +856,7 @@ def _handleLightningRemark( self, d ): parts = [] if d['freq']: parts.append(LIGHTNING_FREQUENCY[d['freq']]) - parts.append("lightning") + parts.append("lightning") if d['type']: ltg_types = [] group = d['type'] @@ -867,7 +867,7 @@ def _handleLightningRemark( self, d ): if d['loc']: parts.append(xlate_loc(d['loc'])) self._remarks.append(string.join(parts," ")) - + def _handleTSLocRemark( self, d ): """ Parse a thunderstorm location remark group. @@ -878,7 +878,7 @@ def _handleTSLocRemark( self, d ): if d['dir']: text += " moving %s" % d['dir'] self._remarks.append(text) - + def _handleAutoRemark( self, d ): """ Parse an automatic station remark group. @@ -887,29 +887,29 @@ def _handleAutoRemark( self, d ): self._remarks.append("Automated station") elif d['type'] == "2": self._remarks.append("Automated station (type 2)") - + def _unparsedRemark( self, d ): """ Handle otherwise unparseable remark groups. """ self._unparsed_remarks.append(d['group']) - + ## the list of handler functions to use (in order) to process a METAR report handlers = [ (TYPE_RE, _handleType, False), - (STATION_RE, _handleStation, False), - (TIME_RE, _handleTime, False), - (MODIFIER_RE, _handleModifier, False), - (WIND_RE, _handleWind, False), - (VISIBILITY_RE, _handleVisibility, True), - (RUNWAY_RE, _handleRunway, True), - (WEATHER_RE, _handleWeather, True), - (SKY_RE, _handleSky, True), - (TEMP_RE, _handleTemp, False), - (PRESS_RE, _handlePressure, True), - (RECENT_RE,_handleRecent, True), - (WINDSHEAR_RE, _handleWindShear, True), - (COLOR_RE, _handleColor, True), + (STATION_RE, _handleStation, False), + (TIME_RE, _handleTime, False), + (MODIFIER_RE, _handleModifier, False), + (WIND_RE, _handleWind, False), + (VISIBILITY_RE, _handleVisibility, True), + (RUNWAY_RE, _handleRunway, True), + (WEATHER_RE, _handleWeather, True), + (SKY_RE, _handleSky, True), + (TEMP_RE, _handleTemp, False), + (PRESS_RE, _handlePressure, True), + (RECENT_RE,_handleRecent, True), + (WINDSHEAR_RE, _handleWindShear, True), + (COLOR_RE, _handleColor, True), (RUNWAYSTATE_RE, _handleRunwayState, True), (TREND_RE, _handleTrend, False), (REMARK_RE, _startRemarks, False) ] @@ -937,7 +937,7 @@ def _unparsedRemark( self, d ): (TEMP_6HR_RE, _handleTemp6hrRemark), (TEMP_24HR_RE, _handleTemp24hrRemark), (UNPARSED_RE, _unparsedRemark) ] - + ## functions that return text representations of conditions for output def string(self): @@ -1018,7 +1018,7 @@ def report_type(self): def wind(self, units="KT"): """ Return a textual description of the wind conditions. - + Units may be specified as "MPS", "KT", "KMH", or "MPH". """ if self.wind_speed == None: @@ -1041,7 +1041,7 @@ def wind(self, units="KT"): def peak_wind(self, units="KT"): """ Return a textual description of the peak wind conditions. - + Units may be specified as "MPS", "KT", "KMH", or "MPH". """ if self.wind_speed_peak == None: @@ -1072,7 +1072,7 @@ def wind_shift(self, units="KT"): def visibility(self, units=None): """ Return a textual description of the visibility. - + Units may be statute miles ("SM") or meters ("M"). """ if self.vis == None: @@ -1087,7 +1087,7 @@ def visibility(self, units=None): else: text += "; %s" % self.max_vis.string(units) return text - + def runway_visual_range(self, units=None): """ Return a textual description of the runway visual range. @@ -1099,7 +1099,7 @@ def runway_visual_range(self, units=None): else: lines.append("on runway %s, %s" % (name, low.string(units))) return string.join(lines,"; ") - + def present_weather(self): """ Return a textual description of the present weather. @@ -1119,7 +1119,7 @@ def present_weather(self): if len(desci) == 4: text_parts.append(WEATHER_DESC[desci[2:]]) if preci: - code_parts.append(preci) + code_parts.append(preci) if len(preci) == 2: precip_text = WEATHER_PREC[preci] elif len(preci) == 4: @@ -1137,7 +1137,7 @@ def present_weather(self): if obsci: code_parts.append(obsci) text_parts.append(WEATHER_OBSC[obsci]) - + if otheri: code_parts.append(otheri) text_parts.append(WEATHER_OTHER[otheri]) @@ -1147,7 +1147,7 @@ def present_weather(self): else: text_list.append(string.join(text_parts," ")) return string.join(text_list,"; ") - + def sky_conditions( self, sep="; " ): """ Return a textual description of the sky conditions. @@ -1162,16 +1162,16 @@ def sky_conditions( self, sep="; " ): what = CLOUD_TYPE[cloud] elif cover != "OVC": what = "clouds" - else: + else: what = "" if cover == "VV": text_list.append("%s%s, visibility to %s" % \ (SKY_COVER[cover],what,str(height))) else: - text_list.append("%s%s at %s" % + text_list.append("%s%s at %s" % (SKY_COVER[cover],what,str(height))) return string.join(text_list,sep) - + def trend(self): """ Return the trend forecast groups diff --git a/metar/Station.py b/metar/Station.py index a2ba69ef..c172a156 100755 --- a/metar/Station.py +++ b/metar/Station.py @@ -9,8 +9,8 @@ class station: """An object representing a weather station.""" - - def __init__(self, id, city=None, state=None, + + def __init__(self, id, city=None, state=None, country=None, latitude=None, longitude=None): self.id = id self.city = city @@ -21,7 +21,7 @@ def __init__(self, id, city=None, state=None, self.name = "%s, %s" % (self.city, self.state) else: self.name = self.city - + station_file_name = "nsd_cccc.txt" station_file_url = "http://www.noaa.gov/nsd_cccc.txt" stations = {} diff --git a/metar/__init__.py b/metar/__init__.py index 9fef445d..c5d3eaf8 100644 --- a/metar/__init__.py +++ b/metar/__init__.py @@ -45,7 +45,9 @@ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ % __author__ +import Datatypes +import Metar +import Station From 641c008ca145f5f6a3d69719007cd3726c13daab Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Wed, 30 May 2012 18:36:20 -0700 Subject: [PATCH 4/4] got things working again with error files --- metar/Metar.py | 18 ++++-- metar/Station.py | 149 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 149 insertions(+), 18 deletions(-) diff --git a/metar/Metar.py b/metar/Metar.py index f7cde7b1..cead0469 100755 --- a/metar/Metar.py +++ b/metar/Metar.py @@ -312,7 +312,8 @@ def _unparsedGroup( self, d ): class Metar(object): """METAR (aviation meteorology report)""" - def __init__( self, metarcode, month=None, year=None, utcdelta=None): + def __init__(self, metarcode, month=None, year=None, + utcdelta=None, errorfile=None): """Parse raw METAR code.""" self.code = metarcode # original METAR code self.type = 'METAR' # METAR (routine) or SPECI (special) @@ -411,11 +412,20 @@ def __init__( self, metarcode, month=None, year=None, utcdelta=None): break except Exception, err: - raise ParserError(handler.__name__+" failed while processing '"+code+"'\n"+string.join(err.args)) - raise err + msg = handler.__name__+" failed while processing '"+code+"'\n"+string.join(err.args) + if errorfile is not None: + errorfile.write(msg+'\n') + else: + print(msg) + #raise ParserError(handler.__name__+" failed while processing '"+code+"'\n"+string.join(err.args)) + #raise err if self._unparsed_groups: code = ' '.join(self._unparsed_groups) - raise ParserError("Unparsed groups in body: "+code) + msg = "Unparsed groups in body: "+code + if errorfile is not None: + errorfile.write(msg+'\n') + else: + print(msg) def _do_trend_handlers(self, code): for pattern, handler, repeatable in Metar.trend_handlers: diff --git a/metar/Station.py b/metar/Station.py index c172a156..1ad08691 100755 --- a/metar/Station.py +++ b/metar/Station.py @@ -4,15 +4,28 @@ # # Copyright 2004 Tom Pollard # +#!/usr/bin/python +# +# Python module to provide station information from the ICAO identifiers +# +# Copyright 2004 Tom Pollard +# +import datetime, urllib, urllib2, cookielib +from Metar import Metar from math import sin, cos, atan2, sqrt -from datatypes import position, distance, direction +from Datatypes import position, distance, direction +import numpy as np +import matplotlib +import matplotlib.dates as mdates +matplotlib.rcParams['timezone'] = 'UTC' + class station: """An object representing a weather station.""" - def __init__(self, id, city=None, state=None, + def __init__(self, sta_id, city=None, state=None, country=None, latitude=None, longitude=None): - self.id = id + self.sta_id = sta_id self.city = city self.state = state self.country = country @@ -22,17 +35,125 @@ def __init__(self, id, city=None, state=None, else: self.name = self.city -station_file_name = "nsd_cccc.txt" -station_file_url = "http://www.noaa.gov/nsd_cccc.txt" -stations = {} + self.urlopener = self.__setCookies() + + def __setCookies(self): + jar = cookielib.CookieJar() + handler = urllib2.HTTPCookieProcessor(jar) + opener = urllib2.build_opener(handler) + url1 = 'http://www.wunderground.com/history/airport/%s/2011/12/4/DailyHistory.html?' % self.sta_id + url2 = 'http://www.wunderground.com/cgi-bin/findweather/getForecast?setpref=SHOWMETAR&value=1' + url3 = 'http://www.wunderground.com/history/airport/%s/2011/12/4/DailyHistory.html?&&theprefset=SHOWMETAR&theprefvalue=1&format=1' % self.sta_id + + opener.open(url1) + opener.open(url2) + opener.open(url3) + return opener + + def urlByDate(self, date): + "http://www.wunderground.com/history/airport/KDCA/1950/12/18/DailyHistory.html?format=1" + baseurl = 'http://www.wunderground.com/history/airport/%s' % self.sta_id + endurl = 'DailyHistory.html?&&theprefset=SHOWMETAR&theprefvalue=1&format=1' + datestring = date.strftime('%Y/%m/%d') + url = '%s/%s/%s' % (baseurl, datestring, endurl) + return url + + def getHourlyData(self, url, outfile, errorfile, keepheader=False): + observations = [] + if keepheader: + start = 1 + else: + start = 2 + + try: + webdata = self.urlopener.open(url) + rawlines = webdata.readlines() + outfile.writelines(rawlines[start:]) + except: + errorfile.write('error on: %s\n' % (url,)) + + + +def getAllStations(): + station_file_name = "nsd_cccc.txt" + station_file_url = "http://www.noaa.gov/nsd_cccc.txt" + stations = {} + + fh = open(station_file_name,'r') + for line in fh: + f = line.strip().split(";") + stations[f[0]] = station(f[0],f[3],f[4],f[5],f[7],f[8]) + fh.close() + + return stations + +def getStationByID(sta_id): + stations = getAllStations() + return stations[sta_id] + +def processWundergroundFile(csvin, csvout, errorfile): + coverdict = {'CLR' : 0, + 'SKC' : 0, + 'OVC' : 1, + 'BKN' : 0.75, + 'SCT' : 0.4375, + 'FEW' : 0.1875, + 'VV' : 0.99} + + headers =['TimeEST', + 'Temperature C', + 'Dew Point C', + 'Humidity', + 'Sea Level Pressure hPa', + 'VisibilityKm', + 'Wind Direction', + 'Wind Speed Km/h', + 'Gust Speed Km/h', + 'Precipitationmm', + 'Events', + 'Conditions', + 'FullMetar', + 'WindDirDegrees', + 'DateUTC', + 'maxskycover', + 'allskycover->'] + datain = open(csvin, 'r') + dataout = open(csvout, 'w') + + for h in headers[:-1]: + dataout.write('%s,' % (h,)) + dataout.write('%s\n' % (headers[-1]),) + + junk = datain.readline() + for line in datain: + dataline = line.split('<')[0] + row = dataline.split(',') + dataout.write(dataline) + datestr = row[-1] + + try: + datenum = mdates.datestr2num(datestr) + date = mdates.num2date(datenum) + good = True + except ValueError: + good = False + errorfile.write('%s: %s\n' % (date.strftime('%Y-%m-%d'), datestr)) + + if good: + metarstring = row[-3] + obs = Metar(metarstring, month=date.month, year=date.year, + errorfile=errorfile) -fh = open(station_file_name,'r') -for line in fh: - f = line.strip().split(";") - stations[f[0]] = station(f[0],f[3],f[4],f[5],f[7],f[8]) -fh.close() + cover = [] + for sky in obs.sky: + coverval = coverdict[sky[0]] + cover.append(coverval) -if __name__ == "__main__": - for id in [ 'KEWR', 'KIAD', 'KIWI', 'EKRK' ]: - print id, stations[id].name, stations[id].country + if len(cover) > 0: + dataout.write(',%0.2f' % (np.max(cover),)) + for c in cover: + dataout.write(',%0.2f' % (c,)) + dataout.write('\n') + datain.close() + dataout.close()