Maria globe client: Difference between revisions

From Maria GDK Wiki
Jump to navigation Jump to search
()
()
 
(34 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>
Pressing the buttons, the map is rotated, and the rotation display is updated accordingly.
[[File:Rotation.png|frame|none|Map rotation.]]
=== 3D map settings ===
[[File:Roll_pitch_yaw.gif]]




[[Category:Creating applications|none|frame|Roll, pitch and yaw ]]
[[Category:Creating applications]]

Latest revision as of 14:58, 28 October 2019

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

Note
  • You will need to include the TPG.MariaGDK3D NuGet package. (Currently available Teleplan Globe internal only)
For more info, see Loading Maria GDK, NuGet Packages
  • 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.
  • For general troubleshooting, see Development troubleshooting

Sections

The following main topics are handled
1. Utilising the globe control

Globe First-3D.PNG

2. Map interaction

Map Pref 3D.PNG

3. Draw object interaction

DrawObjects 3D.PNG

4. Track interaction

Track 3D.PNG

5. Auto follow

Auto follow track 3D.PNG

6. Advanced map settings

3D Set P15 Y120 R30 af.png