@@ -253,6 +253,64 @@ def colour_for_point_type(mlog, point, instance, options):
253
253
'violet' , 'purple' , 'grey' , 'black' ]
254
254
return map_colours [instance ]
255
255
256
+ def message_to_latlon (type , m , is_expression = False ):
257
+ '''convert a message to a lat/lon'''
258
+ if type in ['GPS' ,'GPS2' ] and not is_expression :
259
+ status = getattr (m , 'Status' , None )
260
+ nsats = getattr (m , 'NSats' , None )
261
+ # prevent mapping when no fix
262
+ if status is None :
263
+ status = getattr (m , 'FixType' , None )
264
+ if status is None :
265
+ return None
266
+ if nsats is None :
267
+ nsats = 0
268
+ if status < 2 and nsats < 5 :
269
+ return None
270
+ # flash log
271
+ lat = m .Lat
272
+ lng = getattr (m , 'Lng' , None )
273
+ if lng is None :
274
+ lng = getattr (m , 'Lon' , None )
275
+ if lng is None :
276
+ return None
277
+ return lat , lng
278
+
279
+ if hasattr (m ,'Lat' ) and hasattr (m ,'Lng' ):
280
+ return m .Lat , m .Lng
281
+ if hasattr (m ,'Lat' ) and hasattr (m ,'Lon' ):
282
+ return m .Lat , m .Lon
283
+ if hasattr (m ,'PN' ) and hasattr (m ,'PE' ):
284
+ pos = mavextra .ekf1_pos (m )
285
+ if pos is None :
286
+ return None
287
+ return pos [0 ], pos [1 ]
288
+ if hasattr (m ,'lat' ) and hasattr (m ,'lon' ):
289
+ return m .lat * 1.0e-7 , m .lon * 1.0e-7
290
+ if hasattr (m ,'lat' ) and hasattr (m ,'lng' ):
291
+ return m .lat * 1.0e-7 , m .lng * 1.0e-7
292
+ if hasattr (m ,'latitude' ) and hasattr (m ,'longitude' ):
293
+ return m .latitude * 1.0e-7 , m .longitude * 1.0e-7
294
+ return None
295
+
296
+ class PosExpression :
297
+ '''object repesenting a map expression, with the types that we need to look for in the log'''
298
+ def __init__ (self , expression ):
299
+ self .expression = expression
300
+ re_caps = re .compile ('[A-Z_][A-Z0-9_]+' )
301
+ caps = set (re .findall (re_caps , expression ))
302
+ self .recv_match_types = caps
303
+
304
+ def __repr__ (self ):
305
+ return "Expression(%s,%s)" % (self .expression , self .recv_match_types )
306
+
307
+ def pos_expressions (type_list ):
308
+ '''return a list of PosExpression objects for a type list'''
309
+ ret = []
310
+ for t in type_list :
311
+ ret .append (PosExpression (t ))
312
+ return ret
313
+
256
314
def mavflightview_mav (mlog , options = None , flightmode_selections = []):
257
315
'''create a map for a log file'''
258
316
wp = mavwp .MAVWPLoader ()
@@ -266,51 +324,45 @@ def mavflightview_mav(mlog, options=None, flightmode_selections=[]):
266
324
if s :
267
325
all_false = False
268
326
idx = 0
269
- path = [[]]
270
- instances = {}
327
+ path = []
271
328
ekf_counter = 0
272
329
nkf_counter = 0
330
+ expressions = []
331
+
273
332
types = ['MISSION_ITEM' , 'MISSION_ITEM_INT' , 'CMD' ]
274
333
if options .types is not None :
275
- types .extend (options .types .split (',' ))
334
+ if options .types .find (':' ):
335
+ type_list = options .types .split (':' )
336
+ else :
337
+ type_list = options .types .split (',' )
338
+ expressions .extend (pos_expressions (type_list ))
276
339
else :
277
- types .extend (['POS' ,'GLOBAL_POSITION_INT' ])
340
+ expressions .extend (pos_expressions ( ['POS' , 'GLOBAL_POSITION_INT' ]) )
278
341
if options .rawgps or options .dualgps :
279
- types .extend (['GPS' , 'GPS_RAW_INT' ])
342
+ expressions .extend (pos_expressions ( ['GPS' , 'GPS_RAW_INT' ]) )
280
343
if options .rawgps2 or options .dualgps :
281
- types .extend (['GPS2_RAW' ,'GPS2' ])
344
+ expressions .extend (pos_expressions ( ['GPS2_RAW' ,'GPS2' ]) )
282
345
if options .ekf :
283
- types .extend (['EKF1' , 'GPS' ])
346
+ expressions .extend (pos_expressions ( ['EKF1' , 'GPS' ]) )
284
347
if options .nkf :
285
- types .extend (['NKF1' , 'GPS' ])
348
+ expressions .extend (pos_expressions ( ['NKF1' , 'GPS' ]) )
286
349
if options .ahr2 :
287
- types .extend (['AHR2' , 'AHRS2' , 'GPS' ])
288
-
289
- # handle forms like GPS[0], mapping to GPS for recv_match_types
290
-
291
- # it may be possible to pass conditions in to recv_match_types,
292
- # but for now we filter to desired instances later.
293
- want_instances = {}
294
- recv_match_types = types [:]
295
- for i in range (len (recv_match_types )):
296
- match = re .match ('(?P<name>.*)\[(?P<instancenum>[^\]]+)\]' , recv_match_types [i ])
297
- if match is not None :
298
- name = match .group ("name" )
299
- number = match .group ("instancenum" )
300
- if name not in want_instances :
301
- want_instances [name ] = set ()
302
- want_instances [name ].add (number )
303
- recv_match_types [i ] = name
350
+ expressions .extend (pos_expressions (['AHR2' , 'AHRS2' , 'GPS' ]))
351
+
352
+ # find the union of message types we need from the log for all expressions
353
+ recv_match_types = set ()
354
+ for e in expressions :
355
+ recv_match_types .update (set (e .recv_match_types ))
304
356
305
357
colour_source = getattr (options , "colour_source" )
306
- re_caps = re .compile ('[A-Z_][A-Z0-9_]+' )
307
358
308
359
if colour_source is not None :
309
360
# stolen from mavgraph.py
361
+ re_caps = re .compile ('[A-Z_][A-Z0-9_]+' )
310
362
caps = set (re .findall (re_caps , colour_source ))
311
- recv_match_types .extend (caps )
363
+ recv_match_types .update (caps )
312
364
313
- print ("Looking for types %s" % str (recv_match_types ))
365
+ print ("Looking for types %s" % str (list ( recv_match_types ) ))
314
366
315
367
last_timestamps = {}
316
368
used_flightmodes = {}
@@ -377,126 +429,70 @@ def mavflightview_mav(mlog, options=None, flightmode_selections=[]):
377
429
378
430
if not mlog .check_condition (options .condition ):
379
431
continue
380
- if options .mode is not None and mlog .flightmode .lower () != options .mode .lower ():
381
- continue
382
432
383
- if not type in types and type not in want_instances :
384
- # may only be present for colour-source expressions to work
433
+ if options .mode is not None and mlog .flightmode .lower () != options .mode .lower ():
385
434
continue
386
435
387
- type_with_instance = type
388
- try :
389
- # remember that "m" here might be a mavlink message.
390
- instance_field = m .fmt .instance_field
391
- m_instance_field_value = eval (f"m.{ instance_field } " )
392
- if (type in want_instances and
393
- str (m_instance_field_value ) not in want_instances [type ]
394
- ):
395
- continue
396
-
397
- type_with_instance = '%s[%u]' % (type , m_instance_field_value )
398
- except Exception :
399
- pass
400
-
401
436
if not all_false and len (flightmode_selections ) > 0 and idx < len (options ._flightmodes ) and m ._timestamp >= options ._flightmodes [idx ][2 ]:
402
437
idx += 1
403
438
elif (idx < len (flightmode_selections ) and flightmode_selections [idx ]) or all_false or len (flightmode_selections ) == 0 :
404
439
used_flightmodes [mlog .flightmode ] = 1
405
- (lat , lng ) = (None ,None )
406
- if type in ['GPS' ,'GPS2' ]:
407
- status = getattr (m , 'Status' , None )
408
- nsats = getattr (m , 'NSats' , None )
409
- if status is None :
410
- status = getattr (m , 'FixType' , None )
411
- if status is None :
412
- print ("Can't find status on GPS message" )
413
- print (m )
414
- break
415
- if nsats is None :
416
- nsats = 0
417
- if status < 2 and nsats < 5 :
440
+ for instance in range (len (expressions )):
441
+ expression = expressions [instance ]
442
+ if not type in expression .recv_match_types :
418
443
continue
419
- # flash log
420
- lat = m .Lat
421
- lng = getattr (m , 'Lng' , None )
422
- if lng is None :
423
- lng = getattr (m , 'Lon' , None )
424
- if lng is None :
425
- print ("Can't find longitude on GPS message" )
426
- print (m )
427
- break
428
- elif type in ['EKF1' , 'ANU1' ]:
429
- pos = mavextra .ekf1_pos (m )
430
- if pos is None :
444
+
445
+ # evaluate the expression
446
+ is_expression = (type != expression .expression )
447
+ if not is_expression :
448
+ # this is a simple type as an expression
449
+ v = m
450
+ else :
451
+ # we need to evaluate the expression to produce an object
452
+ try :
453
+ v = mavutil .evaluate_expression (expression .expression , mlog .messages )
454
+ except Exception :
455
+ continue
456
+ if v is None :
431
457
continue
432
- ekf_counter += 1
433
- if ekf_counter % options . ekf_sample != 0 :
458
+ latlng = message_to_latlon ( type , v , is_expression )
459
+ if latlng is None :
434
460
continue
435
- (lat , lng , alt ) = pos
436
- elif type in ['NKF1' ,'XKF1' ]:
437
- pos = mavextra .ekf1_pos (m )
438
- if pos is None :
461
+ lat ,lng = latlng
462
+
463
+ while len (path ) <= instance :
464
+ path .append ([])
465
+
466
+ # only plot thing we have a valid-looking location for:
467
+ if abs (lat )<= 0.01 and abs (lng )<= 0.01 :
439
468
continue
440
- nkf_counter += 1
441
- if nkf_counter % options .nkf_sample != 0 :
469
+
470
+ colour = colour_for_point (mlog , (lat , lng ), instance , options )
471
+ if colour is None :
442
472
continue
443
- (lat , lng , alt ) = pos
444
- elif type in ['ANU5' ]:
445
- (lat , lng ) = (m .Alat * 1.0e-7 , m .Alng * 1.0e-7 )
446
- elif type in ['AHR2' , 'POS' , 'CHEK' ]:
447
- (lat , lng ) = (m .Lat , m .Lng )
448
- elif type == 'AHRS2' :
449
- (lat , lng ) = (m .lat * 1.0e-7 , m .lng * 1.0e-7 )
450
- elif type == 'ORGN' :
451
- (lat , lng ) = (m .Lat , m .Lng )
452
- elif type == 'SIM' :
453
- (lat , lng ) = (m .Lat , m .Lng )
454
- elif type == 'GUID' :
455
- if (m .Type == 0 ):
456
- (lat , lng ) = (m .pX * 1.0e-7 , m .pY * 1.0e-7 )
457
- else :
458
- if hasattr (m ,'Lat' ):
459
- lat = m .Lat
460
- if hasattr (m ,'Lon' ):
461
- lng = m .Lon
462
- if hasattr (m ,'Lng' ):
463
- lng = m .Lng
464
- if hasattr (m ,'lat' ):
465
- lat = m .lat * 1.0e-7
466
- if hasattr (m ,'lon' ):
467
- lng = m .lon * 1.0e-7
468
- if hasattr (m ,'lng' ):
469
- lng = m .lng * 1.0e-7
470
- if hasattr (m ,'latitude' ):
471
- lat = m .latitude * 1.0e-7
472
- if hasattr (m ,'longitude' ):
473
- lng = m .longitude * 1.0e-7
474
-
475
- if lat is None or lng is None :
476
- continue
477
-
478
- # automatically add new types to instances
479
- if type_with_instance not in instances :
480
- instances [type_with_instance ] = len (instances )
481
- while len (instances ) >= len (path ):
482
- path .append ([])
483
- instance = instances [type_with_instance ]
484
473
485
- # only plot thing we have a valid-looking location for:
486
- if abs (lat )<= 0.01 and abs (lng )<= 0.01 :
487
- continue
474
+ tdays = grapher .timestamp_to_days (m ._timestamp )
475
+ point = (lat , lng , colour , tdays )
476
+
477
+ if options .rate == 0 or not expression .expression in last_timestamps or m ._timestamp - last_timestamps [expression .expression ] > 1.0 / options .rate :
478
+ last_timestamps [expression .expression ] = m ._timestamp
479
+ path [instance ].append (point )
488
480
489
- colour = colour_for_point (mlog , (lat , lng ), instance , options )
490
- if colour is None :
491
- continue
481
+ # remove any empty paths and construct instances array
482
+ paths2 = []
483
+ instances = {}
484
+ for instance in range (len (expressions )):
485
+ e = expressions [instance ]
486
+ if instance >= len (path ):
487
+ break
488
+ if len (path [instance ]) == 0 :
489
+ continue
490
+ paths2 .append (path [instance ])
491
+ instances [e .expression ] = instance
492
492
493
- tdays = grapher .timestamp_to_days (m ._timestamp )
494
- point = (lat , lng , colour , tdays )
493
+ path = paths2
495
494
496
- if options .rate == 0 or not type_with_instance in last_timestamps or m ._timestamp - last_timestamps [type_with_instance ] > 1.0 / options .rate :
497
- last_timestamps [type_with_instance ] = m ._timestamp
498
- path [instance ].append (point )
499
- if len (path [0 ]) == 0 :
495
+ if len (path ) == 0 :
500
496
print ("No points to plot" )
501
497
return None
502
498
@@ -691,8 +687,6 @@ def __init__(self):
691
687
parser .add_option ("--debug" , action = 'store_true' , default = False , help = "show debug info" )
692
688
parser .add_option ("--multi" , action = 'store_true' , default = False , help = "show multiple flights on one map" )
693
689
parser .add_option ("--types" , default = None , help = "types of position messages to show" )
694
- parser .add_option ("--ekf-sample" , type = 'int' , default = 1 , help = "sub-sampling of EKF messages" )
695
- parser .add_option ("--nkf-sample" , type = 'int' , default = 1 , help = "sub-sampling of NKF messages" )
696
690
parser .add_option ("--rate" , type = 'int' , default = 0 , help = "maximum message rate to display (0 means all points)" )
697
691
parser .add_option ("--colour-source" , type = "str" , default = "flightmode" , help = "expression with range 0f..255f used for point colour" )
698
692
parser .add_option ("--no-flightmode-legend" , action = "store_false" , default = True , dest = "show_flightmode_legend" , help = "hide legend for colour used for flight modes" )
0 commit comments