source: src/mlx/web.py@ 85:42b688827d63

Last change on this file since 85:42b688827d63 was 74:1aba83981490, checked in by István Váradi <ivaradi@…>, 13 years ago

Modified METAR retrieval to work with the new website

File size: 19.0 KB
Line 
1# Interface towards the websites used
2
3#------------------------------------------------------------------------------
4
5import const
6
7import threading
8import sys
9import urllib
10import urllib2
11import hashlib
12import time
13import datetime
14import codecs
15import xml.sax
16
17#---------------------------------------------------------------------------------------
18
19def readline(f):
20 """Read a line from the given file.
21
22 The line is stripped and empty lines are discarded."""
23 while True:
24 line = f.readline()
25 if not line: return ""
26 line = line.strip()
27 if line:
28 return line
29
30#---------------------------------------------------------------------------------------
31
32class BookedFlight(object):
33 """A flight that was booked."""
34 TYPECODE2TYPE = { "736" : const.AIRCRAFT_B736,
35 "73G" : const.AIRCRAFT_B737,
36 "738" : const.AIRCRAFT_B738,
37 "733" : const.AIRCRAFT_B733,
38 "734" : const.AIRCRAFT_B734,
39 "735" : const.AIRCRAFT_B735,
40 "DH4" : const.AIRCRAFT_DH8D,
41 "762" : const.AIRCRAFT_B762,
42 "763" : const.AIRCRAFT_B763,
43 "CR2" : const.AIRCRAFT_CRJ2,
44 "F70" : const.AIRCRAFT_F70,
45 "LI2" : const.AIRCRAFT_DC3,
46 "TU3" : const.AIRCRAFT_T134,
47 "TU5" : const.AIRCRAFT_T154,
48 "YK4" : const.AIRCRAFT_YK40 }
49
50 def __init__(self, id, f):
51 """Construct a booked flight with the given ID.
52
53 The rest of the data is read from the given file."""
54
55 self.id = id
56 self.callsign = readline(f)
57
58 date = readline(f)
59
60 self.departureICAO = readline(f)
61 self.arrivalICAO = readline(f)
62
63 self._readAircraftType(f)
64 self.tailNumber = readline(f)
65 self.numPassengers = int(readline(f))
66 self.numCrew = int(readline(f))
67 self.bagWeight = int(readline(f))
68 self.cargoWeight = int(readline(f))
69 self.mailWeight = int(readline(f))
70 self.route = readline(f)
71
72 departureTime = readline(f)
73 self.departureTime = datetime.datetime.strptime(date + " " + departureTime,
74 "%Y-%m-%d %H:%M:%S")
75
76 arrivalTime = readline(f)
77 self.arrivalTime = datetime.datetime.strptime(date + " " + arrivalTime,
78 "%Y-%m-%d %H:%M:%S")
79
80 if not readline(f)==".NEXT.":
81 raise Exception("Invalid line in flight data")
82
83 def _readAircraftType(self, f):
84 """Read the aircraft type from the given file."""
85 line = readline(f)
86 typeCode = line[:3]
87 if typeCode in self.TYPECODE2TYPE:
88 self.aircraftType = self.TYPECODE2TYPE[typeCode]
89 else:
90 raise Exception("Invalid aircraft type code: '" + typeCode + "'")
91
92 def __repr__(self):
93 """Get a representation of the flight."""
94 s = "<Flight: %s-%s, %s, %s-%s," % (self.departureICAO,
95 self.arrivalICAO,
96 self.route,
97 self.departureTime, self.arrivalTime)
98 s += " %d %s," % (self.aircraftType, self.tailNumber)
99 s += " pax=%d, crew=%d, bag=%d, cargo=%d, mail=%d" % \
100 (self.numPassengers, self.numCrew,
101 self.bagWeight, self.cargoWeight, self.mailWeight)
102 s += ">"
103 return s
104
105#------------------------------------------------------------------------------
106
107class Plane(object):
108 """Information about an airplane in the fleet."""
109 def __init__(self, s):
110 """Build a plane info based on the given string.
111
112 The string consists of three, space-separated fields.
113 The first field is the tail number, the second field is the gate
114 number, the third field is the plane's status as a character."""
115 try:
116 words = s.split(" ")
117 tailNumber = words[0]
118 self.tailNumber = tailNumber
119
120 status = words[2] if len(words)>2 else None
121 self.status = const.PLANE_HOME if status=="H" else \
122 const.PLANE_AWAY if status=="A" else \
123 const.PLANE_PARKING if status=="P" else \
124 const.PLANE_UNKNOWN
125
126 gateNumber = words[1] if len(words)>1 else ""
127 self.gateNumber = gateNumber if gateNumber else None
128
129 except:
130 print >> sys.stderr, "Plane string is invalid: '" + s + "'"
131 self.tailNumber = None
132
133 def __repr__(self):
134 """Get the representation of the plane object."""
135 s = "<Plane: %s %s" % (self.tailNumber,
136 "home" if self.status==const.PLANE_HOME else \
137 "away" if self.status==const.PLANE_AWAY else \
138 "parking" if self.status==const.PLANE_PARKING \
139 else "unknown")
140 if self.gateNumber is not None:
141 s += " (gate " + self.gateNumber + ")"
142 s += ">"
143 return s
144
145
146#------------------------------------------------------------------------------
147
148class Fleet(object):
149 """Information about the whole fleet."""
150 def __init__(self, f):
151 """Construct the fleet information by reading the given file object."""
152 self._planes = {}
153 while True:
154 line = readline(f)
155 if not line or line == "#END": break
156
157 plane = Plane(line)
158 if plane.tailNumber is not None:
159 self._planes[plane.tailNumber] = plane
160
161 def isGateConflicting(self, plane):
162 """Check if the gate of the given plane conflicts with another plane's
163 position."""
164 for p in self._planes.itervalues():
165 if p.tailNumber!=plane.tailNumber and \
166 p.status==const.PLANE_HOME and \
167 p.gateNumber==plane.gateNumber:
168 return True
169
170 return False
171
172 def getOccupiedGateNumbers(self):
173 """Get a set containing the numbers of the gates occupied by planes."""
174 gateNumbers = set()
175 for p in self._planes.itervalues():
176 if p.status==const.PLANE_HOME and p.gateNumber:
177 gateNumbers.add(p.gateNumber)
178 return gateNumbers
179
180 def __getitem__(self, tailNumber):
181 """Get the plane with the given tail number.
182
183 If the plane is not in the fleet, None is returned."""
184 return self._planes[tailNumber] if tailNumber in self._planes else None
185
186 def __repr__(self):
187 """Get the representation of the fleet object."""
188 return self._planes.__repr__()
189
190#------------------------------------------------------------------------------
191
192class NOTAM(object):
193 """A NOTAM for an airport."""
194 def __init__(self, begin, notice, end = None, permanent = False,
195 repeatCycle = None):
196 """Construct the NOTAM."""
197 self.begin = begin
198 self.notice = notice
199 self.end = end
200 self.permanent = permanent
201 self.repeatCycle = None
202
203 def __repr__(self):
204 """Get the representation of the NOTAM."""
205 s = "<NOTAM " + str(self.begin)
206 if self.end:
207 s += " - " + str(self.end)
208 elif self.permanent:
209 s += " - PERMANENT"
210 if self.repeatCycle:
211 s += " (" + self.repeatCycle + ")"
212 s += ": " + self.notice
213 s += ">"
214 return s
215
216#------------------------------------------------------------------------------
217
218class NOTAMHandler(xml.sax.handler.ContentHandler):
219 """A handler for the NOTAM database."""
220 def __init__(self, airportICAOs):
221 """Construct the handler for the airports with the given ICAO code."""
222 self._notams = {}
223 for icao in airportICAOs:
224 self._notams[icao] = []
225
226 def startElement(self, name, attrs):
227 """Start an element."""
228 if name!="notam" or \
229 "A" not in attrs or not attrs["A"] or \
230 "B" not in attrs or not attrs["B"] or \
231 "E" not in attrs or not attrs["E"]:
232 return
233
234 icao = attrs["A"]
235 if icao not in self._notams:
236 return
237
238 begin = datetime.datetime.strptime(attrs["B"], "%Y-%m-%d %H:%M:%S")
239
240 c = attrs["C"] if "C" in attrs else None
241 end = datetime.datetime.strptime(c, "%Y-%m-%d %H:%M:%S") if c else None
242
243 permanent = attrs["C_flag"]=="PERM" if "C_flag" in attrs else False
244
245 repeatCycle = attrs["D"] if "D" in attrs else None
246
247 self._notams[icao].append(NOTAM(begin, attrs["E"], end = end,
248 permanent = permanent,
249 repeatCycle = repeatCycle))
250
251 def get(self, icao):
252 """Get the NOTAMs for the given ICAO code."""
253 return self._notams[icao] if icao in self._notams else []
254
255#------------------------------------------------------------------------------
256
257class Result(object):
258 """A result object.
259
260 An instance of this filled with the appropriate data is passed to the
261 callback function on each request."""
262
263 def __repr__(self):
264 """Get a representation of the result."""
265 s = "<Result:"
266 for (key, value) in self.__dict__.iteritems():
267 s += " " + key + "=" + unicode(value)
268 s += ">"
269 return s
270
271#------------------------------------------------------------------------------
272
273class Request(object):
274 """Base class for requests.
275
276 It handles any exceptions and the calling of the callback.
277
278 If an exception occurs during processing, the callback is called with
279 the two parameters: a boolean value of False, and the exception object.
280
281 If no exception occurs, the callback is called with True and the return
282 value of the run() function.
283
284 If the callback function throws an exception, that is caught and logged
285 to the debug log."""
286 def __init__(self, callback):
287 """Construct the request."""
288 self._callback = callback
289
290 def perform(self):
291 """Perform the request.
292
293 The object's run() function is called. If it throws an exception,
294 the callback is called with False, and the exception. Otherwise the
295 callback is called with True and the return value of the run()
296 function. Any exceptions thrown by the callback are caught and
297 reported."""
298 try:
299 result = self.run()
300 returned = True
301 except Exception, e:
302 result = e
303 returned = False
304
305 try:
306 self._callback(returned, result)
307 except Exception, e:
308 print >> sys.stderr, "web.Handler.Request.perform: callback throwed an exception: " + str(e)
309
310#------------------------------------------------------------------------------
311
312class Login(Request):
313 """A login request."""
314 iso88592decoder = codecs.getdecoder("iso-8859-2")
315
316 def __init__(self, callback, pilotID, password):
317 """Construct the login request with the given pilot ID and
318 password."""
319 super(Login, self).__init__(callback)
320
321 self._pilotID = pilotID
322 self._password = password
323
324 def run(self):
325 """Perform the login request."""
326 md5 = hashlib.md5()
327 md5.update(self._pilotID)
328 pilotID = md5.hexdigest()
329
330 md5 = hashlib.md5()
331 md5.update(self._password)
332 password = md5.hexdigest()
333
334 url = "http://www.virtualairlines.hu/leker2.php?pid=%s&psw=%s" % \
335 (pilotID, password)
336
337 result = Result()
338
339 f = urllib2.urlopen(url)
340
341 status = readline(f)
342 result.loggedIn = status == ".OK."
343
344 if result.loggedIn:
345 result.pilotName = self.iso88592decoder(readline(f))[0]
346 result.exams = readline(f)
347 result.flights = []
348
349 while True:
350 line = readline(f)
351 if not line or line == "#ENDPIREP": break
352
353 flight = BookedFlight(line, f)
354 result.flights.append(flight)
355
356 result.flights.sort(cmp = lambda flight1, flight2:
357 cmp(flight1.departureTime,
358 flight2.departureTime))
359
360 f.close()
361
362 return result
363
364#------------------------------------------------------------------------------
365
366class GetFleet(Request):
367 """Request to get the fleet from the website."""
368
369 def __init__(self, callback):
370 """Construct the fleet request."""
371 super(GetFleet, self).__init__(callback)
372
373 def run(self):
374 """Perform the login request."""
375 url = "http://www.virtualairlines.hu/onlinegates_get.php"
376
377 f = urllib2.urlopen(url)
378 result = Result()
379 result.fleet = Fleet(f)
380 f.close()
381
382 return result
383
384#------------------------------------------------------------------------------
385
386class UpdatePlane(Request):
387 """Update the status of one of the planes in the fleet."""
388 def __init__(self, callback, tailNumber, status, gateNumber = None):
389 """Construct the request."""
390 super(UpdatePlane, self).__init__(callback)
391 self._tailNumber = tailNumber
392 self._status = status
393 self._gateNumber = gateNumber
394
395 def run(self):
396 """Perform the plane update."""
397 url = "http://www.virtualairlines.hu/onlinegates_set.php"
398
399 status = "H" if self._status==const.PLANE_HOME else \
400 "A" if self._status==const.PLANE_AWAY else \
401 "P" if self._status==const.PLANE_PARKING else ""
402
403 gateNumber = self._gateNumber if self._gateNumber else ""
404
405 data = urllib.urlencode([("lajstrom", self._tailNumber),
406 ("status", status),
407 ("kapu", gateNumber)])
408
409 f = urllib2.urlopen(url, data)
410 line = readline(f)
411
412 result = Result()
413 result.success = line == "OK"
414
415 return result
416
417#------------------------------------------------------------------------------
418
419class GetNOTAMs(Request):
420 """Get the NOTAMs from EURoutePro and select the ones we are interested
421 in."""
422 def __init__(self, callback, departureICAO, arrivalICAO):
423 """Construct the request for the given airports."""
424 super(GetNOTAMs, self).__init__(callback)
425 self._departureICAO = departureICAO
426 self._arrivalICAO = arrivalICAO
427
428 def run(self):
429 """Perform the retrieval of the NOTAMs."""
430 xmlParser = xml.sax.make_parser()
431 notamHandler = NOTAMHandler([self._departureICAO, self._arrivalICAO])
432 xmlParser.setContentHandler(notamHandler)
433
434 url = "http://notams.euroutepro.com/notams.xml"
435
436 f = urllib2.urlopen(url)
437 try:
438 xmlParser.parse(f)
439 finally:
440 f.close()
441
442 result = Result()
443 result.departureNOTAMs = notamHandler.get(self._departureICAO)
444 result.arrivalNOTAMs = notamHandler.get(self._arrivalICAO)
445
446 return result
447
448#------------------------------------------------------------------------------
449
450class GetMETARs(Request):
451 """Get the METARs from the NOAA website for certain airport ICAOs."""
452
453 def __init__(self, callback, airports):
454 """Construct the request for the given airports."""
455 super(GetMETARs, self).__init__(callback)
456 self._airports = airports
457
458 def run(self):
459 """Perform the retrieval opf the METARs."""
460 url = "http://www.aviationweather.gov/adds/dataserver_current/httpparam?"
461 data = urllib.urlencode([ ("dataSource" , "metars"),
462 ("requestType", "retrieve"),
463 ("format", "csv"),
464 ("stationString", " ".join(self._airports)),
465 ("hoursBeforeNow", "24"),
466 ("mostRecentForEachStation", "constraint")])
467 url += data
468 f = urllib2.urlopen(url)
469 try:
470 result = Result()
471 result.metars = {}
472 for line in iter(f.readline, ""):
473 if len(line)>5 and line[4]==' ':
474 icao = line[0:4]
475 if icao in self._airports:
476 result.metars[icao] = line.strip().split(",")[0]
477 finally:
478 f.close()
479
480 return result
481
482#------------------------------------------------------------------------------
483
484class Handler(threading.Thread):
485 """The handler for the web services.
486
487 It can process one request at a time. The results are passed to a callback
488 function."""
489 def __init__(self):
490 """Construct the handler."""
491 super(Handler, self).__init__()
492
493 self._requests = []
494 self._requestCondition = threading.Condition()
495
496 self.daemon = True
497
498 def login(self, callback, pilotID, password):
499 """Enqueue a login request."""
500 self._addRequest(Login(callback, pilotID, password))
501
502 def getFleet(self, callback):
503 """Enqueue a fleet retrieval request."""
504 self._addRequest(GetFleet(callback))
505
506 def updatePlane(self, callback, tailNumber, status, gateNumber = None):
507 """Update the status of the given plane."""
508 self._addRequest(UpdatePlane(callback, tailNumber, status, gateNumber))
509
510 def getNOTAMs(self, callback, departureICAO, arrivalICAO):
511 """Get the NOTAMs for the given two airports."""
512 self._addRequest(GetNOTAMs(callback, departureICAO, arrivalICAO))
513
514 def getMETARs(self, callback, airports):
515 """Get the METARs for the given airports."""
516 self._addRequest(GetMETARs(callback, airports))
517
518 def run(self):
519 """Process the requests."""
520 while True:
521 with self._requestCondition:
522 while not self._requests:
523 self._requestCondition.wait()
524 request = self._requests[0]
525 del self._requests[0]
526
527 request.perform()
528
529 def _addRequest(self, request):
530 """Add the given request to the queue."""
531 with self._requestCondition:
532 self._requests.append(request)
533 self._requestCondition.notify()
534
535#------------------------------------------------------------------------------
536
537if __name__ == "__main__":
538 import time
539
540 def callback(returned, result):
541 print returned, unicode(result)
542
543 handler = Handler()
544 handler.start()
545
546 #handler.login(callback, "P096", "V5fwj")
547 #handler.getFleet(callback)
548 # Plane: HA-LEG home (gate 67)
549 #handler.updatePlane(callback, "HA-LQC", const.PLANE_AWAY, "72")
550 #time.sleep(3)
551 #handler.getFleet(callback)
552 #time.sleep(3)
553
554 #handler.getNOTAMs(callback, "LHBP", "EPWA")
555 handler.getMETARs(callback, ["LHBP", "EPWA"])
556 time.sleep(5)
557
558
559#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.