source: src/mlx/pirep.py@ 918:cfa4d89368fa

Last change on this file since 918:cfa4d89368fa was 908:fcf3c44650f1, checked in by István Váradi <ivaradi@…>, 7 years ago

Basic functions work with the new website (re #332)

File size: 14.8 KB
Line 
1
2from util import utf2unicode
3from flight import Flight
4
5import const
6import cPickle as pickle
7import calendar
8import datetime
9import time
10
11#------------------------------------------------------------------------------
12
13## @package mlx.pirep
14#
15# The PIREP module.
16#
17# This module defines only one class, \ref PIREP. It is used to extract and
18# store the information needed for a PIREP. The saved PIREPs are pickled
19# instances of this class.
20
21#------------------------------------------------------------------------------
22
23class PIREP(object):
24 """A pilot's report of a flight."""
25 class Message(object):
26 """A message belonging to the PIREP."""
27 @staticmethod
28 def fromMessageData(messageData):
29 """Construct a message from a JSON message data."""
30 message = messageData["message"]
31 senderPID = messageData["senderPID"]
32 senderName = messageData["senderName"]
33
34 return PIREP.Message(message, senderPID, senderName)
35
36 def __init__(self, message, senderPID, senderName):
37 """Construct the message object."""
38 self.message = message
39 self.senderPID = senderPID
40 self.senderName = senderName
41
42 _flightTypes = { const.FLIGHTTYPE_SCHEDULED : "SCHEDULED",
43 const.FLIGHTTYPE_OLDTIMER : "OT",
44 const.FLIGHTTYPE_VIP : "VIP",
45 const.FLIGHTTYPE_CHARTER : "CHARTER" }
46
47 @staticmethod
48 def _formatLine(timeStr, line):
49 """Format the given time string and line as needed for the ACARS and
50 some other things."""
51 return "[" + timeStr + "]-[" + line + "]"
52
53 @staticmethod
54 def formatTimestampForRPC(t):
55 """Format the given timestamp for RPC."""
56 return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(t))
57
58 @staticmethod
59 def parseTimestampFromRPC(s):
60 """Format the given timestamp for RPC."""
61 dt = datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
62 return calendar.timegm(dt.utctimetuple())
63
64 @staticmethod
65 def decodeFlightTypeText(s):
66 """Decode the given flight type text."""
67 for (flighType, text) in PIREP._flightTypes.iteritems():
68 if s==text:
69 return flighType
70 return const.FLIGHTTYPE_SCHEDULED
71
72 @staticmethod
73 def parseLogFromRPC(log):
74 """Parse the given log coming from the RPC."""
75 index = 0
76 entries = []
77
78 inTimeStr = False
79 inEntry = False
80
81 timestr = ""
82 entry = ""
83
84 while index<len(log):
85 c = log[index]
86 index += 1
87
88 if c==']':
89 if inEntry:
90 entries.append((timestr, entry))
91 timestr = ""
92 entry = ""
93
94 inTimeStr = False
95 inEntry = False
96 elif not inTimeStr and not inEntry:
97 if c=='[':
98 if timestr:
99 inEntry = True
100 else:
101 inTimeStr = True
102 elif inTimeStr:
103 timestr += c
104 elif inEntry:
105 entry += c
106
107 return entries
108
109 @staticmethod
110 def load(path):
111 """Load a PIREP from the given path.
112
113 Returns the PIREP object, or None on error."""
114 try:
115 with open(path, "rb") as f:
116 pirep = pickle.load(f)
117 if "numCrew" not in dir(pirep):
118 pirep.numCrew = pirep.bookedFlight.numCrew
119 if "numPassengers" not in dir(pirep):
120 pirep.numPassengers = pirep.bookedFlight.numPassengers
121 if "bagWeight" not in dir(pirep):
122 pirep.bagWeight = pirep.bookedFlight.bagWeight
123 if "mailWeight" not in dir(pirep):
124 pirep.mailWeight = pirep.bookedFlight.mailWeight
125 return pirep
126 except Exception, e:
127 print "Failed loading PIREP from %s: %s" % (path,
128 utf2unicode(str(e)))
129 return None
130
131 def __init__(self, flight):
132 """Initialize the PIREP from the given flight."""
133 if flight is None:
134 return
135
136 self.bookedFlight = flight.bookedFlight
137
138 self.numCockpitCrew = flight.numCockpitCrew
139 self.numCabinCrew = flight.numCabinCrew
140 self.numCrew = flight.numCrew
141 self.numPassengers = flight.numPassengers
142 self.bagWeight = flight.bagWeight
143 self.cargoWeight = flight.cargoWeight
144 self.mailWeight = flight.mailWeight
145
146 self.filedCruiseAltitude = flight.filedCruiseAltitude
147 self.cruiseAltitude = flight.cruiseAltitude
148 self.route = flight.route
149
150 self.departureMETAR = flight.departureMETAR.upper()
151 self.arrivalMETAR = flight.arrivalMETAR.upper()
152
153 self.departureRunway = flight.departureRunway.upper()
154 self.sid = flight.sid.upper()
155
156 self.star = flight.star
157 self.transition = flight.transition
158 self.approachType = flight.approachType.upper()
159 self.arrivalRunway = flight.arrivalRunway.upper()
160
161 self.flightType = flight.flightType
162 self.online = flight.online
163
164 self.comments = flight.comments
165 self.flightDefects = flight.flightDefects
166 self.delayCodes = flight.delayCodes
167
168 self.blockTimeStart = flight.blockTimeStart
169 self.flightTimeStart = flight.flightTimeStart
170 self.flightTimeEnd = flight.flightTimeEnd
171 self.blockTimeEnd = flight.blockTimeEnd
172 self.flownDistance = flight.flownDistance
173 self.fuelUsed = flight.startFuel - flight.endFuel
174
175 logger = flight.logger
176 self.rating = logger.getRating()
177 self.logLines = logger.lines
178 self.faultLineIndexes = logger.faultLineIndexes
179
180 self.messages = []
181
182 def setupFromPIREPData(self, pirepData, bookedFlight):
183
184 self.bookedFlight = bookedFlight
185
186 self.numCockpitCrew = int(pirepData["numCockpitCrew"])
187 self.numCabinCrew = int(pirepData["numCabinCrew"])
188 self.numCrew = self.numCockpitCrew + self.numCabinCrew
189 self.numPassengers = int(pirepData["numPassengers"])
190 self.bagWeight = int(pirepData["bagWeight"])
191 self.cargoWeight = int(pirepData["cargoWeight"])
192 self.mailWeight = int(pirepData["mailWeight"])
193
194 filedCruiseLevel = pirepData["filedCruiseLevel"].strip()
195 if filedCruiseLevel:
196 if filedCruiseLevel.startswith("FL"):
197 filedCruiseLevel = filedCruiseLevel[2:]
198 if filedCruiseLevel:
199 self.filedCruiseAltitude = int(filedCruiseLevel)*100
200 else:
201 self.filedCruiseAltitude = 10000;
202
203 cruiseLevel = pirepData["cruiseLevel"].strip()
204 if cruiseLevel:
205 if cruiseLevel.startswith("FL"):
206 cruiseLevel = cruiseLevel[2:]
207 if cruiseLevel:
208 self.cruiseAltitude = int(cruiseLevel[2:])*100
209 else:
210 self.cruiseAltitude = self.filedCruiseAltitude
211 self.route = pirepData["route"]
212
213 self.departureMETAR = pirepData["departureMETAR"]
214 self.arrivalMETAR = pirepData["arrivalMETAR"]
215
216 self.departureRunway = pirepData["departureRunway"]
217 self.sid = pirepData["sid"]
218
219 star = pirepData["star"].split(",")
220 self.star = star[0]
221 self.star.strip()
222
223 if len(star)>1:
224 self.transition = star[1]
225 self.transition.strip()
226 else:
227 self.transition = ""
228 self.approachType = pirepData["approachType"]
229 self.arrivalRunway = pirepData["arrivalRunway"]
230
231 self.flightType = PIREP.decodeFlightTypeText(pirepData["flightType"])
232 self.online = int(pirepData["online"])!=0
233
234 self.comments = pirepData["comments"]
235 self.flightDefects = pirepData["flightDefects"]
236 self.delayCodes = pirepData["timeComment"]
237 if self.delayCodes=="UTC":
238 self.delayCodes = []
239 else:
240 self.delayCodes = self.delayCodes.split(", ")
241
242 flightDate = pirepData["flightDate"] + " "
243
244 self.blockTimeStart = \
245 PIREP.parseTimestampFromRPC(flightDate + pirepData["blockTimeStart"])
246 self.flightTimeStart = \
247 PIREP.parseTimestampFromRPC(flightDate + pirepData["flightTimeStart"])
248 self.flightTimeEnd = \
249 PIREP.parseTimestampFromRPC(flightDate + pirepData["flightTimeEnd"])
250 self.blockTimeEnd = \
251 PIREP.parseTimestampFromRPC(flightDate + pirepData["blockTimeEnd"])
252 self.flownDistance = float(pirepData["flownDistance"])
253 self.fuelUsed = float(pirepData["fuelUsed"])
254
255 # logger = flight.logger
256 self.rating = float(pirepData["rating"])
257
258 log = pirepData["log"]
259
260 self.logLines = PIREP.parseLogFromRPC(log)[1:]
261 if self.logLines and \
262 (self.logLines[0][0]=="LOGGER NG LOG" or
263 self.logLines[0][0]=="MAVA LOGGER X"):
264 self.logLines = self.logLines[1:]
265 numLogLines = len(self.logLines)
266
267 lastFaultLineIndex = 0
268 self.faultLineIndexes = []
269 for ratingText in pirepData["ratingText"].splitlines()[:-1]:
270 faultLines = PIREP.parseLogFromRPC(ratingText)
271 for (timeStr, entry) in faultLines:
272 for i in range(lastFaultLineIndex, numLogLines-1):
273 if timeStr>=self.logLines[i][0] and \
274 timeStr<self.logLines[i+1][0]:
275 self.logLines = self.logLines[:i+1] + \
276 [(timeStr, entry)] + self.logLines[i+1:]
277 self.faultLineIndexes.append(i+1)
278 lastFaultLineIndex = i+1
279 numLogLines += 1
280 break
281
282 self.messages = []
283 for messageData in pirepData["messages"]:
284 self.messages.append(PIREP.Message.fromMessageData(messageData))
285
286 @property
287 def flightDateText(self):
288 """Get the text version of the booked flight's departure time."""
289 return self.bookedFlight.departureTime.strftime("%Y-%m-%d")
290
291 @property
292 def flightTypeText(self):
293 """Get the text representation of the flight type."""
294 return PIREP._flightTypes[self.flightType]
295
296 @property
297 def blockTimeStartText(self):
298 """Get the beginning of the block time in string format."""
299 return PIREP.formatTimestampForRPC(self.blockTimeStart)
300
301 @property
302 def flightTimeStartText(self):
303 """Get the beginning of the flight time in string format."""
304 return PIREP.formatTimestampForRPC(self.flightTimeStart)
305
306 @property
307 def flightTimeEndText(self):
308 """Get the end of the flight time in string format."""
309 return PIREP.formatTimestampForRPC(self.flightTimeEnd)
310
311 @property
312 def blockTimeEndText(self):
313 """Get the end of the block time in string format."""
314 return PIREP.formatTimestampForRPC(self.blockTimeEnd)
315
316 def getACARSText(self):
317 """Get the ACARS text.
318
319 This is a specially formatted version of the log without the faults."""
320 text = "[MAVA LOGGER X LOG]-[%s]" % (const.VERSION,)
321 for index in range(0, len(self.logLines)):
322 if index not in self.faultLineIndexes:
323 (timeStr, line) = self.logLines[index]
324 if timeStr is not None:
325 text += PIREP._formatLine(timeStr, line)
326 return text
327
328 def getRatingText(self):
329 """Get the rating text.
330
331 This is a specially formatted version of the lines containing the
332 faults."""
333 text = ""
334 for index in self.faultLineIndexes:
335 (timeStr, line) = self.logLines[index]
336 if timeStr is not None:
337 text += PIREP._formatLine(timeStr, line)
338 text += "\n"
339
340 text += "\n[Flight Rating: %.1f]" % (max(0.0, self.rating),)
341
342 return text
343
344 def getTimeComment(self):
345 """Get the time comment.
346
347 This is basically a collection of the delay codes, if any."""
348 if not self.delayCodes:
349 return "UTC"
350 else:
351 s = ""
352 for code in self.delayCodes:
353 if s: s += ", "
354 s += code
355 return s
356
357 def getSTAR(self):
358 """Get the STAR and/or the transition."""
359 star = self.star if self.star is not None else ""
360 if self.transition is not None:
361 if star: star += ", "
362 star += self.transition
363 return star.upper()
364
365 def save(self, path):
366 """Save the PIREP to the given file.
367
368 Returns whether the saving has succeeded."""
369 try:
370 with open(path, "wb") as f:
371 pickle.dump(self, f)
372 return None
373 except Exception, e:
374 error = utf2unicode(str(e))
375 print u"Failed saving PIREP to %s: %s" % (path, error)
376 return error
377
378 def _serialize(self):
379 """Serialize the PIREP for JSON-RPC."""
380 attrs = {}
381 attrs["log"] = self.getACARSText()
382 attrs["flightDate"] = self.flightDateText
383 attrs["callsign"] = self.bookedFlight.callsign
384 attrs["departureICAO"] = self.bookedFlight.departureICAO
385 attrs["arrivalICAO"] = self.bookedFlight.arrivalICAO
386 attrs["numPassengers"] = self.numPassengers
387 attrs["numCockpitCrew"] = self.numCockpitCrew
388 attrs["numCabinCrew"] = self.numCabinCrew
389 attrs["numCrew"] = self.numCrew
390 attrs["cargoWeight"] = self.cargoWeight
391 attrs["bagWeight"] = self.bagWeight
392 attrs["mailWeight"] = self.mailWeight
393 attrs["flightType"] = self.flightTypeText
394 attrs["online"] = 1 if self.online else 0
395 attrs["blockTimeStart"] = self.blockTimeStartText
396 attrs["blockTimeEnd"] = self.blockTimeEndText
397 attrs["flightTimeStart"] = self.flightTimeStartText
398 attrs["flightTimeEnd"] = self.flightTimeEndText
399 attrs["timeComment"] = self.getTimeComment()
400 attrs["fuelUsed"] = self.fuelUsed
401 attrs["departureRunway"] = self.departureRunway
402 attrs["arrivalRunway"] = self.arrivalRunway
403 attrs["departureMETAR"] = self.departureMETAR
404 attrs["arrivalMETAR"] = self.arrivalMETAR
405 attrs["filedCruiseLevel"] = self.filedCruiseAltitude / 100.0
406 attrs["cruiseLevel"] = self.cruiseAltitude / 100.0
407 attrs["sid"] = self.sid
408 attrs["route"] = self.route
409 attrs["star"] = self.star
410 attrs["approachType"] = self.approachType
411 attrs["comments"] = self.comments
412 attrs["flightDefects"] = self.flightDefects
413 attrs["ratingText"] = self.getRatingText()
414 attrs["rating"] = max(0.0, self.rating)
415 attrs["flownDistance"] = "%.2f" % (self.flownDistance,)
416 # FIXME: it should be stored in the PIREP when it is sent later
417 attrs["performDate"] = datetime.date.today().strftime("%Y-%m-%d")
418
419 return ([], attrs)
Note: See TracBrowser for help on using the repository browser.