Advertisement
MolSno

weather.py

Nov 30th, 2013
124
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.95 KB | None | 0 0
  1. #!/usr/bin/env python
  2. """
  3. weather.py - Phenny Weather Module
  4. Copyright 2008, Sean B. Palmer, inamidst.com
  5. Licensed under the Eiffel Forum License 2.
  6.  
  7. http://inamidst.com/phenny/
  8. """
  9.  
  10. import re, urllib
  11. import web
  12. from tools import deprecated
  13.  
  14. r_from = re.compile(r'(?i)([+-]\d+):00 from')
  15.  
  16. def location(name):
  17.    name = urllib.quote(name.encode('utf-8'))
  18.    uri = 'http://ws.geonames.org/searchJSON?q=%s&maxRows=1' % name
  19.    for i in xrange(10):
  20.       u = urllib.urlopen(uri)
  21.       if u is not None: break
  22.    bytes = u.read()
  23.    u.close()
  24.  
  25.    results = web.json(bytes)
  26.    try: name = results['geonames'][0]['name']
  27.    except IndexError:
  28.       return '?', '?', '0', '0'
  29.    countryName = results['geonames'][0]['countryName']
  30.    lat = results['geonames'][0]['lat']
  31.    lng = results['geonames'][0]['lng']
  32.    return name, countryName, lat, lng
  33.  
  34. class GrumbleError(object):
  35.    pass
  36.  
  37. def local(icao, hour, minute):
  38.    uri = ('http://www.flightstats.com/' +
  39.           'go/Airport/airportDetails.do?airportCode=%s')
  40.    try: bytes = web.get(uri % icao)
  41.    except AttributeError:
  42.       raise GrumbleError('A WEBSITE HAS GONE DOWN WTF STUPID WEB')
  43.    m = r_from.search(bytes)
  44.    if m:
  45.       offset = m.group(1)
  46.       lhour = int(hour) + int(offset)
  47.       lhour = lhour % 24
  48.       return (str(lhour) + ':' + str(minute) + ', ' + str(hour) +
  49.               str(minute) + 'Z')
  50.       # return (str(lhour) + ':' + str(minute) + ' (' + str(hour) +
  51.       #         ':' + str(minute) + 'Z)')
  52.    return str(hour) + ':' + str(minute) + 'Z'
  53.  
  54. def code(phenny, search):
  55.    from icao import data
  56.    
  57.    if search.upper() in [loc[0] for loc in data]:
  58.       return search.upper()
  59.    else:
  60.       name, country, latitude, longitude = location(search)
  61.       if name == '?': return False
  62.       sumOfSquares = (99999999999999999999999999999, 'ICAO')
  63.       for icao_code, lat, lon in data:
  64.          latDiff = abs(latitude - lat)
  65.          lonDiff = abs(longitude - lon)
  66.          diff = (latDiff * latDiff) + (lonDiff * lonDiff)
  67.          if diff < sumOfSquares[0]:
  68.             sumOfSquares = (diff, icao_code)
  69.       return sumOfSquares[1]
  70.  
  71. @deprecated
  72. def f_weather(self, origin, match, args):
  73.    """.weather <ICAO> - Show the weather at airport with the code <ICAO>."""
  74.    if origin.sender == '#talis':
  75.       if args[0].startswith('.weather '): return
  76.  
  77.    icao_code = match.group(2)
  78.    if not icao_code:
  79.       return self.msg(origin.sender, 'Try .weather London, for example?')
  80.  
  81.    icao_code = code(self, icao_code)
  82.  
  83.    if not icao_code:
  84.       self.msg(origin.sender, 'No ICAO code found, sorry')
  85.       return
  86.  
  87.    uri = 'http://weather.noaa.gov/pub/data/observations/metar/stations/%s.TXT'
  88.    try: bytes = web.get(uri % icao_code)
  89.    except AttributeError:
  90.       raise GrumbleError('OH CRAP NOAA HAS GONE DOWN THE WEB IS BROKEN')
  91.    if 'Not Found' in bytes:
  92.       self.msg(origin.sender, icao_code+': no such ICAO code, or no NOAA data')
  93.       return
  94.  
  95.    metar = bytes.splitlines().pop()
  96.    metar = metar.split(' ')
  97.  
  98.    if len(metar[0]) == 4:
  99.       metar = metar[1:]
  100.  
  101.    if metar[0].endswith('Z'):
  102.       time = metar[0]
  103.       metar = metar[1:]
  104.    else: time = None
  105.  
  106.    if metar[0] == 'AUTO':
  107.       metar = metar[1:]
  108.    if metar[0] == 'VCU':
  109.       self.msg(origin.sender, icao_code + ': no data provided')
  110.       return
  111.  
  112.    if metar[0].endswith('KT'):
  113.       wind = metar[0]
  114.       metar = metar[1:]
  115.    else: wind = None
  116.  
  117.    if ('V' in metar[0]) and (metar[0] != 'CAVOK'):
  118.       vari = metar[0]
  119.       metar = metar[1:]
  120.    else: vari = None
  121.  
  122.    if ((len(metar[0]) == 4) or
  123.        metar[0].endswith('SM')):
  124.       visibility = metar[0]
  125.       metar = metar[1:]
  126.    else: visibility = None
  127.  
  128.    while metar[0].startswith('R') and (metar[0].endswith('L')
  129.                                     or 'L/' in metar[0]):
  130.       metar = metar[1:]
  131.  
  132.    if len(metar[0]) == 6 and (metar[0].endswith('N') or
  133.                               metar[0].endswith('E') or
  134.                               metar[0].endswith('S') or
  135.                               metar[0].endswith('W')):
  136.       metar = metar[1:] # 7000SE?
  137.  
  138.    cond = []
  139.    while (((len(metar[0]) < 5) or
  140.           metar[0].startswith('+') or
  141.           metar[0].startswith('-')) and (not (metar[0].startswith('VV') or
  142.           metar[0].startswith('SKC') or metar[0].startswith('CLR') or
  143.           metar[0].startswith('FEW') or metar[0].startswith('SCT') or
  144.           metar[0].startswith('BKN') or metar[0].startswith('OVC')))):
  145.       cond.append(metar[0])
  146.       metar = metar[1:]
  147.  
  148.    while '/P' in metar[0]:
  149.       metar = metar[1:]
  150.  
  151.    if not metar:
  152.       self.msg(origin.sender, icao_code + ': no data provided')
  153.       return
  154.  
  155.    cover = []
  156.    while (metar[0].startswith('VV') or metar[0].startswith('SKC') or
  157.           metar[0].startswith('CLR') or metar[0].startswith('FEW') or
  158.           metar[0].startswith('SCT') or metar[0].startswith('BKN') or
  159.           metar[0].startswith('OVC')):
  160.       cover.append(metar[0])
  161.       metar = metar[1:]
  162.       if not metar:
  163.          self.msg(origin.sender, icao_code + ': no data provided')
  164.          return
  165.  
  166.    if metar[0] == 'CAVOK':
  167.       cover.append('CLR')
  168.       metar = metar[1:]
  169.  
  170.    if metar[0] == 'PRFG':
  171.       cover.append('CLR') # @@?
  172.       metar = metar[1:]
  173.  
  174.    if metar[0] == 'NSC':
  175.       cover.append('CLR')
  176.       metar = metar[1:]
  177.  
  178.    if ('/' in metar[0]) or (len(metar[0]) == 5 and metar[0][2] == '.'):
  179.       temp = metar[0]
  180.       metar = metar[1:]
  181.    else: temp = None
  182.  
  183.    if metar[0].startswith('QFE'):
  184.       metar = metar[1:]
  185.  
  186.    if metar[0].startswith('Q') or metar[0].startswith('A'):
  187.       pressure = metar[0]
  188.       metar = metar[1:]
  189.    else: pressure = None
  190.  
  191.    if time:
  192.       hour = time[2:4]
  193.       minute = time[4:6]
  194.       time = local(icao_code, hour, minute)
  195.    else: time = '(time unknown)'
  196.  
  197.    if wind:
  198.       speed = int(wind[3:5])
  199.       if speed < 1:
  200.          description = 'Calm'
  201.       elif speed < 4:
  202.          description = 'Light air'
  203.       elif speed < 7:
  204.          description = 'Light breeze'
  205.       elif speed < 11:
  206.          description = 'Gentle breeze'
  207.       elif speed < 16:
  208.          description = 'Moderate breeze'
  209.       elif speed < 22:
  210.          description = 'Fresh breeze'
  211.       elif speed < 28:
  212.          description = 'Strong breeze'
  213.       elif speed < 34:
  214.          description = 'Near gale'
  215.       elif speed < 41:
  216.          description = 'Gale'
  217.       elif speed < 48:
  218.          description = 'Strong gale'
  219.       elif speed < 56:
  220.          description = 'Storm'
  221.       elif speed < 64:
  222.          description = 'Violent storm'
  223.       else: description = 'Hurricane'
  224.  
  225.       degrees = wind[0:3]
  226.       if degrees == 'VRB':
  227.          degrees = u'\u21BB'.encode('utf-8')
  228.       elif (degrees <= 22.5) or (degrees > 337.5):
  229.          degrees = u'\u2191'.encode('utf-8')
  230.       elif (degrees > 22.5) and (degrees <= 67.5):
  231.          degrees = u'\u2197'.encode('utf-8')
  232.       elif (degrees > 67.5) and (degrees <= 112.5):
  233.          degrees = u'\u2192'.encode('utf-8')
  234.       elif (degrees > 112.5) and (degrees <= 157.5):
  235.          degrees = u'\u2198'.encode('utf-8')
  236.       elif (degrees > 157.5) and (degrees <= 202.5):
  237.          degrees = u'\u2193'.encode('utf-8')
  238.       elif (degrees > 202.5) and (degrees <= 247.5):
  239.          degrees = u'\u2199'.encode('utf-8')
  240.       elif (degrees > 247.5) and (degrees <= 292.5):
  241.          degrees = u'\u2190'.encode('utf-8')
  242.       elif (degrees > 292.5) and (degrees <= 337.5):
  243.          degrees = u'\u2196'.encode('utf-8')
  244.  
  245.       if not icao_code.startswith('EN') and not icao_code.startswith('ED'):
  246.          wind = '%s %skt (%s)' % (description, speed, degrees)
  247.       elif icao_code.startswith('ED'):
  248.          kmh = int(round(speed * 1.852, 0))
  249.          wind = '%s %skm/h (%skt) (%s)' % (description, kmh, speed, degrees)
  250.       elif icao_code.startswith('EN'):
  251.          ms = int(round(speed * 0.514444444, 0))
  252.          wind = '%s %sm/s (%skt) (%s)' % (description, ms, speed, degrees)
  253.    else: wind = '(wind unknown)'
  254.  
  255.    if visibility:
  256.       visibility = visibility + 'm'
  257.    else: visibility = '(visibility unknown)'
  258.  
  259.    if cover:
  260.       level = None
  261.       for c in cover:
  262.          if c.startswith('OVC') or c.startswith('VV'):
  263.             if (level is None) or (level < 8):
  264.                level = 8
  265.          elif c.startswith('BKN'):
  266.             if (level is None) or (level < 5):
  267.                level = 5
  268.          elif c.startswith('SCT'):
  269.             if (level is None) or (level < 3):
  270.                level = 3
  271.          elif c.startswith('FEW'):
  272.             if (level is None) or (level < 1):
  273.                level = 1
  274.          elif c.startswith('SKC') or c.startswith('CLR'):
  275.             if level is None:
  276.                level = 0
  277.  
  278.       if level == 8:
  279.          cover = u'Overcast \u2601'.encode('utf-8')
  280.       elif level == 5:
  281.          cover = 'Cloudy'
  282.       elif level == 3:
  283.          cover = 'Scattered'
  284.       elif (level == 1) or (level == 0):
  285.          cover = u'Clear \u263C'.encode('utf-8')
  286.       else: cover = 'Cover Unknown'
  287.    else: cover = 'Cover Unknown'
  288.  
  289.    if temp:
  290.       if '/' in temp:
  291.          temp = temp.split('/')[0]
  292.       else: temp = temp.split('.')[0]
  293.       if temp.startswith('M'):
  294.          temp = '-' + temp[1:]
  295.       try: temp = int(temp)
  296.       except ValueError: temp = '?'
  297.    else: temp = '?'
  298.  
  299.    if pressure:
  300.       if pressure.startswith('Q'):
  301.          pressure = pressure.lstrip('Q')
  302.          if pressure != 'NIL':
  303.             pressure = str(int(pressure)) + 'mb'
  304.          else: pressure = '?mb'
  305.       elif pressure.startswith('A'):
  306.          pressure = pressure.lstrip('A')
  307.          if pressure != 'NIL':
  308.             inches = pressure[:2] + '.' + pressure[2:]
  309.             mb = int(float(inches) * 33.7685)
  310.             pressure = '%sin (%smb)' % (inches, mb)
  311.          else: pressure = '?mb'
  312.  
  313.          if isinstance(temp, int):
  314.             f = round((temp * 1.8) + 32, 2)
  315.             temp = u'%s\u2109 (%s\u2103)'.encode('utf-8') % (f, temp)
  316.    else: pressure = '?mb'
  317.    if isinstance(temp, int):
  318.       temp = u'%s\u2103'.encode('utf-8') % temp
  319.  
  320.    if cond:
  321.       conds = cond
  322.       cond = ''
  323.  
  324.       intensities = {
  325.          '-': 'Light',
  326.          '+': 'Heavy'
  327.       }
  328.  
  329.       descriptors = {
  330.          'MI': 'Shallow',
  331.          'PR': 'Partial',
  332.          'BC': 'Patches',
  333.          'DR': 'Drifting',
  334.          'BL': 'Blowing',
  335.          'SH': 'Showers of',
  336.          'TS': 'Thundery',
  337.          'FZ': 'Freezing',
  338.          'VC': 'In the vicinity:'
  339.       }
  340.  
  341.       phenomena = {
  342.          'DZ': 'Drizzle',
  343.          'RA': 'Rain',
  344.          'SN': 'Snow',
  345.          'SG': 'Snow Grains',
  346.          'IC': 'Ice Crystals',
  347.          'PL': 'Ice Pellets',
  348.          'GR': 'Hail',
  349.          'GS': 'Small Hail',
  350.          'UP': 'Unknown Precipitation',
  351.          'BR': 'Mist',
  352.          'FG': 'Fog',
  353.          'FU': 'Smoke',
  354.          'VA': 'Volcanic Ash',
  355.          'DU': 'Dust',
  356.          'SA': 'Sand',
  357.          'HZ': 'Haze',
  358.          'PY': 'Spray',
  359.          'PO': 'Whirls',
  360.          'SQ': 'Squalls',
  361.          'FC': 'Tornado',
  362.          'SS': 'Sandstorm',
  363.          'DS': 'Duststorm',
  364.          # ? Cf. http://swhack.com/logs/2007-10-05#T07-58-56
  365.          'TS': 'Thunderstorm',
  366.          'SH': 'Showers'
  367.       }
  368.  
  369.       for c in conds:
  370.          if c.endswith('//'):
  371.             if cond: cond += ', '
  372.             cond += 'Some Precipitation'
  373.          elif len(c) == 5:
  374.             intensity = intensities[c[0]]
  375.             descriptor = descriptors[c[1:3]]
  376.             phenomenon = phenomena.get(c[3:], c[3:])
  377.             if cond: cond += ', '
  378.             cond += intensity + ' ' + descriptor + ' ' + phenomenon
  379.          elif len(c) == 4:
  380.             descriptor = descriptors.get(c[:2], c[:2])
  381.             phenomenon = phenomena.get(c[2:], c[2:])
  382.             if cond: cond += ', '
  383.             cond += descriptor + ' ' + phenomenon
  384.          elif len(c) == 3:
  385.             intensity = intensities.get(c[0], c[0])
  386.             phenomenon = phenomena.get(c[1:], c[1:])
  387.             if cond: cond += ', '
  388.             cond += intensity + ' ' + phenomenon
  389.          elif len(c) == 2:
  390.             phenomenon = phenomena.get(c, c)
  391.             if cond: cond += ', '
  392.             cond += phenomenon
  393.  
  394.    # if not cond:
  395.    #    format = u'%s at %s: %s, %s, %s, %s'
  396.    #    args = (icao, time, cover, temp, pressure, wind)
  397.    # else:
  398.    #    format = u'%s at %s: %s, %s, %s, %s, %s'
  399.    #    args = (icao, time, cover, temp, pressure, cond, wind)
  400.  
  401.    if not cond:
  402.       format = u'%s, %s, %s, %s - %s %s'
  403.       args = (cover, temp, pressure, wind, str(icao_code), time)
  404.    else:
  405.       format = u'%s, %s, %s, %s, %s - %s, %s'
  406.       args = (cover, temp, pressure, cond, wind, str(icao_code), time)
  407.  
  408.    self.msg(origin.sender, format.encode('utf-8') % args)
  409. f_weather.rule = (['weather'], r'(.*)')
  410.  
  411. if __name__ == '__main__':
  412.    print __doc__.strip()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement