Skip to content

Commit 6ff1fd5

Browse files
committed
Add map annotation feature.
1 parent 931ffd4 commit 6ff1fd5

File tree

7 files changed

+281
-43
lines changed

7 files changed

+281
-43
lines changed

LogViewer/LogViewer/Controls/Console.xaml.cs

+11-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,17 @@ public void Write(string text)
102102

103103
if ((string)run.Tag == "user")
104104
{
105-
run = run.PreviousInline as Run;
105+
var previousrun = run.PreviousInline as Run;
106+
if (previousrun == null)
107+
{
108+
var newrun = new Run() { Foreground = ConsoleTextBrush };
109+
last.Inlines.InsertBefore(run, newrun);
110+
run = newrun;
111+
}
112+
else
113+
{
114+
run = previousrun;
115+
}
106116
}
107117

108118
// todo: process binary console commands embedded in the text...

LogViewer/LogViewer/LogViewer.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
<SuiteName>PX4 Log Viewer</SuiteName>
3434
<CreateWebPageOnPublish>true</CreateWebPageOnPublish>
3535
<WebPage>publish.htm</WebPage>
36-
<ApplicationRevision>44</ApplicationRevision>
36+
<ApplicationRevision>45</ApplicationRevision>
3737
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
3838
<UseApplicationTrust>false</UseApplicationTrust>
3939
<PublishWizardCompleted>true</PublishWizardCompleted>

LogViewer/LogViewer/MainWindow.xaml

+10-4
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102
<Button x:Name="RecordButton" Style="{StaticResource AppBarButtonStyle}" Grid.Column="8" Click="OnRecord">&#xE102;</Button>
103103

104104
<Button x:Name="SettingsButton" Style="{StaticResource AppBarButtonStyle}" Grid.Column="9" Click="OnSettings">&#xE115;</Button>
105-
105+
106106
<local:ConnectorControl x:Name="ConnectorControl" Grid.Column="11" Padding="0,15,0,0" Width="48" Height="48" ToolTip="Connection" MouseLeftButtonDown="OnConnectorClick" />
107107
</Grid>
108108

@@ -122,7 +122,13 @@
122122
ItemContainerStyle="{StaticResource ContainerListItemStyle}"
123123
Style="{StaticResource NavigationList}"
124124
local:PassthroughMouseWheelBehavior.PassthroughMouseWheel="True"
125+
PreviewMouseRightButtonDown="OnRightClickCategoryList"
125126
ScrollViewer.CanContentScroll="False">
127+
<ListView.ContextMenu>
128+
<ContextMenu>
129+
<MenuItem Header="Annotate on map" Click="OnAnnotateItem"/>
130+
</ContextMenu>
131+
</ListView.ContextMenu>
126132
</ListView>
127133
</local:ScrollViewerLite>
128134

@@ -144,11 +150,11 @@
144150
<Grid x:Name="CameraPanel" Background="Black" Grid.Row="2" Grid.Column="1" Visibility="Collapsed">
145151
<Image x:Name="ImageViewer" />
146152
</Grid>
147-
153+
148154
<local:ChartStack x:Name="ChartStack" Focusable="True" Grid.Row="2" Grid.Column="1" Style="{StaticResource AppWorkspaceStack}" Background="Transparent">
149155
</local:ChartStack>
150156

151-
157+
152158
<local:Console Grid.Row="2" Grid.Column="1" x:Name="SystemConsole"/>
153159

154160
<ProgressBar x:Name="MyProgress" Height="8" Grid.ColumnSpan="2" Grid.Row="3"/>
@@ -164,5 +170,5 @@
164170
<TextBlock x:Name="StatusText" Text="" Margin="5" />
165171
</Border>
166172
</Grid>
167-
173+
168174
</Window>

LogViewer/LogViewer/MainWindow.xaml.cs

+209-21
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public partial class MainWindow : Window
3939
MapPolyline currentFlight;
4040
MavlinkLog currentFlightLog;
4141
long lastAttitudeMessage;
42+
List<LogEntry> mappedLogEntries;
43+
MapLayer annotationLayer;
4244

4345
public MainWindow()
4446
{
@@ -829,6 +831,7 @@ void ShowMap()
829831
line.StrokeThickness = 4;
830832
line.Stroke = new SolidColorBrush(GetRandomColor());
831833
LocationCollection points = new LocationCollection();
834+
mappedLogEntries = new List<Model.LogEntry>();
832835

833836
//Debug.WriteLine("time,\t\tlat,\t\tlong,\t\t\tnsat,\talt,\thdop,\tfix");
834837
foreach (var row in log.GetRows("GPS", flight.StartTime, flight.Duration))
@@ -844,6 +847,7 @@ void ShowMap()
844847
alt = 0;
845848
}
846849
mapData.Add(gps);
850+
mappedLogEntries.Add(row);
847851
var pos = new Location() { Altitude = alt, Latitude = gps.Lat, Longitude = gps.Lon };
848852
points.Add(pos);
849853
ulong time = (ulong)gps.GPSTime;
@@ -953,8 +957,6 @@ private void OnChildListItemSelected(object sender, SelectionChangedEventArgs e)
953957

954958
IEnumerable<DataValue> GetSelectedDataValues(LogItemSchema schema)
955959
{
956-
List<DataValue> combined = new List<DataValue>();
957-
958960
List<Flight> selected = GetSelectedFlights();
959961
if (selected.Count == 0)
960962
{
@@ -970,13 +972,14 @@ IEnumerable<DataValue> GetSelectedDataValues(LogItemSchema schema)
970972
{
971973
if (flight.Log == null || flight.Log == log)
972974
{
973-
combined.AddRange(log.GetDataValues(schema, flight.StartTime, flight.Duration));
975+
foreach (var dv in log.GetDataValues(schema, flight.StartTime, flight.Duration))
976+
{
977+
yield return dv;
978+
}
974979
}
975980
}
976981
}
977-
}
978-
979-
return combined;
982+
}
980983
}
981984

982985
Thickness defaultChartMargin = new Thickness(0, 10, 0, 10);
@@ -1100,22 +1103,184 @@ private void GraphItem(LogItemSchema schema)
11001103
else
11011104
{
11021105
StringBuilder sb = new StringBuilder();
1106+
string previous = null;
1107+
List<DataValue> unique = new List<Model.DataValue>();
11031108
foreach (var value in GetSelectedDataValues(schema))
11041109
{
11051110
if (!string.IsNullOrEmpty(value.Label))
11061111
{
1107-
sb.AppendLine(value.Label);
1112+
if (previous != value.Label)
1113+
{
1114+
unique.Add(value);
1115+
sb.Append(((ulong)value.X).ToString());
1116+
sb.Append(": ");
1117+
sb.AppendLine(value.Label);
1118+
previous = value.Label;
1119+
}
1120+
}
1121+
}
1122+
SystemConsole.Write(sb.ToString());
1123+
ConsoleButton.IsChecked = true;
1124+
SystemConsole.Show();
1125+
}
1126+
}
1127+
1128+
private void AnnotateMap(LogItemSchema schema)
1129+
{
1130+
List<DataValue> unique = new List<Model.DataValue>();
1131+
if (schema.IsNumeric)
1132+
{
1133+
var data = GetSelectedDataValues(schema);
1134+
ShowStatus(string.Format("Found {0} data values", data.Count()));
1135+
if (data.Count() > 0)
1136+
{
1137+
double previous = 0;
1138+
{
1139+
// uniquify it.
1140+
foreach (var value in data)
1141+
{
1142+
if (value.Y != previous)
1143+
{
1144+
unique.Add(value);
1145+
previous = value.Y;
1146+
}
1147+
}
1148+
}
1149+
}
1150+
}
1151+
else
1152+
{
1153+
StringBuilder sb = new StringBuilder();
1154+
string previous = null;
1155+
foreach (var value in GetSelectedDataValues(schema))
1156+
{
1157+
if (!string.IsNullOrEmpty(value.Label))
1158+
{
1159+
if (previous != value.Label)
1160+
{
1161+
unique.Add(value);
1162+
sb.Append(value.X.ToString());
1163+
sb.Append(": ");
1164+
sb.AppendLine(value.Label);
1165+
previous = value.Label;
1166+
}
1167+
}
1168+
}
1169+
}
1170+
1171+
// if there are too many values, then limit it to an even spread of 100 items.
1172+
if (unique.Count > 100)
1173+
{
1174+
var summary = new List<Model.DataValue>();
1175+
double skip = (unique.Count / 100);
1176+
for (int i = 0, n = unique.Count; i < n; i++)
1177+
{
1178+
var value = unique[i];
1179+
if (i >= summary.Count * unique.Count / 100)
1180+
{
1181+
summary.Add(value);
1182+
}
1183+
}
1184+
unique = summary;
1185+
}
1186+
AnnotateMap(unique);
1187+
}
1188+
1189+
private void AnnotateMap(List<DataValue> unique)
1190+
{
1191+
if (this.mappedLogEntries == null || this.mappedLogEntries.Count == 0)
1192+
{
1193+
ShowMap();
1194+
}
1195+
1196+
if (this.mappedLogEntries == null || this.mappedLogEntries.Count == 0)
1197+
{
1198+
MessageBox.Show("Sorry, could not find GPS map info, so cannot annotate data on the map",
1199+
"GPS info is missing", MessageBoxButton.OK, MessageBoxImage.Exclamation);
1200+
return;
1201+
}
1202+
1203+
if (annotationLayer != null)
1204+
{
1205+
myMap.Children.Remove(annotationLayer);
1206+
}
1207+
annotationLayer = new MapLayer();
1208+
1209+
SolidColorBrush annotationBrush = new SolidColorBrush(Color.FromArgb(0x80, 0xff, 0xff, 0xB0));
1210+
1211+
foreach (var dv in unique)
1212+
{
1213+
LogEntry closest = null;
1214+
LogField field = dv.UserData as LogField;
1215+
if (field != null)
1216+
{
1217+
// csv log
1218+
LogEntry e = field.Parent;
1219+
closest = FindNearestMappedItem(e.Timestamp);
1220+
}
1221+
else
1222+
{
1223+
// px4 log?
1224+
Message msg = dv.UserData as Message;
1225+
if (msg != null)
1226+
{
1227+
closest = FindNearestMappedItem(msg.GetTimestamp());
11081228
}
11091229
else
11101230
{
1111-
sb.AppendLine(value.Y.ToString());
1231+
// mavlink
1232+
MavlinkLog.Message mavmsg = dv.UserData as MavlinkLog.Message;
1233+
if (mavmsg != null)
1234+
{
1235+
closest = FindNearestMappedItem(mavmsg.Timestamp.Ticks / 10);
1236+
}
11121237
}
11131238
}
11141239

1115-
SystemConsole.Write(sb.ToString());
1116-
ConsoleButton.IsChecked = true;
1117-
SystemConsole.Show();
1240+
if (closest != null)
1241+
{
1242+
LogEntryGPS gps = new LogEntryGPS(closest);
1243+
// map doesn't like negative altitudes.
1244+
double alt = gps.Alt;
1245+
if (alt < 0)
1246+
{
1247+
alt = 0;
1248+
}
1249+
var pos = new Location() { Altitude = alt, Latitude = gps.Lat, Longitude = gps.Lon };
1250+
string label = dv.Label;
1251+
if (string.IsNullOrEmpty(label))
1252+
{
1253+
label = dv.Y.ToString();
1254+
}
1255+
annotationLayer.AddChild(new TextBlock(new Run(label) { Background = annotationBrush }), pos, PositionOrigin.BottomLeft);
1256+
}
1257+
11181258
}
1259+
myMap.Children.Add(annotationLayer);
1260+
1261+
SystemConsole.Hide();
1262+
ChartStack.Visibility = Visibility.Collapsed;
1263+
myMap.Visibility = Visibility.Visible;
1264+
myMap.UpdateLayout();
1265+
}
1266+
1267+
private LogEntry FindNearestMappedItem(double t)
1268+
{
1269+
LogEntry closest = null;
1270+
double bestDiff = 0;
1271+
// find nearest mapped location (nearest in time).
1272+
foreach (var mapped in this.mappedLogEntries)
1273+
{
1274+
var time = mapped.Timestamp;
1275+
var diff = Math.Abs((double)time - t);
1276+
1277+
if (closest == null || diff < bestDiff)
1278+
{
1279+
closest = mapped;
1280+
bestDiff = diff;
1281+
}
1282+
}
1283+
return closest;
11191284
}
11201285

11211286
private void OnNewChartGenerated(object sender, List<DataValue> e)
@@ -1178,6 +1343,10 @@ private void LayoutCharts()
11781343
double height = ChartStack.ActualHeight;
11791344
double count = ChartStack.ChartCount;
11801345
height -= (count * (defaultChartMargin.Top + defaultChartMargin.Bottom)); // remove margins
1346+
if (height < 0)
1347+
{
1348+
height = 0;
1349+
}
11811350
double chartHeight = Math.Min(MaxChartHeight, height / count);
11821351
bool found = false;
11831352
foreach (FrameworkElement c in ChartStack.Charts)
@@ -1250,6 +1419,7 @@ private void UnselectCategory(LogItemSchema item)
12501419
}
12511420
}
12521421

1422+
12531423
private void OnClear(object sender, RoutedEventArgs e)
12541424
{
12551425
ChartStack.ClearCharts();
@@ -1294,15 +1464,7 @@ private void OnFlightSelected(object sender, SelectionChangedEventArgs e)
12941464
}
12951465
});
12961466
}
1297-
1298-
//private void OnMapPointerMoved(object sender, PointerRoutedEventArgs e)
1299-
//{
1300-
// Point mapPos = e.GetCurrentPoint(myMap).Position;
1301-
// Geopoint location;
1302-
// myMap.GetLocationFromOffset(mapPos, out location);
1303-
// StatusText.Text = location.Position.Latitude + ", " + location.Position.Longitude;
1304-
//}
1305-
1467+
13061468
private void OnFlightViewKeyDown(object sender, KeyEventArgs e)
13071469
{
13081470
if (e.Key == Key.Delete)
@@ -1487,6 +1649,8 @@ struct IncomingImage
14871649

14881650
IncomingImage incoming_image = new IncomingImage();
14891651

1652+
public LogItemSchema rightClickedItem { get; private set; }
1653+
14901654
private void OnShowConsole(object sender, RoutedEventArgs e)
14911655
{
14921656
SystemConsole.Show();
@@ -1583,7 +1747,6 @@ private void DrawVectors(double[,] xmag, double[,] ymag)
15831747
// find guassian lines in the map and draw them so it looks like this:
15841748
// https://www.ngdc.noaa.gov/geomag/WMM/data/WMM2015/WMM2015_D_MERC.pdf
15851749

1586-
15871750
for (int i = 0; i < 180; i++)
15881751
{
15891752
for (int j = 0; j < 360; j++)
@@ -1619,6 +1782,31 @@ private void OnPaste(object sender, ExecutedRoutedEventArgs e)
16191782
CameraPanel.Visibility = Visibility.Visible;
16201783
}
16211784
}
1785+
1786+
private void OnAnnotateItem(object sender, RoutedEventArgs e)
1787+
{
1788+
LogItemSchema item = this.rightClickedItem;
1789+
if (item != null)
1790+
{
1791+
AnnotateMap(item);
1792+
}
1793+
}
1794+
1795+
private void OnRightClickCategoryList(object sender, MouseButtonEventArgs e)
1796+
{
1797+
this.rightClickedItem = null;
1798+
Point pos = e.GetPosition(CategoryList);
1799+
DependencyObject dep = (DependencyObject)e.OriginalSource;
1800+
while ((dep != null) && !(dep is ListViewItem))
1801+
{
1802+
dep = VisualTreeHelper.GetParent(dep);
1803+
}
1804+
if (dep == null)
1805+
return;
1806+
ListViewItem listitem = (ListViewItem)dep;
1807+
LogItemSchema item = listitem.DataContext as LogItemSchema;
1808+
this.rightClickedItem = item;
1809+
}
16221810
}
16231811
}
16241812

0 commit comments

Comments
 (0)