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 |
|
---|