NCCOOS Trac Projects: Top | Web | Platforms | Processing | Viz | Sprints | Sandbox | (Wind)

root/pyglider/trunk/pyglider/pyglider.py

Revision 485 (checked in by haines, 12 years ago)

added ABORT message to previous surface report

Line 
1 """pyglider.py A module of utilities to run on dockserver for glider operations
2
3    parse_glider_mail
4    generate_track_kml
5    
6 """
7
8 REAL_RE_STR = '\\s*(-?\\d(\\.\\d+|)[Ee][+\\-]\\d\\d?|-?(\\d+\\.\\d*|\\d*\\.\\d+)|-?\\d+)\\s*'
9
10 import sys
11 import os
12 import re
13
14 import time
15 import datetime
16
17 def load_data(inFile):
18     lines=None
19     if os.path.exists(inFile):
20         f = open(inFile, 'r')
21         lines = f.readlines()
22         f.close()
23         if len(lines)<=0:
24             print 'Empty file: '+ inFile
25     else:
26         print 'File does not exist: '+ inFile
27     return lines
28
29 def display_time_diff(diff):
30     """Display time difference in HH:MM and days (D) if necessary"""
31     days = diff.days
32     minutes, seconds = divmod(diff.seconds, 60)
33     hours, minutes = divmod(minutes, 60)
34     if (days<=1):
35         if minutes<1:
36             str = "%02:%02d:%02d" % (hours, minutes,seconds)
37         else:
38             str = "%02d:%02d" % (hours, minutes)
39     elif (days>1):
40         str = "%d Days %02d:%02d" % (days, hours, minutes)
41     else:
42         str = "%02d:%02d" % (hours, minutes)
43     return str
44                                                                    
45 # -------------------------------------------------------------------   
46 # playground
47 # fn = '/var/spool/mail/localuser'
48 # lines = load_data(fn)
49 # glider = 'ramses'
50 # data = parse_glider_mail(lines, glider)
51
52 # -------------------------------------------------------------------   
53 def parse_glider_mail(lines, glider):
54     msg_split_patt = r'From root\@dockserver'
55     msgs = re.split(msg_split_patt, ''.join(lines))
56    
57     gms = []
58     # select messages for specific glider based on "Subject:" line
59     for msg in msgs:
60         m = re.search(r'^Subject: Glider: (\w*)', msg, re.MULTILINE)
61         if m:
62             if m.group(1) == glider:
63                 gms.append(msg)
64
65     data = []
66     for msg in gms:
67         m = re.search(r'^Subject: Glider: (\w*).*$', msg, re.MULTILINE)
68         if m:
69             subject_string = m.group(0)
70             subject_glider = m.group(1)
71             # print subject_string
72         else:
73             continue
74
75         m = re.search(r'\s*(Event)*: (.*?)', subject_string, re.MULTILINE)
76         if m:
77             subject_event = m.group(2)
78         else:
79             subject_event = None
80
81         m = re.search(r'\s*(Reason)*:\s*(.*)$', subject_string, re.MULTILINE)
82         if m:
83             subject_reason = m.group(2)
84         else:
85             subject_reason = None
86
87         m = re.search(r'^(Vehicle Name:)\s+(\w*)', msg, re.MULTILINE)
88         if m: glidername = m.group(2)
89         else: glidername = None
90        
91         m = re.search(r'^(Curr Time:)\s+(.*)\s+MT:', msg, re.MULTILINE)
92         if m:
93             try:
94                 t = time.strptime(m.group(2), "%a %b %d %H:%M:%S %Y")
95                 # the '*' operator unpacks the tuple, producing the argument list.
96                 dt = datetime.datetime(*t[0:6])
97                 diff = datetime.datetime.utcnow() - dt
98                 hours_ago = display_time_diff(diff)
99                 dt_str = datetime.date.strftime(dt, "%Y-%m-%d %H:%M:%S UTC")
100                 if (diff.days)>0:
101                      # dt_str_short = datetime.date.strftime(dt, "%b-%d")
102                      dt_str_short = ""                     
103                 if (diff.days) <= 0:
104                     dt_str_short = datetime.date.strftime(dt, "%H:%M")
105             except ValueError, e:
106                 dt_str = None
107                 dt_str_short = None
108                 hours_ago = None
109
110         m = re.search(r'^(GPS Location:)\s+(-?\d{2})(\d{2}\.\d+)\s+([NnSs])'+ \
111                       r'\s+(-?\d{2})(\d{2}\.\d+)\s+([EeWw]).*$', msg, re.MULTILINE)
112         if m:
113             #
114             lat_deg = float(m.group(2))
115             lat_min = float(m.group(3))
116             lat_hem = m.group(4).upper()
117             #
118             lon_deg = float(m.group(5))
119             lon_min = float(m.group(6))
120             lon_hem = m.group(7).upper()
121             if lat_deg<0:
122                 lat = lat_deg - lat_min/60.
123             else:
124                 lat = lat_deg + lat_min/60.
125             if lon_deg<0:
126                 lon = lon_deg - lon_min/60.
127             else:
128                 lon = lon_deg + lon_min/60.
129             gps_str = m.group(0)
130             m = re.search(r'GPS Location: (.*) measured', gps_str)
131             if m: gps_posn_str = m.group(1)
132             else: gps_posn_str = None
133             m = re.search(r'measured\s*(\d*)\.\d* secs ago', gps_str)
134             if m:
135                 gps_secs_ago = int(m.group(1))
136                 gps_dt = dt - datetime.timedelta(0,gps_secs_ago,0)
137                 gps_dt_str = datetime.date.strftime(gps_dt, "%Y-%m-%d %H:%M:%S UTC")
138             else: gps_dt_str = None
139         else:
140             lat = None
141             lon = None
142
143         m = re.search(r'(MT:)\s+(.*)$', msg, re.MULTILINE)
144         if m: mt = m.group(2)
145         else: mt = None
146
147         m = re.search(r'(sensor:m_battery.*?=)\s*(-?\d+\.\d*)', msg, re.MULTILINE)
148         if m: batt = float(m.group(2))
149         else: batt = None
150
151         m = re.search(r'(sensor:m_leakdetect.*?=)\s*(-?\d+\.\d*)', msg, re.MULTILINE)
152         if m: leak = float(m.group(2))
153         else: leak = None
154
155         m = re.search(r'(sensor:m_vacuum.*?=)\s*(-?\d+\.\d*)', msg, re.MULTILINE)
156         if m: vacuum = float(m.group(2))
157         else: vacuum = None
158        
159         m = re.search(r'^(Because:)\s*(.*)', msg, re.MULTILINE)
160         if m: because = m.group(2)
161         else:  because = 'Unknown'
162
163         m = re.search(r'^(MissionName:)\s*(.*?)\s+', msg, re.MULTILINE)
164         if m: mission_name = m.group(2)
165         else: mission_name = 'Unknown'
166
167         m = re.search(r'\s+(MissionNum:)(.*)', msg, re.MULTILINE)
168         if m: mission_num = m.group(2)
169         else: mission_num = 'Unknown'
170
171         m = re.search(r'^(Waypoint:)\s+(\(.*\)).*$', msg, re.MULTILINE)
172         if m:
173             wp_str = m.group(0)
174             waypoint_posn = m.group(2)
175         else:
176             wp_str = None
177             waypoint_posn = 'Unknown'
178             waypoint_range = 'Unknown'
179             waypoint_bearing = 'Unknown'
180             waypoint_age = 'Unknown'
181             wlat=None
182             wlon=None
183
184         if wp_str:
185             # parse out the next waypoint for a place mark
186             m = re.search(r'\((-?\d{2})(\d{2}\.\d+),(-?\d{2})(\d{2}\.\d+)\)', waypoint_posn)
187             if m:
188                 #
189                 lat_deg = float(m.group(1))
190                 lat_min = float(m.group(2))
191                 #
192                 lon_deg = float(m.group(3))
193                 lon_min = float(m.group(4))
194                 if lat_deg<0:
195                     wlat = lat_deg - lat_min/60.
196                 else:
197                     wlat = lat_deg + lat_min/60.
198                 if lon_deg<0:
199                     wlon = lon_deg - lon_min/60.
200                 else:
201                     wlon = lon_deg + lon_min/60.
202             else:
203                 wlat = None
204                 wlon = None
205             m = re.search(r'(Range:)\s+(.*?),', wp_str)
206             if m: waypoint_range = m.group(2)
207             else: waypoint_range = 'Unknown'
208             m = re.search(r'(Bearing:)\s+(.*?),', wp_str)
209             if m: waypoint_bearing = m.group(2)
210             else: waypoint_bearing = 'Unknown'
211             m = re.search(r'(Age:)\s+(.*?)$', wp_str)
212             if m: waypoint_age = m.group(2)
213             else: waypoint_age = 'Unknown'
214
215         if lat and lon:   
216             # generate report table for google earth
217             # using ![CDATA[ {html} ]] inside of KML description tag
218             html_report = [
219                 ' ',
220                 '<div id="main">',
221                 '<h3>Surface Report</h3>',
222                 '<table>',
223                 '<thead>',
224                 '<tr><th>Glider: %s</th><th>%s</th></tr>' % (glider, dt_str,),
225                 '</thead>',
226                 '<tbody>']       
227             html_report.extend(
228                 [
229                  '<tr><td>GPS Location:</td><td>%s</td></tr>' % (gps_posn_str,),
230                  '<tr><td>GPS Time:</td><td>%s</td></tr>' % (gps_dt_str,)
231                  ])
232             html_report.extend(
233                 [
234                  '<tr><td>Surface Event:</td><td>%s</td></tr>' % (subject_event,),
235                  '<tr><td>Surface Reason:</td><td>%s</td></tr>' % (subject_reason,),
236                  '<tr><td>Surface Because:</td><td>%s</td></tr>' % (because,),
237                  ])
238             html_report.extend(
239                 [
240                 '<tr><td>Mission Name:</td><td>%s</td></tr>' % (mission_name,),
241                 '<tr><td>Mission Number:</td><td>%s</td></tr>' % (mission_num,),
242                 '<tr><td>Mission Time:</td><td>%s</td></tr>' % (mt,)
243                 ])
244
245             if batt:
246                 if batt < 10.:
247                     html_report.append('<tr><td>Battery (volts):</td><td class="red_td">%g</td></tr>' % (batt,))
248                 elif (batt>=10) and (batt<11):
249                     html_report.append('<tr><td>Battery (volts):</td><td class="yellow_td">%g</td></tr>' % (batt,))
250                 else:
251                     html_report.append('<tr><td>Battery (volts):</td><td>%f</td></tr>' % (batt,))
252             else:
253                 html_report.append('<tr><td>Battery (volts):</td><td>Unknown</td></tr>')
254
255             if vacuum:
256                 if (vacuum>=10) or (vacuum<6):
257                     html_report.append('<tr><td>Vacuum (inHg):</td><td class="yellow_td">%g</td></tr>' % (vacuum,))
258                 else:
259                     html_report.append('<tr><td>Vacuum (inHg):</td><td>%g</td></tr>' % (vacuum,))
260             else:
261                 html_report.append('<tr><td>Vacuum (inHg):</td><td>Unknown</td></tr>')
262             # need tolerances for flagging leak detect in report
263             if leak:
264                 html_report.append('<tr><td>Leak Detect (volts):</td><td>%f</td></tr>' % (leak,))
265             else:
266                 html_report.append('<tr><td>Leak Detect (Volts):</td><td>Unknown</td></tr>')
267             #
268             html_report.extend(
269                 [
270                 '<tr><td>Waypoint:</td><td>%s</td></tr>' % (waypoint_posn,),
271                 '<tr><td>Waypoint Range:</td><td>%s</td></tr>' % (waypoint_range,),
272                 '<tr><td>Waypoint Bearing:</td><td>%s</td></tr>' % (waypoint_bearing,),
273                 '<tr><td>Waypoint Age:</td><td>%s</td></tr>' % (waypoint_age,)
274                 ])
275             # close the report tbody table div and CDATA
276             html_report.extend(
277                 ['</tbody>',
278                  '</table>',
279                  '</div>',
280                  ' '
281                  ])
282             # make a readable CDATA string
283             html_report_str = '\n'.join(html_report)
284             # can create a KML object since extracted lat and lon
285             data.append({'glider': glider,
286                          'dt': dt,
287                          'datetime': dt_str,
288                          'name': dt_str_short,
289                          'hours_ago': hours_ago,
290                          'lat': lat,
291                          'lon': lon,
292                          'description': html_report_str,
293                          'wlat': wlat,
294                          'wlon': wlon,
295                          })
296         else:
297             # if no lat lon, can't create a KML object without lat and lon
298             # but want to append info to last known surface report
299             html_report = [
300                 '<h2></h2>',
301                 '<table">',
302                 '<thead>',
303                 '<tr><th>Glider: %s</th><th>ABORT Surfacing</th></tr>' % (subject_glider,),
304                 '</thead>',
305                 '<tbody>']       
306             html_report.extend(
307                 [
308                  '<tr><td>Surface Event:</td><td class="red_td">%s</td></tr>' % (subject_event,),
309                  '<tr><td>Surface Reason:</td><td class="red_td">%s</td></tr>' % (subject_reason,),
310                  ])
311             # close the report tbody table div and CDATA
312             html_report.extend(
313                 ['</tbody>',
314                 '</table>',
315                  ' '
316                  ])
317            
318             html_report_str = data[-1]['description']
319             html_report_str = html_report_str + '\n'.join(html_report)
320             print html_report_str
321             data[-1]['description'] = html_report_str
322     # close for-loop of msg in gms:
323     return data
324
325 def generate_track_kml(data, glider):
326     """ Use pykml to generate kml file for each glider from parsed mail data
327     A glider track consists of a line and placemarks for each surfacing
328    
329     Usage: kml_doc_str = surface_report_kml(data, glider)
330     """
331
332     from pykml.factory import KML_ElementMaker as KML
333     import lxml.etree
334
335     # ***** append LookAt after checking that lat, lon exist in data[-1]
336     d = data[-1]
337     # print '(%f, %f)' % (d['lon'], d['lat']) 
338    
339     # start a KML file skeleton with track styles
340     doc = KML.kml(
341         KML.Document(
342             KML.Name(glider + "_track"),
343             KML.LookAt(
344                 KML.longitude('%f' % d['lon']),
345                 KML.latitude('%f' % d['lat']),
346                 KML.heading('0'),
347                 KML.tilt('0'),
348                 KML.range('60000'),
349                 KML.altitudeMode('absolute')
350             ),
351             KML.Style(
352                 KML.IconStyle(
353                     KML.scale(0.7),
354                     KML.color("ff00ff00"),
355                     KML.Icon(KML.href("icons/square.png"))
356                     ),
357                 KML.LabelStyle(
358                     KML.color("ff00ff00"),
359                     KML.scale(0.8)),
360                 id="lastPosnIcon")
361             )
362         )
363     doc.Document.append(
364         KML.Style(
365             KML.IconStyle(
366                 KML.scale(0.8),
367                 KML.color("ff0000ff"),
368                 KML.Icon(KML.href("icons/cross-hairs.png"))),
369             KML.LabelStyle(
370                 KML.scale(0.8)),
371             id="lastWayPosnIcon")
372         )   
373     doc.Document.append(
374         KML.Style(
375             KML.IconStyle(
376                 KML.scale(0.7),
377                 KML.color("7d00ffff"),
378                 KML.Icon(KML.href("icons/donut.png"))),
379             KML.LabelStyle(
380                 KML.color("7d00ffff"),
381                 KML.scale(0.8)),
382             id="prevPosnIcon")
383         )   
384     doc.Document.append(
385         KML.Style(
386             KML.IconStyle(
387                 KML.scale(0.9),
388                 KML.color("ff00ff00"),
389                 KML.Icon(KML.href("icons/donut.png"))),
390             KML.LabelStyle(
391                 KML.color("ff00ff00"),
392                 KML.scale(0.7)),
393             id="histPosnIcon")
394         )   
395     doc.Document.append(
396         KML.Style(
397             KML.IconStyle(
398                 KML.scale(0.8),
399                 KML.color("7d0000ff"),
400                 KML.Icon(KML.href("icons/cross-hairs.png"))),
401             KML.LabelStyle(
402                 KML.scale(0.8)),
403             id="histWayPosnIcon")
404         )   
405     doc.Document.append(
406         KML.Style(
407             KML.LineStyle(
408                 KML.color("7dff0000"),
409                 KML.width(2)
410                 ), 
411             id="transBlueLine")
412         )
413     doc.Document.append(
414         KML.Style(
415             KML.LineStyle(
416                 KML.color("7d00ff00"),
417                 KML.width(2)
418                 ), 
419             id="transGreenLine")
420         )
421    
422     if glider == 'ramses':
423         linestyle = "#transBlueLine"
424     elif glider == 'pelagia':
425         linestyle = "#transGreenLine"
426     else:
427         linestyle = ""
428    
429     coord_str = ""
430     for d in data:
431         if d['lat'] and d['lon']:
432             coord_str = coord_str + "%f,%f,%f\n" % (d['lon'], d['lat'], 0.)
433    
434     track_line = KML.Placemark(
435         KML.name(glider),
436         KML.styleUrl(linestyle),
437         KML.LineString(
438             KML.altitudeMode("absolute"),
439             KML.coordinates(coord_str)
440             )
441         )
442     # glider placemarks (pms)
443     pms = []
444     for d in data[:-1]:
445         pms.append(
446             KML.Placemark(
447                 # short time stamp
448                 KML.name(d['name']),
449                 # surface report data
450                 KML.description(d['description']),
451                 KML.styleUrl("#prevPosnIcon"),
452                 KML.Point(
453                     KML.altitudeMode("absolute"),
454                     KML.coordinates("%f,%f,%f" % (d['lon'], d['lat'], 0.))
455                     )
456                 )
457             )
458     # glider history placemarks (hpms) using timestamp tag
459     histpms = []
460     for idx, d in enumerate(data[:-1]):
461         dt=d['dt']
462         # YYYY-MM-DDTHH:MM:SS + Z for kml <timestamp>
463         dt_str_begin = dt.isoformat()+'Z'
464         dt_str_end = (data[idx+1]['dt']).isoformat()+'Z'
465
466         histpms.append(
467             KML.Placemark(
468                 # ISO time stamp to be displayed with marker
469                 KML.name(dt_str_begin),
470                 # surface report data (not for history placemarks
471                 # KML.description(d['description']),
472                 KML.styleUrl("#histPosnIcon"),
473                 KML.Point(
474                     KML.altitudeMode("absolute"),
475                     KML.coordinates("%f,%f,%f" % (d['lon'], d['lat'], 0.))
476                     ),
477                 KML.TimeSpan(
478                     KML.begin(dt_str_begin),
479                     KML.end(dt_str_end)
480                     )
481                 )
482             )
483    
484    
485     d=data[-1]
486     last_pm = KML.Placemark(
487         # short time stamp
488         KML.name(d['name']),
489         # surface report data
490         KML.description(d['description']),
491         KML.styleUrl("#lastPosnIcon"),
492         KML.Point(
493             KML.altitudeMode("absolute"),
494             KML.coordinates("%f,%f,%f" % (d['lon'], d['lat'], 0.))
495             )
496         )
497     if d['wlon'] and d['wlat']:
498         wp_pm = KML.Placemark(
499             KML.name('Target Waypoint'),
500             # surface report data
501             KML.description(''),
502             KML.styleUrl("#lastWayPosnIcon"),
503             KML.Point(
504                KML.altitudeMode("absolute"),
505                KML.coordinates("%f,%f,%f" % (d['wlon'], d['wlat'], 0.))
506                )
507             )
508     track_folder = KML.Folder(
509         KML.name(d['glider']),
510         track_line,
511         )
512     for pm in pms:
513         track_folder.append(pm)
514     for hpm in histpms:
515         track_folder.append(hpm)   
516     track_folder.append(last_pm)
517     if d['wlon'] and d['wlat']:
518         track_folder.append(wp_pm)
519     doc.Document.append(
520         track_folder
521         )
522     track_kml = lxml.etree.tostring(doc, pretty_print=True)
523     return track_kml
524
Note: See TracBrowser for help on using the browser.