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

root/pyglider/trunk/pyglider/pyglider.py

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

creates goto kml from GCCS output

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