|
|
| (36 intermediate revisions by 2 users not shown) |
| Line 1: |
Line 1: |
| This page describes how to create a Maria GDK map client utilising '''''MariaGlobeMapControl''''' with 2D and 3D visualisation of map, tracks and draw objects.
| | [[File:Globe-3D.PNG|right|thumb|Maria Globe Client]] |
|
| |
|
| [[File:Globe-3D.PNG|right|thumb|Maria Globe Client]]
| | These pages describe how to create a Maria GDK map client utilising '''''MariaGlobeMapControl''''' with 2D and 3D visualisation of map, tracks and draw objects. |
|
| |
|
| == General == | | == General == |
|
| |
|
| '''''<pre style="color: yellow; background:orange" >This page is under construction!</pre>'''''
| |
| ; Note | | ; Note |
| :* You will need to include the '''''TPG.Maria.MariaGlobeMapControl''''' ''NuGet'' package (Currently available Teleplan Globe internal only) | | :* You will need to include the '''''TPG.MariaGDK3D''''' NuGet package. (Currently available Teleplan Globe internal only) |
| | :: For more info, see [[Development requirements#Loading Maria GDK Packages| Loading Maria GDK, NuGet Packages]] |
| :* Sample code is found in the '''''MariaGlobeClient''''' project, in the '''''Sample Projects''''' solution. | | :* Sample code is found in the '''''MariaGlobeClient''''' project, in the '''''Sample Projects''''' solution. |
| :* Note that the sample code is specifying the service connections in ''code'', and not by '''''app.config'''''. | | :* Note that the sample code is specifying the service connections in ''code'', and not by '''''app.config'''''. |
| :* For general troubleshooting, see [[Development_troubleshooting|Development troubleshooting]] | | :* For general troubleshooting, see [[Development_troubleshooting|Development troubleshooting]] |
|
| |
|
| == Utilising the globe control == | | == Sections == |
| | | ;The following main topics are handled: |
| === Including MariaGlobeMapControl ===
| |
| | |
| Create a WPF Application, and add reference to the '''''TPG.Maria.MariaGlobeMapControl''''' package. <br/>
| |
| For NuGet details, see ''[[Basic map client|Maria Basic Map Client]]''.
| |
| | |
| Add the '''''MariaGlobeMapControl''''' to the Main window xaml.
| |
| | |
| <source lang="xml">
| |
| <Window x:Class="MariaGlobeClient.MainWindow"
| |
| . . .
| |
| Title="MariaGlobeClient" Height="600" Width="600">
| |
| <Grid>
| |
| <MariaGlobeMapControl x:Name="MariaGlobeCtrl" Background="#E9ECFA"
| |
| Layers="{Binding Layers}"
| |
| MiniMapLayer="{Binding MapViewModel.MiniMapLayer, Mode=OneWay}"
| |
| CenterScale="{Binding MapViewModel.CenterScale}"
| |
| CenterPosition="{Binding MapViewModel.CenterPosition}"
| |
| MouseMoveDistanceToStartTracking="0"
| |
| ZoomOnDblClick="False"
| |
| DegreeToLockRotateAndScale="2"
| |
| PercentageToLockScaleOnly="2.0"
| |
| Is3DMode="False"
| |
| IsRulerVisible="True"
| |
| IsCenterPositionIndicatorEnabled="True"
| |
| IsMiniMapVisible="True" />
| |
| </Grid>
| |
| </Window>
| |
| </source>
| |
| | |
| === Add view models ===
| |
| | |
| * Add a view model class (''MainViewModel'') for communication with the Maria component, and another (''MapViewModel'') for map handling. Both including inheritance of '''''ViewModelBase''''', for details see [[Map_interaction_client/Prepare_your_application_for_interactions|''Prepare your application for interactions'']].
| |
| | |
| * Set the data context of your client window to the '''''MainViewModel''''', and implement an event handler for the '''''WindowClosing''''' event. For details see [[Basic map client|Maria Basic Map Client]].
| |
| | |
| ==== MainViewModel ====
| |
| | |
| Extend the inheritances to include the '''''IMariaGlobeMapViewModel''''' and implement the interface.
| |
| | |
| <source lang="c#">
| |
| public class MainViewModel : ViewModelBase, IMariaGlobeMapViewModel
| |
| {
| |
| public IGlobeMapManager GlobeMapManager { get; }
| |
| public IDrawObjectLayerFactory DrawObjectLayerFactory { get; private set; }
| |
| public IGlobeMapViewModel GlobeMapViewModel { get; set; }
| |
| ...
| |
| </source>
| |
| | |
| Add the following fields and properties:
| |
| | |
| <source lang="c#">
| |
| . . .
| |
| private IMariaMapLayer _mapLayer;
| |
| private IMariaMapLayer _miniMapLayer;
| |
| | |
| public MapViewModel MapViewModel { get; }
| |
| public ObservableCollection<IMariaLayer> Layers { get; set; }
| |
| . . .
| |
| </source>
| |
| | |
| In the constructor:
| |
| * initialise the Layer property
| |
| * set up the map services
| |
| * create the GlobeMapManager
| |
| * create map layer, mini-map layer and add to the '''''Layers''''' property
| |
| * instanciate the MapViewModel
| |
| | |
| | |
| <source lang="c#">
| |
| ...
| |
| public MainViewModel()
| |
| {
| |
| Layers = new ObservableCollection<IMariaLayer>();
| |
| | |
| IBindingFactory bindingFactory = new BindingFactory();
| |
| IEndpointAddressFactory endpointAddressFactory = new EndpointAddressFactory();
| |
| | |
| // Set up template client
| |
| var mapTemplateServiceClient = new MapTemplateServiceClientFactory(
| |
| bindingFactory,
| |
| endpointAddressFactory).New("http://tpg-mariagdktest:9008/maptemplates", BindingType.BasicHttp);
| |
| | |
| // Set up catalog service client and connect to service
| |
| var mapCatalogServiceClient = new MapCatalogServiceClient(
| |
| bindingFactory.New(BindingType.BasicHttp),
| |
| endpointAddressFactory.New("http://tpg-mariagdktest:9008/catalog"));
| |
| | |
| GlobeMapManager = new GlobeMapManager(mapCatalogServiceClient, mapTemplateServiceClient);
| |
| GlobeMapManager.TileCacheManager.MaxCacheSize = 250;
| |
| | |
| _mapLayer = new MapLayer(GlobeMapManager);
| |
| _miniMapLayer = new MapLayer(GlobeMapManager);
| |
| Layers.Add(_mapLayer);
| |
| | |
| MapViewModel = new MapViewModel(_mapLayer, _miniMapLayer);
| |
| }
| |
| | |
| </source>
| |
| | |
| Make the class disposable by including and implementing '''''IDisposable'''''.
| |
| | |
| <source lang="c#">
| |
| ...
| |
| public class MainViewModel : ViewModelBase, IMariaGlobeMapViewModel, IDisposable
| |
| {
| |
| ...
| |
| public void Dispose()
| |
| {
| |
| _mapLayer?.Dispose();
| |
| _miniMapLayer?.Dispose();
| |
| GlobeMapViewModel?.Dispose();
| |
| }
| |
| </source>
| |
| | |
| ==== MapViewModel ====
| |
| | |
| Add the following fields and properties:
| |
| | |
| <source lang="c#">
| |
| ...
| |
| private readonly IMariaMapLayer _mapLayer;
| |
| | |
| public IMariaMapLayer MiniMapLayer { get; private set; }
| |
| public GeoPos CenterPosition { get; set; }
| |
| public double CenterScale { get; set; }
| |
| ...
| |
| </source>
| |
| | |
| Create a constructor, taking map layer and and mini-map layer as parameters and initialising map and mini-map event handling.
| |
| | |
| <source lang="c#">
| |
| public MapViewModel(IMariaMapLayer mapLayer, IMariaMapLayer miniMapLayer)
| |
| {
| |
| _mapLayer = mapLayer;
| |
| | |
| if (_mapLayer != null)
| |
| _mapLayer.LayerInitialized += OnMapLayerInitialized;
| |
| | |
| MiniMapLayer = miniMapLayer;
| |
| if (MiniMapLayer != null)
| |
| MiniMapLayer.LayerInitialized += OnMiniMapLayerInitialized;
| |
| }
| |
| </source>
| |
| | |
| Implement the '''''LayerInitialized''''' event handlers for the map and mini map.
| |
| | |
| <source lang="c#">
| |
| ...
| |
| private void OnMapLayerInitialized()
| |
| {
| |
| CenterPosition = new GeoPos(59.908358, 10.628190); // TPG - Lysaker
| |
| CenterScale = 500000;
| |
| | |
| _mapLayer.ActiveMapTemplate = _mapLayer.ActiveMapTemplates.Any() ? _mapLayer.ActiveMapTemplates.First() : null;
| |
| }
| |
| | |
| private void OnMiniMapLayerInitialized()
| |
| {
| |
| MiniMapLayer.ActiveMapTemplate = _mapLayer.ActiveMapTemplates.Any() ? _mapLayer.ActiveMapTemplates.First() : null;
| |
| }
| |
| ...
| |
| </source>
| |
| | |
| === Running the globe client ===
| |
| | |
| Running your application, the window area should now include 2D map information and mini map - and you should be able to navigate the map!
| |
| | |
| [[File:Globe_First-2D.PNG|none|frame|Globe client with 2D map]]
| |
| | |
| To start the globe client in #D mode, Change the '''''Is3DMode''''' property of the '''''MariaGlobeMapControl''''' to ''True'', and start the application again.<br>
| |
| | |
| The window area should now include 3D map information - and you should be able to navigate the 3D-map!
| |
| | |
| | |
| [[File:Globe_First-3D.PNG|none|frame|Globe client with 3D map]]
| |
| | |
| == Map interaction ==
| |
| | |
| === Map utilities ===
| |
| | |
| The different map utilities can be turned on/off programatically e.g. through User controlls.
| |
| For more details about tools and map functionality, see '''''[[Map interaction client]]''''', '''''[[Map_interaction_client/Map_interaction|Map_interaction]]''''' and '''''[[Map_interaction_client/Tools_interaction|Tools interaction]]'''''
| |
| | |
| Here is an example of adding check boxes with direct binding to the '''''MariaGlobeMapControl''''' from your main window.
| |
| | |
| <source lang="xml">
| |
| ...
| |
| <Grid>
| |
| <Grid.RowDefinitions>
| |
| <RowDefinition Height="auto"/>
| |
| <RowDefinition Height="*"/>
| |
| </Grid.RowDefinitions>
| |
| <StackPanel Grid.Row="0" Orientation="Horizontal"
| |
| Visibility="Visible">
| |
| <GroupBox Header="Map Utilities">
| |
| <StackPanel Orientation="Vertical" Visibility="Visible" Margin="5">
| |
| <CheckBox Content="Ruler" Margin="3"
| |
| ToolTip="Ruler"
| |
| IsChecked="{Binding IsRulerVisible, ElementName=MariaGlobeCtrl}" />
| |
| <CheckBox Content="Pan Navigation" Margin="3"
| |
| ToolTip="Pan Navigation"
| |
| IsChecked="{Binding IsPanNavigationVisible, ElementName=MariaGlobeCtrl}" />
| |
| <CheckBox Content="Scale Bar" Margin="3"
| |
| ToolTip="Center Position, Scale Bar"
| |
| IsChecked="{Binding IsScaleBarVisible, ElementName=MariaGlobeCtrl}" />
| |
| <CheckBox Content="Center Mark" Margin="3"
| |
| ToolTip="Center Position Indicator"
| |
| IsChecked="{Binding IsCenterPositionIndicatorEnabled, ElementName=MariaGlobeCtrl}" />
| |
| <CheckBox Content="Mini Map" Margin="3"
| |
| ToolTip="Mini Map"
| |
| IsChecked="{Binding IsMiniMapVisible, ElementName=MariaGlobeCtrl}" />
| |
| </StackPanel>
| |
| </GroupBox>
| |
| </StackPanel>
| |
| <Grid Grid.Row="1">
| |
| <mariaglobemapcontrol:MariaGlobeMapControl x:Name="MariaGlobeCtrl"
| |
| Background="#E9ECFA"
| |
| Layers="{Binding Layers}"
| |
| MiniMapLayer="{Binding MapViewModel.MiniMapLayer, Mode=OneWay}"
| |
| CenterScale="{Binding MapViewModel.CenterScale}"
| |
| CenterPosition="{Binding MapViewModel.CenterPosition}"
| |
| Is3DMode="False"
| |
| MouseMoveDistanceToStartTracking="0"
| |
| ZoomOnDblClick="False"
| |
| DegreeToLockRotateAndScale="2"
| |
| PercentageToLockScaleOnly="2.0"
| |
| IsRulerVisible="True"
| |
| IsCenterPositionIndicatorEnabled="True"
| |
| IsMiniMapVisible="True" />
| |
| </Grid>
| |
| </Grid>
| |
| ...
| |
| </source>
| |
| | |
| === Map settings ===
| |
| | |
| Your map service will most likely contain different map templates, and you would like to select the template to be used.<br>
| |
| In we will create a list of templates to chose from, display the current center position and scale, and also choose the display mode - 2D or 3D.
| |
| | |
| Add the following to your Main Window XAML:
| |
| | |
| <source lang="xml">
| |
| ...
| |
| <GroupBox Header="Map Settings">
| |
| <Grid Margin="3">
| |
| <Grid.RowDefinitions>
| |
| <RowDefinition Height="auto" />
| |
| <RowDefinition Height="auto" />
| |
| <RowDefinition Height="auto" />
| |
| <RowDefinition Height="auto" />
| |
| </Grid.RowDefinitions>
| |
| <Grid.ColumnDefinitions>
| |
| <ColumnDefinition Width="auto"/>
| |
| <ColumnDefinition Width="*"/>
| |
| </Grid.ColumnDefinitions>
| |
| <ComboBox Name="cmbActiveMap" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Margin="0,0,0,5"
| |
| Width="Auto" Height="Auto" MinWidth="200"
| |
| VerticalAlignment="Top"
| |
| ItemsSource="{Binding MapViewModel.AvailableMapTemplateNames}"
| |
| SelectedItem="{Binding MapViewModel.ActiveMapTemplateName}"
| |
| IsSynchronizedWithCurrentItem="true" />
| |
| | |
| <Label Grid.Row="1" Grid.Column="0"
| |
| Content="Mode" FontWeight="DemiBold"/>
| |
| <StackPanel Grid.Row="1" Grid.Column="1">
| |
| <RadioButton Content="2D"
| |
| IsChecked="{Binding MapViewModel.Is2DMode, Mode=OneWay}" />
| |
| <RadioButton Content="3D"
| |
| IsChecked="{Binding MapViewModel.Is3DMode}" />
| |
| </StackPanel>
| |
| | |
| <Label Grid.Row="2" Grid.Column="0" FontWeight="DemiBold">
| |
| <TextBlock>
| |
| Center<LineBreak/>
| |
| Scale
| |
| </TextBlock>
| |
| </Label>
| |
| <TextBox Grid.Row="2" Grid.Column="1" VerticalAlignment="Center"
| |
| Text="{Binding MapViewModel.CenterScale, StringFormat=N1}"
| |
| IsReadOnly="True" />
| |
| | |
| <Label Grid.Row="3" Grid.Column="0" FontWeight="DemiBold">
| |
| <TextBlock>
| |
| Center<LineBreak/>
| |
| Position
| |
| </TextBlock>
| |
| </Label>
| |
| <TextBox Grid.Row="3" Grid.Column="1" VerticalAlignment="Center"
| |
| Text="{Binding MapViewModel.CenterPosition, Mode=OneWay}"
| |
| IsReadOnly="True" />
| |
| </Grid>
| |
| </GroupBox>
| |
| ...
| |
| </source>
| |
| | |
| And bind the '''''Is3DMode''''' property of the '''''MariaGlobeMapControl''''' to same value as the Radio button.
| |
| | |
| <source lang="xml">
| |
| ...
| |
| <mariaglobemapcontrol:MariaGlobeMapControl x:Name="MariaGlobeCtrl" Background="#E9ECFA"
| |
| Is3DMode="{Binding MapViewModel.Is3DMode}"
| |
| Layers="{Binding Layers}"
| |
| ...
| |
| </source>
| |
| | |
| Extend the '''''MapViewModel''''' with:
| |
| * fields and properties to handle display of available map templates and 2D/3D mode selection
| |
| * '''''initialized''''' event handlers with initialisation and selection of templates
| |
| * '''''CenterPosition''''' and '''''CenterScale''''' properties with notification handling.
| |
| | |
| <source lang="C#">
| |
| ...
| |
| private Dictionary<string, MapTemplate> _availableMapTemplateDictionary = new Dictionary<string, MapTemplate>();
| |
| | |
| private double _scale;
| |
| private GeoPos _pos;
| |
| private bool _is3DMode;
| |
| ...
| |
| public bool Is2DMode { get { return !_is3DMode; } }
| |
| | |
| public bool Is3DMode
| |
| {
| |
| get { return _is3DMode; }
| |
| set
| |
| {
| |
| _is3DMode = value;
| |
| NotifyPropertyChanged(() => Is2DMode);
| |
| NotifyPropertyChanged(() => Is3DMode);
| |
| }
| |
| }
| |
| | |
| public GeoPos CenterPosition
| |
| {
| |
| get { return _pos; }
| |
| set
| |
| {
| |
| _pos = value;
| |
| NotifyPropertyChanged(() => CenterPosition);
| |
| }
| |
| }
| |
| | |
| public double CenterScale
| |
| {
| |
| get { return _scale; }
| |
| set
| |
| {
| |
| _scale = value;
| |
| NotifyPropertyChanged(() => CenterScale);
| |
| }
| |
| }
| |
| | |
| public MapViewModel(IMariaMapLayer mapLayer, IMariaMapLayer miniMapLayer)
| |
| {
| |
| _mapLayer = mapLayer;
| |
| | |
| if (_mapLayer != null)
| |
| _mapLayer.LayerInitialized += OnMapLayerInitialized;
| |
| | |
| MiniMapLayer = miniMapLayer;
| |
| if (MiniMapLayer != null)
| |
| MiniMapLayer.LayerInitialized += OnMiniMapLayerInitialized;
| |
| }
| |
| | |
| private void OnMapLayerInitialized()
| |
| {
| |
| foreach (var template in _mapLayer.ActiveMapTemplates)
| |
| {
| |
| _availableMapTemplateDictionary.Add(template.Name, template);
| |
| }
| |
| | |
| NotifyPropertyChanged(() => AvailableMapTemplateNames);
| |
| | |
| CenterPosition = new GeoPos(59.908358, 10.628190); // TPG - Lysaker
| |
| CenterScale = 3500000;
| |
| | |
| _mapLayer.ActiveMapTemplate = _mapLayer.ActiveMapTemplates.Any() ?
| |
| _mapLayer.ActiveMapTemplates.First() : null;
| |
| | |
| _mapLayer.ActiveMapTemplate = PreferredMapTemplate();
| |
| NotifyPropertyChanged(() => ActiveMapTemplateName);
| |
| }
| |
| | |
| private void OnMiniMapLayerInitialized()
| |
| {
| |
| MiniMapLayer.ActiveMapTemplate = _mapLayer.ActiveMapTemplates.Any() ?
| |
| _mapLayer.ActiveMapTemplates.First() : null;
| |
| MiniMapLayer.ActiveMapTemplate = PreferredMapTemplate();
| |
| }
| |
| | |
| | |
| | |
| public IEnumerable<string> AvailableMapTemplateNames
| |
| {
| |
| get
| |
| {
| |
| return _availableMapTemplateDictionary.Keys.ToList();
| |
| }
| |
| }
| |
| | |
| public string ActiveMapTemplateName
| |
| {
| |
| get
| |
| {
| |
| if (_mapLayer != null && _mapLayer.ActiveMapTemplate != null)
| |
| return _mapLayer.ActiveMapTemplate.Name;
| |
| return "";
| |
| }
| |
| set
| |
| {
| |
| MapTemplate template = null;
| |
| | |
| if (_availableMapTemplateDictionary.ContainsKey(value))
| |
| {
| |
| template = _availableMapTemplateDictionary[value];
| |
| }
| |
| else if (_availableMapTemplateDictionary.Any())
| |
| {
| |
| template = _availableMapTemplateDictionary.Values.First();
| |
| }
| |
| | |
| _mapLayer.ActiveMapTemplate = template;
| |
| MiniMapLayer.ActiveMapTemplate = template;
| |
| NotifyPropertyChanged(() => ActiveMapTemplateName);
| |
| }
| |
| }
| |
| | |
| private MapTemplate PreferredMapTemplate()
| |
| {
| |
| var preferred = "TOPO";
| |
| | |
| if (_availableMapTemplateDictionary.ContainsKey(preferred))
| |
| return _availableMapTemplateDictionary[preferred];
| |
| | |
| return _availableMapTemplateDictionary.Values.Any() ? _availableMapTemplateDictionary.Values.First() : null;
| |
| }
| |
| ...
| |
| </source>
| |
| | |
| === Running with map interaction ===
| |
| | |
| Running your application, observe the following:
| |
| * the map utilities are shown/hidden according to the corresponding check boxes
| |
| * the center position and scale are updated when panning/zooming the map
| |
| * the map template drop-down list contains available map templates
| |
| * the map display changes according to selected map template
| |
| * the map display changes according to deleted mode
| |
| | |
|
| |
|
| {| class="wikitable" | | {| class="wikitable" |
| |-
| |
| |
| |
| [[File:Map_Pref_2D.PNG|500px|Preferred map, 2D]] <br>
| |
| Preferred map, 2D
| |
| |
| |
| [[File:Map_Pref_3D.PNG|500px|Preferred map, 3D]] <br>
| |
| Preferred map, 3D
| |
| |- | | |- |
| | | | | |
| [[File:Map_Alt_2D.PNG|500px|Another map, 2D]] <br> | | ;1. [[Maria_globe_client/Utilising_the_globe_control|Utilising the globe control]] |
| Another map, 2D
| |
| | | | | |
| [[File:Map_Alt_3D.PNG|500px|Another map, 3D]] <br> | | [[File:Globe_First-3D.PNG|150px|link=Maria_globe_client/Utilising_the_globe_control]] |
| Another map, 3D
| |
| |}
| |
| | |
| == Draw object interaction ==
| |
| | |
| In this section, we will create and update three different draw objects, a area object, a line object, and a point object, and see how they are presented in 2D and 3D mode.
| |
| | |
| For more details on track handling, see '''''[http://40.127.187.78/Category:Draw_objects Draw objects]''''', '''''[[Map_interaction_client/Draw_object_interaction|Draw object interaction]]''''' and '''''[[Map_interaction_client/Draw_object_visualization|Draw objects visualisation]]'''''
| |
| | |
| === Adding draw object layer and draw object view model ===
| |
| | |
| To be able to display draw objects, we need to include a draw object layer in the globe client.
| |
| | |
| First, add a view model class, '''''DrawObjectViewModel''''', to handle the draw object interaction:
| |
| | |
| * Inheriting '''''ViewModelBase'''''
| |
| * Add a constructor taking a draw layer object as input
| |
| * Create and implement event handlers for:
| |
| ** '''''LayerInitialized''''' event
| |
| ** '''''ServiceConnected''''' event
| |
| | |
| <source lang="C#">
| |
| public class DrawObjectViewModel : ViewModelBase
| |
| {
| |
| private IMariaDrawObjectLayer _drawObjectLayer;
| |
| private IMariaService _drawObjectService;
| |
| | |
| public DrawObjectViewModel(DrawObjectLayer drawObjectLayer)
| |
| {
| |
| _drawObjectLayer = drawObjectLayer;
| |
| _drawObjectLayer.LayerInitialized += DrawObjectLayer_LayerInitialized;
| |
| _drawObjectLayer.ServiceConnected += DrawObjectLayer_ServiceConnected;
| |
| }
| |
| | |
| private void DrawObjectLayer_LayerInitialized()
| |
| {
| |
| _drawObjectService = new MariaService(
| |
| "DrawObjectService",
| |
| "http://localhost:9008/drawobjects",
| |
| BindingType.BasicHttp);
| |
| | |
| _drawObjectLayer.DrawObjectServices = new ObservableCollection<IMariaService> { _drawObjectService };
| |
| | |
| _drawObjectLayer.CreateDrawObjectList("local", true);
| |
| }
| |
| | |
| private void DrawObjectLayer_ServiceConnected(object sender, MariaServiceEventArgs args)
| |
| {
| |
| _drawObjectLayer.ActiveDrawObjectService = _drawObjectLayer.DrawObjectServices[0];
| |
| | |
| _drawObjectLayer.CreateDrawObjectList("test", false);
| |
| _drawObjectLayer.ActiveDrawObjectList = "test";
| |
| }
| |
| }
| |
| </source>
| |
| | |
| In the '''''MainViewModel''''', add a draw object layer field, and in the constructor, add creation of the layer and '''''DrawObjectViewModel''''' and add the layer to the '''''Layers''''' list.
| |
| | |
| <source lang="C#">
| |
| ...
| |
| public DrawObjectViewModel DrawObjectViewModel { get; private set; }
| |
| | |
| public MainViewModel()
| |
| {
| |
| ...
| |
| DrawObjectLayerFactory = new DrawObjectLayerFactory();
| |
| _drawObjectLayer = new DrawObjectLayer(GlobeMapManager, DrawObjectLayerFactory, true)
| |
| { InitializeCreationWorkflows = true };
| |
| DrawObjectViewModel = new DrawObjectViewModel(_drawObjectLayer);
| |
| Layers.Add(_drawObjectLayer);
| |
| }
| |
| ...
| |
| </source>
| |
| | |
| === Draw object utilities ===
| |
| | |
| Add the following GUI elements to your main window:
| |
| * Button for adding/updating a volume object.
| |
| * Button for adding/updating a line object.
| |
| * Button for adding/updating a point object.
| |
| | |
| | |
| The XAML could look something like this:
| |
| | |
| <source lang="xml">
| |
| ...
| |
| <GroupBox Header="Draw Object" >
| |
| <StackPanel VerticalAlignment="Top">
| |
| <Button Margin="3"
| |
| Content="Volume"
| |
| Command="{Binding DrawObjectViewModel.VolumeTestCmd}"/>
| |
| <Button Margin="3"
| |
| Content="Line"
| |
| Command="{Binding DrawObjectViewModel.LineTestCmd}"/>
| |
| <Button Margin="3"
| |
| Content="Point"
| |
| Command="{Binding DrawObjectViewModel.PointTestCmd}"/>
| |
| </StackPanel>
| |
| </GroupBox>
| |
| ...
| |
| </source>
| |
| | |
| <source lang="C#">
| |
| ...
| |
| ...
| |
| </source>
| |
| | |
| Implement command handler and command handler delegate in '''''DrawObjectViewModel'''''.
| |
| | |
| <source lang="C#">
| |
| ...
| |
| public ICommand VolumeTestCmd { get { return new DelegateCommand(DrawObjectViewModel.CreateOrUpdateVolumeObject); } }
| |
| public ICommand LineTestCmd { get { return new DelegateCommand(DrawObjectViewModel.CreateOrUpdateLineObject); } }
| |
| public ICommand PointTestCmd { get { return new DelegateCommand(DrawObjectViewModel.CreateOrUpdatePointObject); } }
| |
| | |
| public void CreateOrUpdatePointObject(object obj)
| |
| {
| |
| var pos = RandomProvider.GetRandomPosition(_drawObjectLayer.GeoContext.Viewport.GeoRect);
| |
| | |
| var altitude = RandomProvider.GetRandomInt(1, 5) * 500.0;
| |
| | |
| var pointObject = _drawObjectLayer.DrawObjectFactory.CreateTacticalGraphic("STBOPS.OPN.HJKG.APL");
| |
| pointObject.TacticalDataFields.StandardIdentity = StandardIdentity.Hostile;
| |
| pointObject.TacticalDataFields.Name = "Globe-Point";
| |
| pointObject.Id = new ItemId("local", "Globe-Point");
| |
| | |
| pointObject.Points[0] = new GeoPoint() { Latitude = pos.Lat, Longitude = pos.Lon, Altitude = altitude };
| |
| _drawObjectLayer.UpdateStore(pointObject);
| |
| }
| |
| | |
| public void CreateOrUpdateVolumeObject(object obj)
| |
| {
| |
| | |
| var volumeObject = _drawObjectLayer.DrawObjectFactory.CreateTacticalGraphic("TACGRP.FSUPP.ARS.C2ARS.ACA.IRR");
| |
| volumeObject.TacticalDataFields.StandardIdentity = StandardIdentity.Friendly;
| |
| volumeObject.Id = new ItemId("local", "Globe-Volume");
| |
| | |
| var pos = RandomProvider.GetRandomPosition(_drawObjectLayer.GeoContext.Viewport.GeoRect, 0.8);
| |
| var factorLat = _drawObjectLayer.GeoContext.Viewport.GeoRect.DeltaLat * RandomProvider.GetRandomDouble(0.05, 0.1);
| |
| var factorLon = _drawObjectLayer.GeoContext.Viewport.GeoRect.DeltaLon * RandomProvider.GetRandomDouble(0.05, 0.1);
| |
| | |
| volumeObject.Points = new GeoPoint[] {
| |
| new GeoPoint() { Latitude = pos.Lat + factorLat, Longitude = pos.Lon + factorLon },
| |
| new GeoPoint() { Latitude = pos.Lat + factorLat, Longitude = pos.Lon - factorLon },
| |
| new GeoPoint() { Latitude = pos.Lat - factorLat, Longitude = pos.Lon - factorLon },
| |
| new GeoPoint() { Latitude = pos.Lat - factorLat, Longitude = pos.Lon + factorLon },
| |
| };
| |
| | |
| _drawObjectLayer.UpdateStore(volumeObject);
| |
| }
| |
| | |
| public void CreateOrUpdateLineObject(object obj)
| |
| {
| |
| var lineObject = _drawObjectLayer.DrawObjectFactory.CreatePolyLine(new ItemId("local", "Globe-Line"));
| |
| var pt0 = RandomProvider.GetRandomPosition(_drawObjectLayer.GeoContext.Viewport.GeoRect, 0.5);
| |
| var pt1 = RandomProvider.GetRandomPosition(_drawObjectLayer.GeoContext.Viewport.GeoRect, 0.5);
| |
| var pt2 = RandomProvider.GetRandomPosition(_drawObjectLayer.GeoContext.Viewport.GeoRect, 0.5);
| |
| | |
| lineObject.Points = new GeoPoint[] {
| |
| new GeoPoint() { Latitude = pt0.Lat, Longitude = pt0.Lon, Altitude = RandomProvider.GetRandomInt(1, 5) * 500.0 },
| |
| new GeoPoint() { Latitude = pt1.Lat, Longitude = pt1.Lon, Altitude = RandomProvider.GetRandomInt(1, 5) * 500.0 },
| |
| new GeoPoint() { Latitude = pt2.Lat, Longitude = pt2.Lon, Altitude = RandomProvider.GetRandomInt(1, 5) * 500.0 },
| |
| };
| |
| | |
| lineObject.GenericDataFields.LineColor = Colors.Navy;
| |
| var dashStyle = DashStyles.DashDot;
| |
| lineObject.GenericDataFields.LineDashStyle = dashStyle.Dashes.ToList();
| |
| lineObject.GenericDataFields.LineDashStyleOffset = dashStyle.Offset;
| |
| lineObject.GenericDataFields.LineWidth = 6.0;
| |
| | |
| _drawObjectLayer.UpdateStore(lineObject);
| |
| }
| |
| ...
| |
| </source>
| |
| | |
| The '''''RandomProvider''''' class is described in context with [[Map_interaction_client/Track_layer_interaction#Create_tracks| ''Map interaction client, track creation''. ]]
| |
| | |
| === Draw object styling ===
| |
| | |
| Add a draw object style xml file to your project, and include it as a resource. You can find style xml to use [[Draw_object_style_xml| '''here''']]
| |
| | |
| Add the resource to the draw object layer in the '''''LayerInitialized''''' event handler in '''''DrawObjectViewModel'''''.
| |
| | |
| <source lang="C#">
| |
| ...
| |
| private void DrawObjectLayer_LayerInitialized()
| |
| {
| |
| _drawObjectLayer.StyleXml = Properties.Resources.DrawObjectStyle;
| |
| ...
| |
| </source>
| |
| | |
| === Running with draw object interaction ===
| |
| | |
| Running your application, observe the following:
| |
| * Pressing the draw object buttons, draw objects created / moved around in the map area.
| |
| * In 2D mode, the objects can also be selected and moved/resized manually.
| |
| | |
| | |
| {| class="wikitable"
| |
| |- | | |- |
| | | | | |
| [[File:DrawObjects_2D.PNG|500px|Draw objects in map, 2D]] <br> | | ;2. [[Maria_globe_client/Map_interaction|Map interaction]] |
| Draw objects in map, 2D
| | | |
| | [[File:Map_Pref_3D.PNG|150px|link=Maria_globe_client/Map_interaction]] |
| | |- |
| | | | | |
| [[File:DrawObjects_3D.PNG|500px|Draw objects in map, 3D]] <br> | | ;3. [[Maria_globe_client/Draw_object_interaction|Draw object interaction]] |
| Draw objects in map, 3D
| | | |
| |} | | [[File:DrawObjects_3D.PNG|150px|link=Maria_globe_client/Draw_object_interaction]] |
| | |
| == Track interaction ==
| |
| | |
| In this section, we will create and update a track, and observe the presentation in 2D and 3D mode.
| |
| | |
| For more details on track handling, see '''''[http://40.127.187.78/Category:Tracks Tracks]''''', '''''[[Map_interaction_client/Track_layer_interaction|Track interaction]]''''' and '''''[[Map_interaction_client/Track_visualization|Track visualisation]]'''''
| |
| | |
| === Adding draw object layer and draw object view model ===
| |
| | |
| To be able to display tracks, we need to include a track layer in the globe client.
| |
| | |
| First, add a view model class, '''''TrackViewModel''''', to handle the draw object interaction:
| |
| | |
| * Inheriting ViewModelBase
| |
| * Add a constructor taking a draw layer object as input
| |
| * Create and implement event handlers for:
| |
| ** '''''LayerInitialized''''' event
| |
| ** '''''ServiceConnected''''' event
| |
| | |
| <source lang="C#">
| |
| public class TrackViewModel : ViewModelBase
| |
| {
| |
| private TrackLayer _trackLayer;
| |
| private MariaService _trackService;
| |
| | |
| public TrackViewModel(TrackLayer trackLayer)
| |
| {
| |
| _trackLayer = trackLayer;
| |
| | |
| _trackLayer.LayerInitialized += TrackLayer_LayerInitialized;
| |
| _trackLayer.ServiceConnected += TrackLayer_ServiceConnected;
| |
| }
| |
| | |
| private void TrackLayer_LayerInitialized()
| |
| {
| |
| _trackService = new MariaService("TrackService", "http://localhost:9008/tracks", BindingType.BasicHttp);
| |
| _trackLayer.TrackServices = new ObservableCollection<IMariaService> { _trackService };
| |
| }
| |
| | |
| private void TrackLayer_ServiceConnected(object sender, MariaServiceEventArgs args)
| |
| {
| |
| _trackLayer.ActiveTrackService = _trackLayer.TrackServices[0];
| |
| _trackLayer.ExtendedTrackLayer.TrackRefreshInterval = 1000;
| |
| | |
| _trackLayer.TrackLists = new ObservableCollection<string>(_trackLayer.GetTrackLists());
| |
| | |
| if (!_trackLayer.TrackLists.Contains("globeTest"))
| |
| _trackLayer.TrackLists.Add("globeTest");
| |
| | |
| _trackLayer.ActiveTrackList = "globeTest";
| |
| }
| |
| }
| |
| </source>
| |
| | |
| In the '''''MainViewModel''''', add a track layer field, and in the constructor, add creation of the layer and '''''TrackViewModel''''', and add the layer to the '''''Layers''''' list.
| |
| | |
| <source lang="C#">
| |
| ...
| |
| public TrackViewModel TrackViewModel { get; private set; }
| |
| | |
| public MainViewModel()
| |
| {
| |
| ...
| |
| _trackLayer = new TrackLayer(GlobeMapManager);
| |
| TrackViewModel = new TrackViewModel(_trackLayer);
| |
| Layers.Add(_trackLayer);
| |
| }
| |
| ...
| |
| </source>
| |
| | |
| === Track utilities ===
| |
| | |
| Add the following GUI elements to your main window:
| |
| * Button for adding/updating the track
| |
| * Check box to enable/disable automatic track update (simulate movement)
| |
| | |
| The XAML could look something like this:
| |
| | |
| <source lang="xml">
| |
| ...
| |
| <GroupBox Header="Track">
| |
| <StackPanel>
| |
| <Button Margin="3"
| |
| Content="Track"
| |
| Command="{Binding TrackViewModel.TrackTestCmd}"/>
| |
| <CheckBox Margin="3"
| |
| Content="Auto update"
| |
| IsChecked="{Binding TrackViewModel.AutoUpdateActive}"/>
| |
| </StackPanel>
| |
| </GroupBox>
| |
| ...
| |
| </source>
| |
| | |
| In '''''TrackViewModel''''' implement:
| |
| * command handler and command handler delegate in for the track update button
| |
| * auto update property with timer, initialised in during the '''''LayerInitialized''''' event handler.
| |
| | |
| <source lang="C#">
| |
| ...
| |
| private bool _autoUpdateActive;
| |
| Timer _autoUpdateTimer;
| |
| ...
| |
| private void TrackLayer_LayerInitialized()
| |
| {
| |
| ...
| |
| _autoUpdateTimer = new Timer(1000);
| |
| _autoUpdateTimer.Elapsed += OnAutoUpdateTimerTick;
| |
| }
| |
| ...
| |
| public ICommand TrackTestCmd { get { return new DelegateCommand(CreateOrUpdateTrack, CheckAutoUpdate); } }
| |
| | |
| public void CreateOrUpdateTrack(object obj)
| |
| {
| |
| if (_trackLayer.ActiveTrackList == null)
| |
| return;
| |
| | |
| var currentTime = DateTime.UtcNow;
| |
| | |
| var strId = "Globe-Track";
| |
| var itemId = new ItemId(_trackLayer.ActiveTrackList, strId);
| |
| | |
| var list = _trackLayer.GetTrackData(strId);
| |
| | |
| var speed = RandomProvider.GetRandomDouble(100.0, 250.0);
| |
| var course = RandomProvider.GetRandomDouble(0, 360.0);
| |
| var pos = RandomProvider.GetRandomPosition(_trackLayer.GeoContext.Viewport.GeoRect, 0.9);
| |
| | |
| var altitude = RandomProvider.GetRandomDouble(500, 2000);
| |
| var roll = RandomProvider.GetRandomDouble(-45.0, 45.0);
| |
| var pitch = RandomProvider.GetRandomDouble(-30.0, 30.0);
| |
| | |
| ITrackData trackData;
| |
| if (!list.Any())
| |
| {
| |
| trackData = new TrackData(itemId, pos, course, speed) { ObservationTime = currentTime };
| |
| InitTrackData(ref trackData, strId);
| |
| }
| |
| else
| |
| {
| |
| trackData = list[0];
| |
| | |
| var trackAge = trackData.ObservationTime.HasValue ?
| |
| currentTime - trackData.ObservationTime.Value :
| |
| TimeSpan.MaxValue;
| |
| | |
| if (trackAge > new TimeSpan(0, 0, 5))
| |
| {
| |
| trackData = new TrackData(itemId, pos, course, speed) { ObservationTime = currentTime };
| |
| InitTrackData(ref trackData, strId);
| |
| }
| |
| else
| |
| {
| |
| trackData.ObservationTime = currentTime;
| |
| | |
| trackData.Speed = trackData.Speed.HasValue ?
| |
| RandomProvider.GetRandomDouble(trackData.Speed.Value * 0.8, trackData.Speed.Value * 1.2) :
| |
| speed;
| |
| trackData.Course = trackData.Course.HasValue ?
| |
| RandomProvider.GetRandomDouble(trackData.Course.Value * 0.9, trackData.Course.Value * 1.1) :
| |
| course;
| |
| | |
| trackData.Pos = trackData.Pos.HasValue ?
| |
| CalculateNewPos(trackData.Pos.Value, trackData.Speed.Value, trackData.Course.Value, trackAge) :
| |
| pos;
| |
| | |
| if (double.TryParse(trackData.Fields["altitude"], out altitude))
| |
| {
| |
| altitude = RandomProvider.GetRandomDouble(altitude * 0.8, altitude * 1.2);
| |
| }
| |
| | |
| if (double.TryParse(trackData.Fields["roll"], out roll))
| |
| {
| |
| roll = RandomProvider.GetRandomDouble(roll * 0.9, roll * 1.1);
| |
| }
| |
| | |
| if (double.TryParse(trackData.Fields["pitch"], out pitch))
| |
| {
| |
| pitch = RandomProvider.GetRandomDouble(pitch * 0.9, pitch * 1.1);
| |
| }
| |
| }
| |
| }
| |
| | |
| trackData.Fields["altitude"] = altitude.ToString();
| |
| trackData.Fields["roll"] = roll.ToString();
| |
| trackData.Fields["pitch"] = pitch.ToString();
| |
| trackData.Fields["heading"] = course.ToString();
| |
| | |
| _trackLayer.SetTrackData(trackData);
| |
| }
| |
| | |
| public bool CheckAutoUpdate(object obj)
| |
| {
| |
| return !AutoUpdateActive;
| |
| }
| |
| | |
| public bool AutoUpdateActive
| |
| {
| |
| get { return _autoUpdateActive; }
| |
| set
| |
| {
| |
| _autoUpdateActive = value;
| |
| | |
| if (_autoUpdateActive)
| |
| _autoUpdateTimer.Start();
| |
| else
| |
| _autoUpdateTimer.Stop();
| |
| }
| |
| }
| |
| | |
| private void OnAutoUpdateTimerTick(object sender, ElapsedEventArgs e)
| |
| {
| |
| CreateOrUpdateTrack(null);
| |
| }
| |
| | |
| private void InitTrackData(ref ITrackData td, string id)
| |
| {
| |
| td.Fields["name"] = id;
| |
| td.Fields["symbol.2525code"] = "SFAPCF----*****";
| |
| td.Fields["identity"] = "Friendly";
| |
| td.Fields["type"] = "F";
| |
| }
| |
| | |
| private GeoPos CalculateNewPos(GeoPos oldPos, double speed, double course, TimeSpan age)
| |
| {
| |
| var distance = speed * age.TotalSeconds;
| |
| var newPos = Earth.BearingRangeToPos(oldPos, new BearingRange(course, distance));
| |
| return newPos;
| |
| }
| |
| ...
| |
| </source>
| |
| | |
| === Track styling ===
| |
| | |
| Add a track style xml file to your project, and include it as a resource. You can find style xml to use [[Track_style_xml| '''here''']]
| |
| | |
| Add the resource to the track layer in the '''''LayerInitialized''''' event handler in '''''TrackViewModel'''''.
| |
| | |
| <source lang="C#">
| |
| ...
| |
| private void TrackLayer_LayerInitialized()
| |
| {
| |
| _trackLayer.StyleXml = Properties.Resources.TrackStyle;
| |
| ...
| |
| </source>
| |
| | |
| === Running with track interaction ===
| |
| | |
| Running your application, observe the following:
| |
| * Pressing the track button, the track is created/moved around in the map area.
| |
| * The track button is disabled while auto-update is activated.
| |
| | |
| {| class="wikitable"
| |
| |- | | |- |
| | | | | |
| [[File:Track_2D.PNG|500px|Track in map, 2D]] <br> | | ;4. [[Maria_globe_client/Track_interaction|Track interaction]] |
| Track in map, 2D
| | | |
| | [[File:Track_3D.PNG|150px|link=Maria_globe_client/Track_interaction]] |
| | |- |
| | | | | |
| [[File:Track_3D.PNG|500px|Track in map, 3D]] <br> | | ;5. [[Maria_globe_client/Auto_follow|Auto follow]] |
| Track in map, 3D
| | | |
| |} | | [[File:Auto_follow_track_3D.PNG|150px|link=Maria_globe_client/Auto_follow]] |
| | |
| == Auto follow ==
| |
| | |
| In this section, we will utilise the globe control auto follow functionality.
| |
| | |
| ; 2D
| |
| :* valid for tracks and all draw object types | |
| :* the specified object will always be displayed with its center point in the center of the screen.
| |
| :* if the item position is changed, the map area will be adjusted accordingly.
| |
| ; 3D
| |
| :* valid for tracks and point objects only
| |
| | |
| === Auto follow control and display ===
| |
| | |
| Add the following GUI elements to your main window:
| |
| * Check box to enable/disable auto follow
| |
| * Text box for display of currently followed item
| |
| | |
| The XAML could look something like this:
| |
| | |
| <source lang="xml">
| |
| ...
| |
| <GroupBox Header="Auto follow">
| |
| <StackPanel>
| |
| <CheckBox Margin="3"
| |
| Content="Follow selected"
| |
| IsChecked="{Binding IsAutoFollowActive}" />
| |
| <Label Content="Currently followed:" FontWeight="DemiBold" />
| |
| <TextBox Margin="3"
| |
| Text="{Binding AutoFollowItemName, Mode=OneWay}"
| |
| IsReadOnly="True"/>
| |
| </StackPanel>
| |
| </GroupBox>
| |
| ...
| |
| </source>
| |
| | |
| In '''''MainViewModel''''' implement:
| |
| * Auto follow property,
| |
| * auto update property with timer, initialised in during the '''''LayerInitialized''''' event handler.
| |
| | |
| <source lang="C#">
| |
| ...
| |
| public bool IsAutoFollowActive
| |
| {
| |
| get
| |
| {
| |
| return GlobeMapViewModel.AutoFollow.TargetItem != null;
| |
| }
| |
| | |
| set
| |
| {
| |
| if (!(value && GlobeMapViewModel.AutoFollow.FollowSelectedItem()))
| |
| GlobeMapViewModel.AutoFollow.TargetItem = null;
| |
| | |
| NotifyPropertyChanged(() => IsAutoFollowActive);
| |
| NotifyPropertyChanged(() => AutoFollowItemName);
| |
| }
| |
| }
| |
| | |
| public string AutoFollowItemName
| |
| {
| |
| get
| |
| {
| |
| if (GlobeMapViewModel?.AutoFollow?.TargetItem != null)
| |
| {
| |
| return GlobeMapViewModel.AutoFollow.TargetItem.ToString();
| |
| }
| |
| | |
| return "Inactive";
| |
| }
| |
| }
| |
| ...
| |
| </source>
| |
| | |
| === Running with auto follow ===
| |
| | |
| Activating/deactivating auto follow functionality, observe the following:
| |
| * in 3D mode, tracks and point objects only can be selected.
| |
| * the map will re-position to the activated item if the map is panned.
| |
| * the map will re-position to the activated item if the item is moved.
| |
| * the activated item will stay activated also when deselected in the map and when another item is selected.
| |
| ** To activate another item - deactivate, then activate the new item.
| |
| | |
| {| class="wikitable"
| |
| |- | | |- |
| | [[File:Auto_follow_none_2D.PNG|500px|No auto follow, 2D]] <br> No auto follow, 2D | | | |
| |-
| | ;6. [[Maria_globe_client/Advanced_map_settings|Advanced map settings]] |
| | [[File:Auto_follow_area_2D.PNG|500px|Auto follow volume object, 2D]] <br> Auto follow volume object, 2D
| | | |
| | [[File:Auto_follow_line_2D.PNG|500px|Auto follow line object, 2D]] <br> Auto follow line object, 2D
| | [[File:3D_Set_P15_Y120_R30_af.png|150px|link=Maria_globe_client/Advanced_map_settings]] |
| |-
| |
| | [[File:Auto_follow_point_2D.PNG|500px|Auto follow point object, 2D]] <br> Auto follow point object, 2D
| |
| | [[File:Auto_follow_track_2D.PNG|500px|Auto follow track, 2D]] <br> Auto follow track, 2D
| |
| |- | |
| | [[File:Auto_follow_point_3D.PNG|500px|Auto follow point object, 3D]] <br> Auto follow point object, 3D
| |
| | [[File:Auto_follow_track_3D.PNG|500px|Auto follow track, 3D]] <br> Auto follow track, 3D
| |
| |} | | |} |
|
| |
|
| == Advanced map settings ==
| |
|
| |
| === Map rotation ===
| |
|
| |
| As default, 2D maps are displayed with north up, but you may rotate the map by modifying the map layer rotation parameter - GeoContext.RotateValue.
| |
|
| |
| Add the following components:
| |
| * Text box to display the current rotation value
| |
| * Two buttons, for right (clockwise) add left (counter clockwise).
| |
|
| |
| Add the following to your Main Window XAML:
| |
|
| |
| <source lang="xml">
| |
| . . .
| |
| <Label Grid.Row="4" Grid.Column="0"
| |
| Content="Rotation" FontWeight="DemiBold"/>
| |
|
| |
| <TextBox Grid.Row="4" Grid.Column="1" Margin="3"
| |
| Text="{Binding MapViewModel.Rotation}" TextAlignment="Right" />
| |
|
| |
| <StackPanel Grid.Row="5" Grid.Column="1" Orientation="Horizontal"
| |
| VerticalAlignment="Center" HorizontalAlignment="Center">
| |
| <RepeatButton Margin="3" Width="50"
| |
| Content="Left"
| |
| Command="{Binding MapViewModel.RotateLeftCmnd}"/>
| |
| <RepeatButton Margin="3" Width="50"
| |
| Content="Right"
| |
| Command="{Binding MapViewModel.RotateRightCmnd}"/>
| |
| </StackPanel>
| |
| . . .
| |
| </source>
| |
|
| |
| And implement corresponding properties and command handlers in '''''MapViewModel''''':
| |
|
| |
| <source source lang="C#">
| |
| . . .
| |
| public ICommand RotateLeftCmnd { get { return new DelegateCommand(OnRotateLeft); } }
| |
| public ICommand RotateRightCmnd { get { return new DelegateCommand(OnRotateRight); } }
| |
|
| |
| private void OnRotateLeft(object obj)
| |
| {
| |
| Rotation += 1;
| |
| }
| |
|
| |
| private void OnRotateRight(object obj)
| |
| {
| |
| Rotation -= 1;
| |
| }
| |
|
| |
| public int Rotation
| |
| {
| |
| get { return _mapLayer.GeoContext != null ? (int)_mapLayer.GeoContext.RotateValue : 0; }
| |
| set
| |
| {
| |
| _mapLayer.GeoContext.RotateValue = value;
| |
| NotifyPropertyChanged(() => Rotation);
| |
| }
| |
| }
| |
| . . .
| |
| </source>
| |
|
| |
|
| === 3D map settings ===
| |
|
| |
|
| [[Category:Creating applications]] | | [[Category:Creating applications]] |