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

root/pyglider/trunk/pyglider/pyglider.py

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

Initial commit

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                 '<div id="main">',
301                 '<h2></h2>',
302                 '<table">',
303                 '<thead>',
304                 '<tr><th>Glider: %s</th><th>%s</th></tr>' % (glider, dt_str,),
305                 '</thead>',
306                 '<tbody>']       
307             html_report.extend(
308                 ['<tr><td><b>Glider Location</b></td><td>--</td></tr>',
309                  '<tr><td>GPS Fix:</td><td>%s</td></tr>' % (gps_posn_str,),
310                  '<tr><td>Time of Fix:</td><td>%s</td></tr>' % (gps_dt_str,)
311                  ])
312             # close the report tbody table div and CDATA
313             html_report.extend(
314                 ['</tbody>',
315                 '</table>',
316                  '</div>',
317                  ' '
318                  ])
319            
320     # close for-loop of msg in gms:
321     return data
322
323 def generate_track_kml(data, glider):
324     """ Use pykml to generate kml file for each glider from parsed mail data
325     A glider track consists of a line and placemarks for each surfacing
326    
327     Usage: kml_doc_str = surface_report_kml(data, glider)
328     """
329
330     from pykml.factory import KML_ElementMaker as KML
331     import lxml.etree
332
333     # ***** append LookAt after checking that lat, lon exist in data[-1]
334     d = data[-1]
335     # print '(%f, %f)' % (d['lon'], d['lat']) 
336    
337     # start a KML file skeleton with track styles
338     doc = KML.kml(
339         KML.Document(
340             KML.Name(glider + "_track"),
341             KML.LookAt(
342                 KML.longitude('%f' % d['lon']),
343                 KML.latitude('%f' % d['lat']),
344                 KML.heading('0'),
345                 KML.tilt('0'),
346                 KML.range('60000'),
347                 KML.altitudeMode('absolute')
348             ),
349             KML.Style(
350                 KML.IconStyle(
351                     KML.scale(0.7),
352                     KML.color("ff00ff00"),
353                     KML.Icon(KML.href("icons/square.png"))
354                     ),
355                 KML.LabelStyle(
356                     KML.color("ff00ff00"),
357                     KML.scale(0.8)),
358                 id="lastPosnIcon")
359             )
360         )
361     doc.Document.append(
362         KML.Style(
363             KML.IconStyle(
364                 KML.scale(0.8),
365                 KML.color("ff0000ff"),
366                 KML.Icon(KML.href("icons/cross-hairs.png"))),
367             KML.LabelStyle(
368                 KML.scale(0.8)),
369             id="lastWayPosnIcon")
370         )   
371     doc.Document.append(
372         KML.Style(
373             KML.IconStyle(
374                 KML.scale(0.7),
375                 KML.color("7d00ffff"),
376                 KML.Icon(KML.href("icons/donut.png"))),
377             KML.LabelStyle(
378                 KML.color("7d00ffff"),
379                 KML.scale(0.8)),
380             id="prevPosnIcon")
381         )   
382     doc.Document.append(
383         KML.Style(
384             KML.IconStyle(
385                 KML.scale(0.9),
386                 KML.color("ff00ff00"),
387                 KML.Icon(KML.href("icons/donut.png"))),
388             KML.LabelStyle(
389                 KML.color("ff00ff00"),
390                 KML.scale(0.7)),
391             id="histPosnIcon")
392         )   
393     doc.Document.append(
394         KML.Style(
395             KML.IconStyle(
396                 KML.scale(0.8),
397                 KML.color("7d0000ff"),
398                 KML.Icon(KML.href("icons/cross-hairs.png"))),
399             KML.LabelStyle(
400                 KML.scale(0.8)),
401             id="histWayPosnIcon")
402         )   
403     doc.Document.append(
404         KML.Style(
405             KML.LineStyle(
406                 KML.color("7dff0000"),
407                 KML.width(2)
408                 ), 
409             id="transBlueLine")
410         )
411     doc.Document.append(
412         KML.Style(
413             KML.LineStyle(
414                 KML.color("7d00ff00"),
415                 KML.width(2)
416                 ), 
417             id="transGreenLine")
418         )
419    
420     if glider == 'ramses':
421         linestyle = "#transBlueLine"
422     elif glider == 'pelagia':
423         linestyle = "#transGreenLine"
424     else:
425         linestyle = ""
426    
427     coord_str = ""
428     for d in data:
429         if d['lat'] and d['lon']:
430             coord_str = coord_str + "%f,%f,%f\n" % (d['lon'], d['lat'], 0.)
431    
432     track_line = KML.Placemark(
433         KML.name(glider),
434         KML.styleUrl(linestyle),
435         KML.LineString(
436             KML.altitudeMode("absolute"),
437             KML.coordinates(coord_str)
438             )
439         )
440     # glider placemarks (pms)
441     pms = []
442     for d in data[:-1]:
443         pms.append(
444             KML.Placemark(
445                 # short time stamp
446                 KML.name(d['name']),
447                 # surface report data
448                 KML.description(d['description']),
449                 KML.styleUrl("#prevPosnIcon"),
450                 KML.Point(
451                     KML.altitudeMode("absolute"),
452                     KML.coordinates("%f,%f,%f" % (d['lon'], d['lat'], 0.))
453                     )
454                 )
455             )
456     # glider history placemarks (hpms) using timestamp tag
457     histpms = []
458     for idx, d in enumerate(data[:-1]):
459         dt=d['dt']
460         # YYYY-MM-DDTHH:MM:SS + Z for kml <timestamp>
461         dt_str_begin = dt.isoformat()+'Z'
462         dt_str_end = (data[idx+1]['dt']).isoformat()+'Z'
463
464         histpms.append(
465             KML.Placemark(
466                 # ISO time stamp to be displayed with marker
467                 KML.name(dt_str_begin),
468                 # surface report data (not for history placemarks
469                 # KML.description(d['description']),
470                 KML.styleUrl("#histPosnIcon"),
471                 KML.Point(
472                     KML.altitudeMode("absolute"),
473                     KML.coordinates("%f,%f,%f" % (d['lon'], d['lat'], 0.))
474                     ),
475                 KML.TimeSpan(
476                     KML.begin(dt_str_begin),
477                     KML.end(dt_str_end)
478                     )
479                 )
480             )
481    
482    
483     d=data[-1]
484     last_pm = KML.Placemark(
485         # short time stamp
486         KML.name(d['name']),
487         # surface report data
488         KML.description(d['description']),
489         KML.styleUrl("#lastPosnIcon"),
490         KML.Point(
491             KML.altitudeMode("absolute"),
492             KML.coordinates("%f,%f,%f" % (d['lon'], d['lat'], 0.))
493             )
494         )
495     if d['wlon'] and d['wlat']:
496         wp_pm = KML.Placemark(
497             KML.name('Target Waypoint'),
498             # surface report data
499             KML.description(''),
500             KML.styleUrl("#lastWayPosnIcon"),
501             KML.Point(
502                KML.altitudeMode("absolute"),
503                KML.coordinates("%f,%f,%f" % (d['wlon'], d['wlat'], 0.))
504                )
505             )
506     track_folder = KML.Folder(
507         KML.name(d['glider']),
508         track_line,
509         )
510     for pm in pms:
511         track_folder.append(pm)
512     for hpm in histpms:
513         track_folder.append(hpm)   
514     track_folder.append(last_pm)
515     if d['wlon'] and d['wlat']:
516         track_folder.append(wp_pm)
517     doc.Document.append(
518         track_folder
519         )
520     track_kml = lxml.etree.tostring(doc, pretty_print=True)
521     return track_kml
522
Note: See TracBrowser for help on using the browser.