mirror of
https://github.com/dotMorten/NmeaParser.git
synced 2026-02-03 22:34:20 +01:00
Moved map into its own control + added 3D view
This commit is contained in:
parent
77a92b3938
commit
e02113072f
|
|
@ -44,72 +44,10 @@
|
|||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Map">
|
||||
<Grid>
|
||||
<!--Map-->
|
||||
<esri:MapView x:Name="mapView" Grid.Row="1">
|
||||
<local:RestoreAutoPanMode.RestoreAutoPanSettings>
|
||||
<local:RestoreAutoPanMode DelayInSeconds="2.5" PanMode="Navigation" IsEnabled="True" RestoreScale="5000" />
|
||||
</local:RestoreAutoPanMode.RestoreAutoPanSettings>
|
||||
</esri:MapView>
|
||||
|
||||
<!--North arrow-->
|
||||
<Grid Width="50" Height="50" HorizontalAlignment="Right" VerticalAlignment="Bottom"
|
||||
Margin="20" RenderTransformOrigin=".5,.5">
|
||||
<Grid.Resources>
|
||||
<local:ReverseConverter x:Key="conv" />
|
||||
</Grid.Resources>
|
||||
<Grid.Effect>
|
||||
<DropShadowEffect Direction="0" ShadowDepth="0"
|
||||
BlurRadius="10" Opacity=".75" />
|
||||
</Grid.Effect>
|
||||
<Grid.RenderTransform>
|
||||
<RotateTransform Angle="{Binding ElementName=mapView, Path=MapRotation, Converter={StaticResource conv}}" />
|
||||
</Grid.RenderTransform>
|
||||
<TextBlock Text="Ù" FontFamily="Wingdings" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="40" Foreground="White" />
|
||||
<TextBlock Text="N" HorizontalAlignment="Center" VerticalAlignment="Top"
|
||||
FontSize="12" Margin="0,-5,0,0" RenderTransformOrigin=".5,.5"
|
||||
Foreground="White" >
|
||||
<TextBlock.RenderTransform>
|
||||
<RotateTransform Angle="{Binding ElementName=mapView, Path=MapRotation}" />
|
||||
</TextBlock.RenderTransform>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
|
||||
<Grid HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
Margin="10" Background="White" Width="150"
|
||||
DataContext="{Binding ElementName=mapView, Path=LocationDisplay.Location}">
|
||||
<Grid.Effect>
|
||||
<DropShadowEffect Direction="0" ShadowDepth="0"
|
||||
BlurRadius="10" Opacity=".75" />
|
||||
</Grid.Effect>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Border Background="CornflowerBlue" Grid.ColumnSpan="2" Padding="10">
|
||||
<TextBlock Text="Details" FontSize="20" FontWeight="Bold" Foreground="White" />
|
||||
</Border>
|
||||
<StackPanel Margin="10" Grid.Row="1">
|
||||
<TextBlock Text="Speed:" FontWeight="Bold" />
|
||||
<TextBlock Text="Course:" FontWeight="Bold" />
|
||||
<TextBlock Text="Altitude:" FontWeight="Bold" />
|
||||
<TextBlock Text="Accuracy:" FontWeight="Bold" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,10,10,10" Grid.Column="1" Grid.Row="1">
|
||||
<TextBlock Text="{Binding Velocity, StringFormat='{}{0} km/h'}" />
|
||||
<TextBlock Text="{Binding Course, StringFormat='{}{0}°'}" />
|
||||
<TextBlock Text="{Binding Position.Z, StringFormat='{}{0} m'}" />
|
||||
<TextBlock Text="{Binding HorizontalAccuracy, StringFormat='{}{0} m'}" />
|
||||
</StackPanel>
|
||||
<local:AltitudeGraph Grid.Row="2" Grid.ColumnSpan="2" Height="50" x:Name="altitude" Value="{Binding Position.Z}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<local:View2D x:Name="view2d" />
|
||||
</TabItem>
|
||||
<TabItem Header="3D">
|
||||
<local:View3D x:Name="view3d" />
|
||||
</TabItem>
|
||||
<TabItem Header="Messages">
|
||||
<TextBox x:Name="output"
|
||||
|
|
|
|||
|
|
@ -1,19 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using Esri.ArcGISRuntime.Mapping;
|
||||
using NmeaParser;
|
||||
|
||||
namespace SampleApp.WinDesktop
|
||||
{
|
||||
|
|
@ -30,10 +21,10 @@ namespace SampleApp.WinDesktop
|
|||
Filter = "Text files|*.txt|NMEA Log|*.nmea|All files|*.*",
|
||||
InitialDirectory = new System.IO.FileInfo(typeof(MainWindow).Assembly.Location).DirectoryName
|
||||
};
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
mapView.Map = new Map(Basemap.CreateNavigationVector());
|
||||
|
||||
//Get list of serial ports for device tab
|
||||
var availableSerialPorts = System.IO.Ports.SerialPort.GetPortNames().OrderBy(s=>s);
|
||||
|
|
@ -46,7 +37,6 @@ namespace SampleApp.WinDesktop
|
|||
|
||||
//Use a log file for playing back logged data
|
||||
var device = new NmeaParser.NmeaFileDevice("NmeaSampleData.txt");
|
||||
|
||||
StartDevice(device);
|
||||
}
|
||||
|
||||
|
|
@ -75,10 +65,9 @@ namespace SampleApp.WinDesktop
|
|||
//Start new device
|
||||
currentDevice = device;
|
||||
currentDevice.MessageReceived += device_MessageReceived;
|
||||
mapView.LocationDisplay.DataSource = new NmeaLocationProvider(device);
|
||||
mapView.LocationDisplay.IsEnabled = true;
|
||||
mapView.LocationDisplay.InitialZoomScale = 5000;
|
||||
mapView.LocationDisplay.AutoPanMode = Esri.ArcGISRuntime.UI.LocationDisplayAutoPanMode.Navigation;
|
||||
view2d.NmeaDevice = device;
|
||||
view3d.NmeaDevice = device;
|
||||
|
||||
if (device is NmeaParser.NmeaFileDevice)
|
||||
currentDeviceInfo.Text = string.Format("NmeaFileDevice( file={0} )", ((NmeaParser.NmeaFileDevice)device).FileName);
|
||||
else if (device is NmeaParser.SerialPortDevice)
|
||||
|
|
|
|||
|
|
@ -4,8 +4,13 @@
|
|||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<RootNamespace>SampleApp.WinDesktop</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="car.glb" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NmeaParser\NmeaParser.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
@ -14,8 +19,9 @@
|
|||
<Link>NmeaSampleData.txt</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Esri.ArcGISRuntime.WPF" Version="100.7.0" />
|
||||
<Content Include="car.glb">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
75
src/SampleApp.WinDesktop/View2D.xaml
Normal file
75
src/SampleApp.WinDesktop/View2D.xaml
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<UserControl x:Class="SampleApp.WinDesktop.View2D"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SampleApp.WinDesktop"
|
||||
xmlns:esri="http://schemas.esri.com/arcgis/runtime/2013"
|
||||
mc:Ignorable="d">
|
||||
<Grid>
|
||||
<!--Map-->
|
||||
<esri:MapView x:Name="mapView" Grid.Row="1">
|
||||
<local:RestoreAutoPanMode.RestoreAutoPanSettings>
|
||||
<local:RestoreAutoPanMode DelayInSeconds="2.5" PanMode="Navigation" IsEnabled="True" RestoreScale="5000" />
|
||||
</local:RestoreAutoPanMode.RestoreAutoPanSettings>
|
||||
</esri:MapView>
|
||||
|
||||
<!--North arrow-->
|
||||
<Grid Width="50" Height="50" HorizontalAlignment="Right" VerticalAlignment="Bottom"
|
||||
Margin="20" RenderTransformOrigin=".5,.5">
|
||||
<Grid.Resources>
|
||||
<local:ReverseConverter x:Key="conv" />
|
||||
</Grid.Resources>
|
||||
<Grid.Effect>
|
||||
<DropShadowEffect Direction="0" ShadowDepth="0"
|
||||
BlurRadius="10" Opacity=".75" />
|
||||
</Grid.Effect>
|
||||
<Grid.RenderTransform>
|
||||
<RotateTransform Angle="{Binding ElementName=mapView, Path=MapRotation, Converter={StaticResource conv}}" />
|
||||
</Grid.RenderTransform>
|
||||
<TextBlock Text="Ù" FontFamily="Wingdings" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="40" Foreground="White" />
|
||||
<TextBlock Text="N" HorizontalAlignment="Center" VerticalAlignment="Top"
|
||||
FontSize="12" Margin="0,-5,0,0" RenderTransformOrigin=".5,.5"
|
||||
Foreground="White" >
|
||||
<TextBlock.RenderTransform>
|
||||
<RotateTransform Angle="{Binding ElementName=mapView, Path=MapRotation}" />
|
||||
</TextBlock.RenderTransform>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
|
||||
<Grid HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
Margin="10" Background="White" Width="150"
|
||||
DataContext="{Binding ElementName=mapView, Path=LocationDisplay.Location}">
|
||||
<Grid.Effect>
|
||||
<DropShadowEffect Direction="0" ShadowDepth="0"
|
||||
BlurRadius="10" Opacity=".75" />
|
||||
</Grid.Effect>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Border Background="CornflowerBlue" Grid.ColumnSpan="2" Padding="10">
|
||||
<TextBlock Text="Details" FontSize="20" FontWeight="Bold" Foreground="White" />
|
||||
</Border>
|
||||
<StackPanel Margin="10" Grid.Row="1">
|
||||
<TextBlock Text="Speed:" FontWeight="Bold" />
|
||||
<TextBlock Text="Course:" FontWeight="Bold" />
|
||||
<TextBlock Text="Altitude:" FontWeight="Bold" />
|
||||
<TextBlock Text="Accuracy:" FontWeight="Bold" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,10,10,10" Grid.Column="1" Grid.Row="1">
|
||||
<TextBlock Text="{Binding Velocity, StringFormat='{}{0} km/h'}" />
|
||||
<TextBlock Text="{Binding Course, StringFormat='{}{0}°'}" />
|
||||
<TextBlock Text="{Binding Position.Z, StringFormat='{}{0} m'}" />
|
||||
<TextBlock Text="{Binding HorizontalAccuracy, StringFormat='{}{0} m'}" />
|
||||
</StackPanel>
|
||||
<local:AltitudeGraph Grid.Row="2" Grid.ColumnSpan="2" Height="50" x:Name="altitude" Value="{Binding Position.Z}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
59
src/SampleApp.WinDesktop/View2D.xaml.cs
Normal file
59
src/SampleApp.WinDesktop/View2D.xaml.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
using Esri.ArcGISRuntime.Mapping;
|
||||
using NmeaParser;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace SampleApp.WinDesktop
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for View2D.xaml
|
||||
/// </summary>
|
||||
public partial class View2D : UserControl
|
||||
{
|
||||
public View2D()
|
||||
{
|
||||
InitializeComponent();
|
||||
if (mapView.Map == null)
|
||||
{
|
||||
mapView.Map = new Map(Basemap.CreateNavigationVector());
|
||||
}
|
||||
|
||||
mapView.LocationDisplay.InitialZoomScale = 5000;
|
||||
mapView.LocationDisplay.AutoPanMode = Esri.ArcGISRuntime.UI.LocationDisplayAutoPanMode.Navigation;
|
||||
}
|
||||
|
||||
public NmeaDevice NmeaDevice
|
||||
{
|
||||
get { return (NmeaDevice)GetValue(NmeaDeviceProperty); }
|
||||
set { SetValue(NmeaDeviceProperty, value); }
|
||||
}
|
||||
|
||||
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
|
||||
public static readonly DependencyProperty NmeaDeviceProperty =
|
||||
DependencyProperty.Register(nameof(NmeaDevice), typeof(NmeaDevice), typeof(View2D), new PropertyMetadata(null, OnNmeaDevicePropertyChanged));
|
||||
|
||||
private static void OnNmeaDevicePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((View2D)d).InitNmeaProvider(e.OldValue as NmeaDevice, e.NewValue as NmeaDevice);
|
||||
}
|
||||
|
||||
private void InitNmeaProvider(NmeaDevice oldDevice, NmeaDevice newDevice)
|
||||
{
|
||||
mapView.LocationDisplay.IsEnabled = false;
|
||||
if (newDevice != null)
|
||||
{
|
||||
mapView.LocationDisplay.DataSource = new NmeaLocationProvider(newDevice);
|
||||
mapView.LocationDisplay.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/SampleApp.WinDesktop/View3D.xaml
Normal file
13
src/SampleApp.WinDesktop/View3D.xaml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<UserControl x:Class="SampleApp.WinDesktop.View3D"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SampleApp.WinDesktop"
|
||||
xmlns:esri="http://schemas.esri.com/arcgis/runtime/2013"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<esri:SceneView x:Name="sceneView"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
120
src/SampleApp.WinDesktop/View3D.xaml.cs
Normal file
120
src/SampleApp.WinDesktop/View3D.xaml.cs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
using Esri.ArcGISRuntime.Geometry;
|
||||
using Esri.ArcGISRuntime.Mapping;
|
||||
using Esri.ArcGISRuntime.Symbology;
|
||||
using Esri.ArcGISRuntime.UI;
|
||||
using NmeaParser;
|
||||
using NmeaParser.Messages;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace SampleApp.WinDesktop
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for View3D.xaml
|
||||
/// </summary>
|
||||
public partial class View3D : UserControl
|
||||
{
|
||||
public View3D()
|
||||
{
|
||||
InitializeComponent();
|
||||
InitializeScene();
|
||||
this.IsVisibleChanged += View3D_IsVisibleChanged;
|
||||
}
|
||||
|
||||
private void View3D_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if(IsVisible)
|
||||
{
|
||||
graphic3D.Geometry = null;
|
||||
if (NmeaDevice != null)
|
||||
NmeaDevice.MessageReceived += NmeaDevice_MessageReceived;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (NmeaDevice != null)
|
||||
NmeaDevice.MessageReceived -= NmeaDevice_MessageReceived;
|
||||
}
|
||||
}
|
||||
|
||||
private void NmeaDevice_MessageReceived(object sender, NmeaMessageReceivedEventArgs e)
|
||||
{
|
||||
if (e.Message is Rmc rmc)
|
||||
UpdateLocation(rmc.Latitude, rmc.Longitude, rmc.Course);
|
||||
}
|
||||
|
||||
private Graphic graphic3D;
|
||||
|
||||
private async void InitializeScene()
|
||||
{
|
||||
sceneView.Scene = new Scene(BasemapType.Imagery);
|
||||
sceneView.Scene.BaseSurface.ElevationSources.Add(new ArcGISTiledElevationSource(new Uri("https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer")));
|
||||
sceneView.GraphicsOverlays.Add(new GraphicsOverlay() { Id = "Position", Renderer = new SimpleRenderer() });
|
||||
sceneView.GraphicsOverlays["Position"].Renderer.SceneProperties.HeadingExpression = "[Heading]";
|
||||
|
||||
graphic3D = new Esri.ArcGISRuntime.UI.Graphic() { Symbol = SimpleMarkerSceneSymbol.CreateSphere(System.Drawing.Color.FromArgb(127, 255, 0, 0), 10) };
|
||||
var symb = await ModelSceneSymbol.CreateAsync(new Uri("car.glb", UriKind.Relative));
|
||||
symb.Width = 3; symb.Depth = 5; symb.Height = 2; symb.AnchorPosition = SceneSymbolAnchorPosition.Bottom;
|
||||
graphic3D.Symbol = symb;
|
||||
sceneView.GraphicsOverlays["Position"].Graphics.Add(graphic3D);
|
||||
|
||||
sceneView.CameraController = new OrbitGeoElementCameraController(sceneView.GraphicsOverlays["Position"].Graphics[0], 200);
|
||||
}
|
||||
|
||||
private int updateId = 0;
|
||||
private async void UpdateLocation(double latitude, double longitude, double newHeading)
|
||||
{
|
||||
// Handle 3D updates on 3D Scene
|
||||
var cid = ++updateId;
|
||||
var start = graphic3D.Geometry as MapPoint;
|
||||
if (start == null)
|
||||
{
|
||||
graphic3D.Attributes["Heading"] = newHeading;
|
||||
graphic3D.Geometry = new MapPoint(longitude, latitude, SpatialReferences.Wgs84);
|
||||
}
|
||||
else
|
||||
{
|
||||
var heading = (double)graphic3D.Attributes["Heading"];
|
||||
if (double.IsNaN(heading))
|
||||
graphic3D.Attributes["Heading"] = heading = newHeading;
|
||||
var dx = longitude - start.X;
|
||||
var dy = latitude - start.Y;
|
||||
var dh = newHeading - heading;
|
||||
if (dh < -180) dh += 360;
|
||||
else if (dh > 180) dh -= 360;
|
||||
// Interpolate position update over 16 frames
|
||||
for (int i = 0; i < 60; i++)
|
||||
{
|
||||
if (updateId != cid) break;
|
||||
graphic3D.Geometry = new MapPoint(start.X + dx * i / 60d, start.Y + dy * i / 60d, start.SpatialReference);
|
||||
var h = heading + dh * i / 60d;
|
||||
if (h > 360) h -= 360;
|
||||
else if (h < 0) h += 360;
|
||||
graphic3D.Attributes["Heading"] = h;
|
||||
await Task.Delay(16).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public NmeaDevice NmeaDevice
|
||||
{
|
||||
get { return (NmeaDevice)GetValue(NmeaDeviceProperty); }
|
||||
set { SetValue(NmeaDeviceProperty, value); }
|
||||
}
|
||||
|
||||
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
|
||||
public static readonly DependencyProperty NmeaDeviceProperty =
|
||||
DependencyProperty.Register(nameof(NmeaDevice), typeof(NmeaDevice), typeof(View3D), new PropertyMetadata(null));
|
||||
|
||||
}
|
||||
}
|
||||
BIN
src/SampleApp.WinDesktop/car.glb
Normal file
BIN
src/SampleApp.WinDesktop/car.glb
Normal file
Binary file not shown.
BIN
src/SampleApp.WinDesktop/simplecar.glb
Normal file
BIN
src/SampleApp.WinDesktop/simplecar.glb
Normal file
Binary file not shown.
Loading…
Reference in a new issue