Changeset 570:7b92e7021a5b


Ignore:
Timestamp:
10/05/14 12:30:27 (10 years ago)
Author:
István Váradi <ivaradi@…>
Branch:
default
Phase:
public
Message:

Changed NOTAM acquisition to use PilotWeb as the primary source (re #232)

Location:
src/mlx
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • src/mlx/gui/flight.py

    r564 r570  
    19891989            s = ""
    19901990            for notam in notams:
    1991                 s += str(notam.begin)
    1992                 if notam.end is not None:
    1993                     s += " - " + str(notam.end)
    1994                 elif notam.permanent:
    1995                     s += " - PERMANENT"
    1996                 s += "\n"
    1997                 if notam.repeatCycle:
    1998                     s += "Repeat cycle: " + notam.repeatCycle + "\n"
    1999                 s += notam.notice + "\n"
     1991                s += str(notam)
    20001992                s += "-------------------- * --------------------\n"
    20011993            buffer.set_text(s)
  • src/mlx/web.py

    r496 r570  
    1414import xml.sax
    1515import xmlrpclib
     16import HTMLParser
    1617
    1718#---------------------------------------------------------------------------------------
     
    353354class NOTAM(object):
    354355    """A NOTAM for an airport."""
    355     def __init__(self, begin, notice, end = None, permanent = False,
     356    def __init__(self, ident, basic,
     357                 begin, notice, end = None, permanent = False,
    356358                 repeatCycle = None):
    357359        """Construct the NOTAM."""
     360        self.ident = ident
     361        self.basic = basic
    358362        self.begin = begin
    359363        self.notice = notice
    360364        self.end = end
    361365        self.permanent = permanent
    362         self.repeatCycle = None
     366        self.repeatCycle = repeatCycle
    363367
    364368    def __repr__(self):
     
    375379        return s
    376380
     381    def __str__(self):
     382        """Get the string representation of the NOTAM."""
     383        s = ""
     384        s += str(self.ident) + " " + str(self.basic) + "\n"
     385        s += str(self.begin)
     386        if self.end is not None:
     387            s += " - " + str(self.end)
     388        elif self.permanent:
     389            s += " - PERMANENT"
     390        s += "\n"
     391        if self.repeatCycle:
     392            s += "Repeat cycle: " + self.repeatCycle + "\n"
     393        s += self.notice + "\n"
     394        return s
     395
    377396#------------------------------------------------------------------------------
    378397
     
    388407        """Start an element."""
    389408        if name!="notam" or \
     409           "ident" not in attrs or not attrs["ident"] or \
     410           "Q" not in attrs or not attrs["Q"] or \
    390411           "A" not in attrs or not attrs["A"] or \
    391412           "B" not in attrs or not attrs["B"] or \
     
    406427        repeatCycle = attrs["D"] if "D" in attrs else None
    407428
    408         self._notams[icao].append(NOTAM(begin, attrs["E"], end = end,
     429        self._notams[icao].append(NOTAM(attrs["ident"], attrs["Q"],
     430                                        begin, attrs["E"], end = end,
    409431                                        permanent = permanent,
    410432                                        repeatCycle = repeatCycle))
     
    413435        """Get the NOTAMs for the given ICAO code."""
    414436        return self._notams[icao] if icao in self._notams else []
     437
     438#------------------------------------------------------------------------------
     439
     440class PilotsWebNOTAMsParser(HTMLParser.HTMLParser):
     441    """XML handler for the NOTAM query results on the PilotsWeb website."""
     442    def __init__(self):
     443        """Construct the handler."""
     444        HTMLParser.HTMLParser.__init__(self)
     445
     446        self._notams = []
     447        self._currentNOTAM = ""
     448        self._stage = 0
     449
     450    def handle_starttag(self, name, attrs):
     451        """Start an element."""
     452        if (self._stage==0 and name=="div" and ("id", "notamRight") in attrs) or \
     453           (self._stage==1 and name=="span") or \
     454           (self._stage==2 and name=="pre"):
     455            self._stage += 1
     456            if self._stage==1:
     457                self._currentNOTAM = ""
     458
     459    def handle_data(self, content):
     460        """Handle characters"""
     461        if self._stage==3:
     462            self._currentNOTAM += content
     463
     464    def handle_endtag(self, name):
     465        """End an element."""
     466        if (self._stage==3 and name=="pre") or \
     467           (self._stage==2 and name=="span") or \
     468           (self._stage==1 and name=="div"):
     469            self._stage -= 1
     470            if self._stage==0:
     471                self._processCurrentNOTAM()
     472
     473    def getNOTAMs(self):
     474        """Get the NOTAMs collected"""
     475        return self._notams
     476
     477    def _processCurrentNOTAM(self):
     478        """Parse the current NOTAM and append its contents to the list of
     479        NOTAMS."""
     480        notam = None
     481        try:
     482            notam = self._parseCurrentNOTAM2()
     483        except Exception, e:
     484            print "Error parsing current NOTAM: " + str(e)
     485
     486        if notam is None:
     487            print "Could not parse NOTAM: " + self._currentNOTAM
     488            if self._currentNOTAM:
     489                self._notams.append(self._currentNOTAM + "\n")
     490        else:
     491            self._notams.append(notam)
     492
     493    def _parseCurrentNOTAM(self):
     494        """Parse the current NOTAM, if possible, and return a NOTAM object."""
     495        lines = self._currentNOTAM.splitlines()
     496        lines = map(lambda line: line.strip(), lines)
     497
     498        if len(lines)<4:
     499            return None
     500
     501        if not lines[1].startswith("Q)") or \
     502           not lines[2].startswith("A)") or \
     503           not (lines[3].startswith("E)") or
     504                (lines[3].startswith("D)") and lines[4].startswith("E)"))):
     505            return None
     506
     507        ident = lines[0].split()[0]
     508        basic = lines[1][2:].strip()
     509
     510        words = lines[2].split()
     511        if len(words)<4 or words[0]!="A)" or words[2]!="B)":
     512            return None
     513
     514        begin = datetime.datetime.strptime(words[3], "%y%m%d%H%M")
     515        end = None
     516        permanent = False
     517        if words[4]=="C)" and len(words)>=6:
     518            if words[5] in ["PERM", "UFN"]:
     519                permanent = True
     520            else:
     521                end = datetime.datetime.strptime(words[5], "%y%m%d%H%M")
     522        else:
     523            permanent = True
     524
     525        repeatCycle = None
     526        noticeStartIndex = 3
     527        if lines[3].startswith("D)"):
     528            repeatCycle = lines[3][2:].strip()
     529            noticeStartIndex = 4
     530
     531        notice = ""
     532        for index in range(noticeStartIndex, len(lines)):
     533            line = lines[index][2:] if index==noticeStartIndex else lines[index]
     534            line = line.strip()
     535
     536            if line.lower().startswith("created:") or \
     537               line.lower().startswith("source:"):
     538               break
     539
     540            if notice: notice += " "
     541            notice += line
     542
     543        return NOTAM(ident, basic, begin, notice, end = end,
     544                     permanent = permanent, repeatCycle = repeatCycle)
     545
     546    def _parseCurrentNOTAM2(self):
     547        """Parse the current NOTAM with a second, more flexible method."""
     548        lines = self._currentNOTAM.splitlines()
     549        lines = map(lambda line: line.strip(), lines)
     550
     551        if not lines:
     552            return None
     553
     554        ident = lines[0].split()[0]
     555
     556        lines = lines[1:]
     557        for i in range(0, 2):
     558            l = lines[-1].lower()
     559            if l.startswith("created:") or l.startswith("source:"):
     560                lines = lines[:-1]
     561
     562        lines = map(lambda line: line.strip(), lines)
     563        contents = " ".join(lines).split()
     564
     565        items = {}
     566        for i in ["Q)", "A)", "B)", "C)", "D)", "E)"]:
     567            items[i] = ""
     568
     569        currentItem = None
     570        for word in contents:
     571            if word in items:
     572                currentItem = word
     573            elif currentItem in items:
     574                s = items[currentItem]
     575                if s: s+= " "
     576                s += word
     577                items[currentItem] = s
     578
     579        if not items["Q)"] or not items["A)"] or not items["B)"] or \
     580           not items["E)"]:
     581            return None
     582
     583        basic = items["Q)"]
     584        begin = datetime.datetime.strptime(items["B)"], "%y%m%d%H%M")
     585
     586        end = None
     587        permanent = False
     588        if items["C)"]:
     589            endItem = items["C)"]
     590            if endItem in ["PERM", "UFN"]:
     591                permanent = True
     592            else:
     593                end = datetime.datetime.strptime(items["C)"], "%y%m%d%H%M")
     594        else:
     595            permanent = True
     596
     597        repeatCycle = None
     598        if items["D)"]:
     599            repeatCycle = items["D)"]
     600
     601        notice = items["E)"]
     602
     603        return NOTAM(ident, basic, begin, notice, end = end,
     604                     permanent = permanent, repeatCycle = repeatCycle)
    415605
    416606#------------------------------------------------------------------------------
     
    469659        except Exception, e:
    470660            print >> sys.stderr, "web.Handler.Request.perform: callback throwed an exception: " + util.utf2unicode(str(e))
    471             traceback.print_exc()
     661            #traceback.print_exc()
    472662
    473663#------------------------------------------------------------------------------
     
    610800    def run(self):
    611801        """Perform the retrieval of the NOTAMs."""
    612         xmlParser = xml.sax.make_parser()
    613         notamHandler = NOTAMHandler([self._departureICAO, self._arrivalICAO])
    614         xmlParser.setContentHandler(notamHandler)
    615 
    616         url = "http://notams.euroutepro.com/notams.xml"
    617 
    618         f = urllib2.urlopen(url, timeout = 10.0)
     802        departureNOTAMs = self.getPilotsWebNOTAMs(self._departureICAO)
     803        arrivalNOTAMs = self.getPilotsWebNOTAMs(self._arrivalICAO)
     804
     805        icaos = []
     806        if not departureNOTAMs: icaos.append(self._departureICAO)
     807        if not arrivalNOTAMs: icaos.append(self._arrivalICAO)
     808
     809        if icaos:
     810            xmlParser = xml.sax.make_parser()
     811            notamHandler = NOTAMHandler(icaos)
     812            xmlParser.setContentHandler(notamHandler)
     813
     814            url = "http://notams.euroutepro.com/notams.xml"
     815
     816            f = urllib2.urlopen(url, timeout = 10.0)
     817            try:
     818                xmlParser.parse(f)
     819            finally:
     820                f.close()
     821
     822            for icao in icaos:
     823                if icao==self._departureICAO:
     824                    departureNOTAMs = notamHandler.get(icao)
     825                else:
     826                    arrivalNOTAMs = notamHandler.get(icao)
     827
     828        result = Result()
     829        result.departureNOTAMs = departureNOTAMs
     830        result.arrivalNOTAMs = arrivalNOTAMs
     831
     832        return result
     833
     834    def getPilotsWebNOTAMs(self, icao):
     835        """Try to get the NOTAMs from FAA's PilotsWeb site for the given ICAO
     836        code.
     837
     838        Returns a list of PilotsWEBNOTAM objects, or None in case of an error."""
    619839        try:
    620             xmlParser.parse(f)
    621         finally:
    622             f.close()
    623 
    624         result = Result()
    625         result.departureNOTAMs = notamHandler.get(self._departureICAO)
    626         result.arrivalNOTAMs = notamHandler.get(self._arrivalICAO)
    627 
    628         return result
     840            parser = PilotsWebNOTAMsParser()
     841
     842            url = "https://pilotweb.nas.faa.gov/PilotWeb/notamRetrievalByICAOAction.do?method=displayByICAOs&formatType=ICAO&retrieveLocId=%s&reportType=RAW&actionType=notamRetrievalByICAOs" % \
     843              (icao.upper(),)
     844
     845            f = urllib2.urlopen(url, timeout = 10.0)
     846            try:
     847                data = f.read(16384)
     848                while data:
     849                    parser.feed(data)
     850                    data = f.read(16384)
     851            finally:
     852                f.close()
     853
     854            return parser.getNOTAMs()
     855
     856        except Exception, e:
     857            traceback.print_exc()
     858            print "mlx.web.GetNOTAMs.getPilotsWebNOTAMs: failed to get NOTAMs for '%s': %s" % \
     859                  (icao, str(e))
     860            return None
    629861
    630862#------------------------------------------------------------------------------
Note: See TracChangeset for help on using the changeset viewer.