# Copyright (C) 2002  Tobias Klausmann
# Modified by Jerome Alet
# Code contributed by Jerome Alet and Davide Di Blasi
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.        See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# By reading this code you agree not to ridicule the author =)

import string
import re
import urllib2
import types
import math, fpformat

__author__ = "klausman-usenet@tuts.net"

__version__ = "0.5"

__doc__ = """Pymetar v%s (c) 2002 Tobias Klausman

Pymetar is a python module and command line tool designed to fetch Metar
reports from the NOAA (http://www.noaa.gov) and allow access to the included
weather information.

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307, USA.

Please e-mail bugs to: %s""" % (__version__, __author__)


#
# What a boring list to type !
#
# It seems the NOAA doesn't want to return plain text, but considering the
# format of their response, this is not to save bandwidth :-)
#
_WeatherConditions = {
                      "DZ" : ("Drizzle", "rain", {
                               "" :   "Moderate drizzle",
                               "-" :  "Light drizzle",
                               "+" :  "Heavy drizzle",
                               "VC" : "Drizzle in the vicinity",
                               "MI" : "Shallow drizzle",
                               "BC" : "Patches of drizzle",
                               "PR" : "Partial drizzle",
                               "TS" : ("Thunderstorm", "storm"),
                               "BL" : "Windy drizzle",
                               "SH" : "Showers",
                               "DR" : "Drifting drizzle",
                               "FZ" : "Freezing drizzle",
                             }),
                      "RA" : ("Rain", "rain", {
                               "" :   "Moderate rain",
                               "-" :  "Light rain",
                               "+" :  "Heavy rain",
                               "VC" : "Rain in the vicinity",
                               "MI" : "Shallow rain",
                               "BC" : "Patches of rain",
                               "PR" : "Partial rainfall",
                               "TS" : ("Thunderstorm", "storm"),
                               "BL" : "Blowing rainfall",
                               "SH" : "Rain showers",
                               "DR" : "Drifting rain",
                               "FZ" : "Freezing rain",
                             }),
                      "SN" : ("Snow", "snow", {
                               "" :   "Moderate snow",
                               "-" :  "Light snow",
                               "+" :  "Heavy snow",
                               "VC" : "Snow in the vicinity",
                               "MI" : "Shallow snow",
                               "BC" : "Patches of snow",
                               "PR" : "Partial snowfall",
                               "TS" : ("Snowstorm", "storm"),
                               "BL" : "Blowing snowfall",
                               "SH" : "Snowfall showers",
                               "DR" : "Drifting snow",
                               "FZ" : "Freezing snow",
                             }),
                      "SG" : ("Snow grains", "snow", {
                               "" :   "Moderate snow grains",
                               "-" :  "Light snow grains",
                               "+" :  "Heavy snow grains",
                               "VC" : "Snow grains in the vicinity",
                               "MI" : "Shallow snow grains",
                               "BC" : "Patches of snow grains",
                               "PR" : "Partial snow grains",
                               "TS" : ("Snowstorm", "storm"),
                               "BL" : "Blowing snow grains",
                               "SH" : "Snow grain showers",
                               "DR" : "Drifting snow grains",
                               "FZ" : "Freezing snow grains",
                             }),
                      "IC" : ("Ice crystals", "snow", {
                               "" :   "Moderate ice crystals",
                               "-" :  "Few ice crystals",
                               "+" :  "Heavy ice crystals",
                               "VC" : "Ice crystals in the vicinity",
                               "BC" : "Patches of ice crystals",
                               "PR" : "Partial ice crystals",
                               "TS" : ("Ice crystal storm", "storm"),
                               "BL" : "Blowing ice crystals",
                               "SH" : "Showers of ice crystals",
                               "DR" : "Drifting ice crystals",
                               "FZ" : "Freezing ice crystals",
                             }),
                      "PE" : ("Ice pellets", "snow", {
                               "" :   "Moderate ice pellets",
                               "-" :  "Few ice pellets",
                               "+" :  "Heavy ice pellets",
                               "VC" : "Ice pellets in the vicinity",
                               "MI" : "Shallow ice pellets",
                               "BC" : "Patches of ice pellets",
                               "PR" : "Partial ice pellets",
                               "TS" : ("Ice pellets storm", "storm"),
                               "BL" : "Blowing ice pellets",
                               "SH" : "Showers of ice pellets",
                               "DR" : "Drifting ice pellets",
                               "FZ" : "Freezing ice pellets",
                             }),
                      "GR" : ("Hail", "rain", {
                               "" :   "Moderate hail",
                               "-" :  "Light hail",
                               "+" :  "Heavy hail",
                               "VC" : "Hail in the vicinity",
                               "MI" : "Shallow hail",
                               "BC" : "Patches of hail",
                               "PR" : "Partial hail",
                               "TS" : ("Hailstorm", "storm"),
                               "BL" : "Blowing hail",
                               "SH" : "Hail showers",
                               "DR" : "Drifting hail",
                               "FZ" : "Freezing hail",
                             }),
                      "GS" : ("Small hail", "rain", {
                               "" :   "Moderate small hail",
                               "-" :  "Light small hail",
                               "+" :  "Heavy small hail",
                               "VC" : "Small hail in the vicinity",
                               "MI" : "Shallow small hail",
                               "BC" : "Patches of small hail",
                               "PR" : "Partial small hail",
                               "TS" : ("Small hailstorm", "storm"),
                               "BL" : "Blowing small hail",
                               "SH" : "Showers of small hail",
                               "DR" : "Drifting small hail",
                               "FZ" : "Freezing small hail",
                             }),
                      "UP" : ("Precipitation", "rain", {
                               "" :   "Moderate precipitation",
                               "-" :  "Light precipitation",
                               "+" :  "Heavy precipitation",
                               "VC" : "Precipitation in the vicinity",
                               "MI" : "Shallow precipitation",
                               "BC" : "Patches of precipitation",
                               "PR" : "Partial precipitation",
                               "TS" : ("Unknown thunderstorm", "storm"),
                               "BL" : "Blowing precipitation",
                               "SH" : "Showers, type unknown",
                               "DR" : "Drifting precipitation",
                               "FZ" : "Freezing precipitation",
                             }),
                      "BR" : ("Mist", "fog", {
                               "" :   "Moderate mist",
                               "-" :  "Light mist",
                               "+" :  "Thick mist",
                               "VC" : "Mist in the vicinity",
                               "MI" : "Shallow mist",
                               "BC" : "Patches of mist",
                               "PR" : "Partial mist",
                               "BL" : "Mist with wind",
                               "DR" : "Drifting mist",
                               "FZ" : "Freezing mist",
                             }),
                      "FG" : ("Fog", "fog", {
                               "" :   "Moderate fog",
                               "-" :  "Light fog",
                               "+" :  "Thick fog",
                               "VC" : "Fog in the vicinity",
                               "MI" : "Shallow fog",
                               "BC" : "Patches of fog",
                               "PR" : "Partial fog",
                               "BL" : "Fog with wind",
                               "DR" : "Drifting fog",
                               "FZ" : "Freezing fog",
                             }),
                      "FU" : ("Smoke", "fog", {
                               "" :   "Moderate smoke",
                               "-" :  "Thin smoke",
                               "+" :  "Thick smoke",
                               "VC" : "Smoke in the vicinity",
                               "MI" : "Shallow smoke",
                               "BC" : "Patches of smoke",
                               "PR" : "Partial smoke",
                               "TS" : ("Smoke w/ thunders", "storm"),
                               "BL" : "Smoke with wind",
                               "DR" : "Drifting smoke",
                             }),
                      "VA" : ("Volcanic ash", "fog", {
                               "" :   "Moderate volcanic ash",
                               "+" :  "Thick volcanic ash",
                               "VC" : "Volcanic ash in the vicinity",
                               "MI" : "Shallow volcanic ash",
                               "BC" : "Patches of volcanic ash",
                               "PR" : "Partial volcanic ash",
                               "TS" : ("Volcanic ash w/ thunders", "storm"),
                               "BL" : "Blowing volcanic ash",
                               "SH" : "Showers of volcanic ash",
                               "DR" : "Drifting volcanic ash",
                               "FZ" : "Freezing volcanic ash",
                             }),
                      "SA" : ("Sand", "fog", {
                               "" :   "Moderate sand",
                               "-" :  "Light sand",
                               "+" :  "Heavy sand",
                               "VC" : "Sand in the vicinity",
                               "BC" : "Patches of sand",
                               "PR" : "Partial sand",
                               "BL" : "Blowing sand",
                               "DR" : "Drifting sand",
                             }),
                      "HZ" : ("Haze", "fog", {
                               "" :   "Moderate haze",
                               "-" :  "Light haze",
                               "+" :  "Thick haze",
                               "VC" : "Haze in the vicinity",
                               "MI" : "Shallow haze",
                               "BC" : "Patches of haze",
                               "PR" : "Partial haze",
                               "BL" : "Haze with wind",
                               "DR" : "Drifting haze",
                               "FZ" : "Freezing haze",
                             }),
                      "PY" : ("Sprays", "fog", {
                               "" :   "Moderate sprays",
                               "-" :  "Light sprays",
                               "+" :  "Heavy sprays",
                               "VC" : "Sprays in the vicinity",
                               "MI" : "Shallow sprays",
                               "BC" : "Patches of sprays",
                               "PR" : "Partial sprays",
                               "BL" : "Blowing sprays",
                               "DR" : "Drifting sprays",
                               "FZ" : "Freezing sprays",
                             }),
                      "DU" : ("Dust", "fog", {
                               "" :   "Moderate dust",
                               "-" :  "Light dust",
                               "+" :  "Heavy dust",
                               "VC" : "Dust in the vicinity",
                               "BC" : "Patches of dust",
                               "PR" : "Partial dust",
                               "BL" : "Blowing dust",
                               "DR" : "Drifting dust",
                             }),
                      "SQ" : ("Squall", "storm", {
                               "" :   "Moderate squall",
                               "-" :  "Light squall",
                               "+" :  "Heavy squall",
                               "VC" : "Squall in the vicinity",
                               "PR" : "Partial squall",
                               "TS" : "Thunderous squall",
                               "BL" : "Blowing squall",
                               "DR" : "Drifting squall",
                               "FZ" : "Freezing squall",
                             }),
                      "SS" : ("Sandstorm", "fog", {
                               "" :   "Moderate sandstorm",
                               "-" :  "Light sandstorm",
                               "+" :  "Heavy sandstorm",
                               "VC" : "Sandstorm in the vicinity",
                               "MI" : "Shallow sandstorm",
                               "PR" : "Partial sandstorm",
                               "TS" : ("Thunderous sandstorm", "storm"),
                               "BL" : "Blowing sandstorm",
                               "DR" : "Drifting sandstorm",
                               "FZ" : "Freezing sandstorm",
                             }),
                      "DS" : ("Duststorm", "fog", {
                               "" :   "Moderate duststorm",
                               "-" :  "Light duststorm",
                               "+" :  "Heavy duststorm",
                               "VC" : "Duststorm in the vicinity",
                               "MI" : "Shallow duststorm",
                               "PR" : "Partial duststorm",
                               "TS" : ("Thunderous duststorm", "storm"),
                               "BL" : "Blowing duststorm",
                               "DR" : "Drifting duststorm",
                               "FZ" : "Freezing duststorm",
                             }),
                      "PO" : ("Dustwhirls", "fog", {
                               "" :   "Moderate dustwhirls",
                               "-" :  "Light dustwhirls",
                               "+" :  "Heavy dustwhirls",
                               "VC" : "Dustwhirls in the vicinity",
                               "MI" : "Shallow dustwhirls",
                               "BC" : "Patches of dustwhirls",
                               "PR" : "Partial dustwhirls",
                               "BL" : "Blowing dustwhirls",
                               "DR" : "Drifting dustwhirls",
                             }),
                      "+FC" : ("Tornado", "storm", {
                               "" :   "Moderate tornado",
                               "+" :  "Raging tornado",
                               "VC" : "Tornado in the vicinity",
                               "PR" : "Partial tornado",
                               "TS" : "Thunderous tornado",
                               "BL" : "Tornado",
                               "DR" : "Drifting tornado",
                               "FZ" : "Freezing tornado",
                              }),
                      "FC" : ("Funnel cloud", "fog", {
                               "" :   "Moderate funnel cloud",
                               "-" :  "Light funnel cloud",
                               "+" :  "Thick funnel cloud",
                               "VC" : "Funnel cloud in the vicinity",
                               "MI" : "Shallow funnel cloud",
                               "BC" : "Patches of funnel cloud",
                               "PR" : "Partial funnel cloud",
                               "BL" : "Funnel cloud w/ wind",
                               "DR" : "Drifting funnel cloud",
                             }),
                    }

#
# RegExps to extract the different parts from the Metar encoded report
TIME_RE_STR = r"^([0-9]{6})Z$"
WIND_RE_STR = r"^(([0-9]{3})|VRB)([0-9]?[0-9]{2})(G[0-9]?[0-9]{2})?KT$"
VIS_RE_STR  = r"^(([0-9]?[0-9])|(M?1/[0-9]?[0-9]))SM$"
CLOUD_RE_STR= r"^(CLR|SKC|BKN|SCT|FEW|OVC)([0-9]{3})?$"
TEMP_RE_STR = r"^(M?[0-9][0-9])/(M?[0-9][0-9])$"
PRES_RE_STR = r"^(A|Q)([0-9]{4})$"
COND_RE_STR = r"^(-|\\+)?(VC|MI|BC|PR|TS|BL|SH|DR|FZ)?(DZ|RA|SN|SG|IC|PE|GR|GS|UP|BR|FG|FU|VA|SA|HZ|PY|DU|SQ|SS|DS|PO|\\+?FC)$"

class MetarReport:

    """
    This class provides a means to download and parse the decoded METAR reports
    from http://weather.noaa.gov/

    In order to find the report relevant for you, find the closest station at
    http://www.nws.noaa.gov/oso/siteloc.shtml and feed its 4-letter station
    code to fetchMetarReport(), case does not matter.

    All methods return metric values, no matter what the station provides. The
    conversion factors were taken from the excellent "units" program.

    If None is returned that means the information wasn't available or could
    not be parsed. If you have the suspicion that failure to parse an
    information was this libs fault, please save the report generating the
    error send it to me with a few lines detailing the problem. Thanks!
    """

    def match_WeatherPart(self, regexp) :
        """
        Returns the matching part of the encoded Metar report.  
        regexp: the regexp needed to extract this part.  
        Returns the first matching string or None.  
        WARNING: Some Metar reports may contain several matching strings, only
        the first one is taken into account, e.g. FEW020 SCT075 BKN053 
        """
        if self.code is not None :
            rg = re.compile(regexp)
            for wpart in self.getRawMetarCode().split() :
                match = rg.match(wpart)
                if match :
                    return match.string[match.start(0) : match.end(0)]

    def extractSkyConditions(self) :
        """
        Extracts sky condition information from the encoded report.  Returns a
        tuple containing the description of the sky conditions as a string and
        a suggested pixmap name for an icon representing said sky condition.
        """
        wcond = self.match_WeatherPart(COND_RE_STR)
        if wcond is not None :
            if (len(wcond) > 3) and (wcond.startswith('+') or wcond.startswith('-')) :
                wcond = wcond[1:]
            if wcond.startswith('+') or wcond.startswith('-') :
                pphen = 1
            elif len(wcond) < 4 :
                pphen = 0
            else :
                pphen = 2
            squal = wcond[:pphen]
            sphen = wcond[pphen : pphen + 4]
            phenomenon = _WeatherConditions.get(sphen, None)
            if phenomenon is not None :
                (name, pixmap, phenomenon) = phenomenon
                pheninfo = phenomenon.get(squal, name)
                if type(pheninfo) != types.TupleType :
                    return (pheninfo, pixmap)
                else :
                    # contains pixmap info
                    return pheninfo

    def parseLatLong(self, latlong):
        """
        Parse Lat or Long in METAR notation into float values. N and E are +, S
        and W are -. Expects one positional string and returns one float value.
        """
        # I know, I could invert this if and put
        # the rest of the function into its block,
        # but I find this to be more readable
        if latlong is None: return None

        s = string.strip(string.upper(latlong))
        elms = string.split(s,'-')
        ud = elms[-1][-1]
        elms[-1] = elms[-1][:-1]
        elms = map(string.atoi, elms)
        coords = 0.0
        elen = len(elms)
        if elen > 2:
            coords = coords + float(elms[2])/3600.0

        if elen > 1:
            coords = coords + float(elms[1])/60.0

        coords=coords + float(elms[0])

        if ud in ('W','S'):
            coords=-1.0*coords

        f,i=math.modf(coords)

        if elen > 2:
            f=float(fpformat.sci(f,4))
        elif elen > 1:
            f=float(fpformat.sci(f,2))
        else:
            f=0.0

        return f+i

    def extractCloudInformation(self) :
        """
        Extracts cloud information.  returns None or a tuple (sky type as a
        string of text and suggested pixmap name)
        """   
        wcloud = self.match_WeatherPart(CLOUD_RE_STR)
        if wcloud is not None :
            stype = wcloud[:3]
            if (stype == "CLR") or (stype == "SKC") :
                return ("Clear sky", "sun")
            elif stype == "BKN" :
                return ("Broken clouds", "suncloud")
            elif stype == "SCT" :
                return ("Scattered clouds", "suncloud")
            elif stype == "FEW" :
                return ("Few clouds", "suncloud")
            elif stype == "OVC" :
                return ("Overcast", "cloud")

    def getFullReport(self):
        """
        Return the complete weather report.
        """
        return self.fullreport

    def getTemperatureCelsius(self):
        """
        Return the temperature in degrees Celsius.
        """
        return self.temp

    def getTemperatureFahrenheit(self):
        """
        Return the temperature in degrees Fahrenheit.
        """
        return (self.temp * (9.0/5.0)) + 32.0

    def getDewPointCelsius(self):
        """
        Return dewpoint in degrees Celsius.
        """
        return self.dewp

    def getDewPointFahrenheit(self):
        """
        Return dewpoint in degrees Fahrenheit.
        """
        return (self.dewp * (9.0/5.0)) + 32.0

    def getWindSpeed(self):
        """
        Return the wind speed in meters per second.
        """
        return self.windspeed

    def getWindDirection(self):
        """
        Return wind direction in degrees.
        """
        return self.winddir

    def getWindCompass(self):
        """
        Return wind direction as compass direction
        (e.g. NE or SSE)
        """
        return self.windcomp

    def getVisibilityKilometers(self):
        """
        Return visibility in km.
        """
        return self.vis

    def getVisibilityMiles(self):
        """
        Return visibility in miles.
        """
        return self.vis / 1.609344

    def getHumidity(self):
        """
        Return relative humidity in percent.
        """
        return self.humid

    def getPressure(self):
        """
        Return pressure in hPa.
        """
        return self.press

    def getRawMetarCode(self):
        """
        Return the encoded weather report.
        """
        return self.code

    def getWeather(self):
        """
        Return short weather conditions
        """
        return self.weather

    def getSkyConditions(self):
        """
        Return sky conditions
        """
        return self.sky

    def getStationName(self):
        """
        Return full station name
        """
        return self.fulln

    def getStationCity(self):
        """
        Return city-part of station name
        """
        return self.stat_city

    def getStationCountry(self):
        """
        Return country-part of station name
        """
        return self.stat_country

    def getCycle(self):
        """
        Return cycle value.
        The cycle value is not the frequency or delay between observations but
        the "time slot" in which the observation was made. There are 24 cycle
        slots every day which usually last from N:45 to N+1:45. The cycle from
        23:45 to 0:45 is cycle 0.
        """
        return self.cycle

    def getStationPosition(self):
        """
        Return latitude, longitude and altitude above sea level of station as a
        tuple. Some stations don't deliver altitude, for those, None is
        returned as altitude.  The lat/longs are expressed as follows:
        xx-yyd
        where xx is degrees, yy minutes and d the direction.
        Thus 51-14N means 51 degrees, 14 minutes north.  d may take the values
        N, S for latitues and W, E for longitudes. Latitude and Longitude may
        include seconds.  Altitude is always given as meters above sea level,
        including a trailing M.
        Schipohl Int. Airport Amsterdam has, for example:
        ('52-18N', '004-46E', '-2M')
        Moenchengladbach (where I live):
        ('51-14N', '063-03E', None)
        If you need lat and long as float values, look at 
        getStationPositionFloat() instead
        """
        return (self.latitude, self.longitude, self.altitude)

    def getStationPositionFloat(self):
        """
        Return latitude and longitude as float values in a tuple (lat,long,alt)
        """
        return (self.latf,self.longf,self.altitude)

    def getStationLatitude(self) :
        """
        Return the station's latitude in dd-mm[-ss]D format :
        dd : degrees
        mm : minutes
        ss : seconds
        D : direction (N, S, E, W)
        """
        return self.latitude

    def getStationLatitudeFloat(self):
        """
        Return latitude as a float
        """
        return self.latf

    def getStationLongitude(self) :
        """
        Return the station's longitude in dd-mm[-ss]D format :
        dd : degrees
        mm : minutes
        ss : seconds
        D : direction (N, S, E, W)
        """
        return self.longitude

    def getStationLongitudeFloat(self):
        """
        Return Longitude as a float
        """
        return (self.longf)

    def getStationAltitude(self) :
        """
        Return the station's altitude above the sea in meters.
        """
        return self.altitude

    def getReportURL(self):
        """
        Return the URL from which the report was fetched.
        """
        return self.reporturl

    def getTime(self):
        """
        Return the time when the observation was made.  Note that this is *not*
        the time when the report was fetched by us
        Format:  YYYY.MM.DD HHMM UTC
        Example: 2002.04.01 1020 UTC
        """
        return self.rtime

    def getISOTime(self):
        """
        Return the time when the observation was made in ISO 8601 format
        (e.g. 2002-07-25 15:12:00Z)
        """
        return(self.metar_to_iso8601(self.rtime))

    def getPixmap(self):
        """
        Return a suggested pixmap name, without extension, depending on current
        weather.
        """
        return self.pixmap

    def metar_to_iso8601(self, metardate) :
        """
        Converts a metar date to an ISO8601 date.
        """
        if metardate is not None:
            (date, hour, tz) = metardate.split()
            (year, month, day) = date.split('.')
            # assuming tz is always 'UTC', aka 'Z'
            return "%s-%s-%s %s:%s:00Z" % (year, month, day, hour[:2], hour[2:4])

    def strreverse(self, str):
        """
        Reverses a string
        """
        listr=list(str)
        listr.reverse()
        return (string.join(listr,""))

    def __init__(self, MetarStationCode = None):
        """
        Get initial report if a station code is passed as a parameter,
        otherwise initialize fields.
        """
        if MetarStationCode is not None :
            self.fetchMetarReport(MetarStationCode)
        else :
            self._ClearAllFields()

    def _ClearAllFields(self):
        """
        Clears all fields values.
        """
        # until finished, report is invalid
        self.valid = 0
        # Clear all
        self.fullreport=None
        self.temp=None
        self.windspeed=None
        self.winddir=None
        self.vis=None
        self.dewp=None
        self.humid=None
        self.press=None
        self.code=None
        self.weather=None
        self.sky=None
        self.fulln=None
        self.cycle=None
        self.windcomp=None
        self.rtime=None
        self.pixmap=None
        self.latitude=None
        self.longitude=None
        self.altitude=None
        self.stat_city=None
        self.stat_country=None
        self.reportutl=None
        self.latf=None
        self.longf=None

    def fetchMetarReport(self, station, baseurl="http://weather.noaa.gov/pub/data/observations/metar/decoded/"):
        """
        Retrieve the decoded METAR report from the given base URL and parse it.
        Fill the data properties in the class instance with the values found in
        the report, converting them to metric values where necessary.
        """
        self._ClearAllFields()
        station=string.upper(station)
        self.reporturl="%s%s.TXT" % (baseurl, station)
        
        # Honor HTTP_PROXY environment var
        # Thanks to Davide Di Blasi for suggesting this
        urllib2.install_opener(
            urllib2.build_opener(urllib2.ProxyHandler, urllib2.HTTPHandler))
        
        fn=urllib2.urlopen(self.reporturl)
        # Dump entire report in a variable
        self.fullreport=fn.read()

        # if fn.info().status is 0, everything seems to be in order
        if not fn.info().status:

            lines=string.split(self.fullreport,"\n")

            for line in lines:
                # Most lines start with a Header and a colon
                try:
                    header,data=string.split(line,":",1)
                except ValueError:
                    header=data=line

                header=string.strip(header)
                data=string.strip(data)

                # The station identifier and location
                if (string.find(header,"("+station+")")!=-1):
                    try:
                        loc,p=string.split(data,"(",1)
                        loc=string.strip(loc)
                        rloc=self.strreverse(loc)
                        rcoun,rcity=string.split(rloc,",",1)
                    except ValueError:
                        city=""
                        coun=""
                        p=data
                    try:
                        id,lat,long,ht=string.split(p," ")
                        ht = int(ht[:-1]) # skip the 'M' for meters
                    except ValueError:
                        id,lat,long=string.split(p," ")
                        ht=None
                    self.stat_city=string.strip(self.strreverse(rcity))
                    self.stat_country=string.strip(self.strreverse(rcoun))
                    self.fulln=loc
                    self.latitude = lat
                    self.longitude = long
                    self.latf=self.parseLatLong(lat)
                    self.longf=self.parseLatLong(long)
                    self.altitude = ht

                # The line containing the date/time of the report
                elif (string.find(data,"UTC")!=-1):
                    local,rt=string.split(data,"/")
                    self.rtime=string.strip(rt)

                # temperature
                elif (header=="Temperature"):
                    t,i=string.split(data," ",1)
                    self.temp=(float(t)-32)*(5.0/9.0)

                # wind direction and speed
                elif (header == "Wind"):
                    if (string.find(data,"Calm")!=-1):
                        self.windspeed=0.0
                        self.winddir=None
                        self.windcomp=None
                    elif (string.find(data,"Variable")!=-1):
                        v,a,speed,r=string.split(data," ",3)
                        self.windspeed=(float(speed)*0.44704)
                        self.winddir=None
                        self.windcomp=None
                    else:
                        f,t,comp,deg,r,d,speed,r=string.split(data," ",7)
                        self.winddir=int(deg[1:])
                        self.windcomp=string.strip(comp)
                        self.windspeed=(float(speed)*0.44704)

                # visibility
                elif (header=="Visibility"):
                    for d in data.split() :
                        try :
                            self.vis = float(d)*1.609344
                            break
                        except ValueError :
                            pass

                # dew point
                elif (header=="Dew Point"):
                    dp,i=string.split(data," ",1)
                    self.dewp=(float(dp)-32)*(5.0/9.0)

                # humidity
                elif (header=="Relative Humidity"):
                    h,i=string.split(data,"%",1)
                    self.humid=int(h)

                # pressure
                elif (header=="Pressure (altimeter)"):
                    p,r=string.split(data," ",1)
                    self.press=(float(p)*33.863886)

                # short weather description ("rain", "mist", ...)
                elif (header=="Weather"):
                    self.weather=data

                # short description of sky cond.
                elif (header=="Sky conditions"):
                    self.sky=data

                # the encoded weather report
                elif (header=="ob"):
                    self.code=string.strip(data)

                # the cycle value describes the cycle in
                # which the observations were made
                elif (header=="cycle"):
                    self.cycle=int(data)

            # decode cloud and sky conditions informations from
            # the raw report, this will suggest us a pixmap to use
            # to graphically represent the weather.
            cloudinfo = self.extractCloudInformation()
            if cloudinfo is not None :
                (cloudinfo, cloudpixmap) = cloudinfo
            else :
                (cloudinfo, cloudpixmap) = (None, None)
            conditions = self.extractSkyConditions()
            if conditions is not None :
                (conditions, condpixmap) = conditions
            else :
                (conditions, condpixmap) = (None, None)

            # fill the weather information
            self.weather = self.weather or conditions or cloudinfo

            # Pixmap guessed from general conditions has priority
            # over pixmap guessed from clouds
            self.pixmap = condpixmap or cloudpixmap

            # report is complete
            self.valid = 1

        # Even if the report is bad, toss the fd
        del fn
