Elevation profile client

From Maria GDK Wiki
Jump to navigation Jump to search
Elevation Profile Client

This page describes how to create a map client utilizing Maria elevation profile functionality. Maria provides display of elevation profile windows for a given set of positions, by utilizing MariaElevationProfileControl (available in the TPG.Maria.MapLayer namespace) or EnhancedElevationProfileControl (available in the TPG.EnhancedElevation.Profile namespace). The controls may be displayed in connection with the map area, as we will do in this example, or in separate windows.

General

This example describes how to include elevation profiles in your map application. Profile possitions are provided by use of the distance tool or from draw object (programatically created polyline within the displayed map area).

Note
  • The example is based on a map component corresponding to Maria Basic Map Client.
  • You will need to include the TPG.MariaGDK NuGet package.
For more info, see Loading Maria GDK, NuGet Packages
  • Sample code for this example is found in the MariaElevationProfile project, in the MariaAdditionalComponents folder of the Sample Projects solution.
  • For troubleshooting, see Development troubleshooting

Preparation, common to both profile components

Create a view model class (ProfilesViewModel) inheriting ViewModelBase, with the MariaWindowViewModel and IMariaDrawObjectLayer as constructor parameters.

public class ProfilesViewModel : ViewModelBase
{
    private MariaWindowViewModel _mainModel;
    private readonly IMariaDrawObjectLayer _drawLayer;

   public ProfilesViewModel(MariaWindowViewModel mainModel,
                            IMariaDrawObjectLayer mariaDrawObjectLayer)
   {
       _mainModel = mainModel;
       _drawLayer = mariaDrawObjectLayer;
   }
}

Instansiate the view model in the main view model.

. . .
public ProfilesViewModel ProfilesViewModel { get; private set; }
private readonly IMariaDrawObjectLayer _drawobjLayer;

public MariaWindowViewModel()
{
    . . .

    _drawobjLayer = new DrawObjectLayer(new DefaultDrawObjectTypeDefinitionProvider())
        {InitializeCreationWorkflows = true};

    ProfilesViewModel = new ProfilesViewModel(this, _drawobjLayer, _mapLayer);
    Layers.Add(_drawobjLayer);
}

Add the following controls to your application window:

Group Function GUI element Description Binding
Map Map selection ComboBox Selecting map template.
Described in
MapViewModel: AvailableMapTemplateNames
MapViewModel: ActiveMapTemplateName
Navigation Ruler CheckBox Controls display of the Ruler component. Direct binding to IsRulerVisible in the MariaCtrl element.
Navigation Pan Navigation CheckBox Controls display of the Pan Navigation component. Direct binding to IsPanNavigationVisible in the MariaCtrl element.
Navigation Scale Bar CheckBox Controls display of the Scale Bar component. Direct binding to IsScaleBarVisible in the MariaCtrl element.
Navigation Mini Map CheckBox Controls display of the Mini Map component. Direct binding to IsMiniMapVisible in the MariaCtrl element.
Tools Distance Tool CheckBox Activates Maria GDK distance tool.
Described in tools
MariaWindowViewModel: IsDistanceToolActive
ProfilesViewModel: ChkDistToolCmd
Tools Zoom tool CheckBox Activates Maria GDK distance tool.
Described in tools
MariaWindowViewModel: IsZoomToolActive
Tools Zoom Out Button Activates Maria GDK distance tool.
Described in tools
MapViewModel: ZoomOutCommand
Draw Objects Add Button Add draw object at random position within the screen area. See Create draw objects programmatically in Map Interaction Client. ProfilesViewModel: AddObjectCmnd
Draw Objects Remove Button Remove selected draw objects. See in Map Interaction Client. ProfilesViewModel: RemoveSelectedObjectsCmnd
Elevation Profile Local Profile Check box Controls the display of the local profile component.
Omit if you are not implementing MariaElevationProfileControl.
ProfilesViewModel: IsElevationProfileActive
Elevation Profile Enhanced Profile Check box Controls the display of the enhanced profile component.
Omit if you are not implementing EnhancedElevationProfileControl.
ProfilesViewModel: IsEnhancedProfileActive
Elevation Profile Enhanced Properties Check box Controls the display of the enhanced profile properties.
Omit if you are not implementing EnhancedElevationProfileControl.
ProfilesViewModel: IsEnhancedPropertiesActive
IsEnhancedProfileActive

Add properties and event handlers to handle the components to ProfilesViewModel. For details see the sample code view model and xaml.

Your application will now look something like this, and you should be able to use the distancetool and add and remove draw objects.

Elevation Profile Preparations

To prepare for interaction between the components and the draw layer and tools, add the following event handlers to ProfilesViewModel:

    public ProfilesViewModel(MariaWindowViewModel mainModel,
                             IMariaDrawObjectLayer mariaDrawObjectLayer)
    {
        . . .
        _mainModel.ToolsLoaded += OnToolsLoaded;
        _drawLayer.LayerInitialized += OnDrawLayerInitialized;
    }

    private void OnToolsLoaded(object sender)
    {
        _distanceTool = (DistanceTool)_mainModel.GetToolByName("DistanceTool");
        if (_distanceTool != null)
        {
            _distanceTool.DistanceUpdateHandler += HandleDistanceUpdate;
        }
    }

    private void OnDrawLayerInitialized()
    {
        _drawLayer.ExtendedDrawObjectLayer.LayerSelectionChanged += ExtendedDrawObjectLayerOnLayerSelectionChanged;
        _drawLayer.ExtendedDrawObjectLayer.LayerChanged += ExtendedDrawObjectLayerOnLayerChanged;
    }

    private void HandleDistanceUpdate(object sender)
    {
            UpdateProfiles();
    }  

    private void ExtendedDrawObjectLayerOnLayerChanged(object sender, DataStoreChangedEventArgs args)
    {
        UpdateProfiles();
    }

    private void ExtendedDrawObjectLayerOnLayerSelectionChanged(object sender, DrawObjectSelectionChangedEventArgs args)
    {
        UpdateProfiles();
    }

    private void UpdateProfiles()
    {
        // Handle update of the different profiles here....
    }

Including the elevation profile controls

Local Elevation Profile, MariaElevationProfileControl

As this profile component is managed through Maria GDK Map Layer, we need to extend the ProfilesViewModel constructor to include the map layer.

    public ProfilesViewModel(
        MariaWindowViewModel mainModel,
        IMariaDrawObjectLayer mariaDrawObjectLayer, 
        IMariaMapLayer mapLayer)
    {
        . . .
        MapLayer = mapLayer;
    }

Add the profile component to the main window xaml, with bindings to the elevation profile view model:

    <mapLayer:MariaElevationProfileControl Grid.Row="2"
                                           MapLayer="{Binding ProfilesViewModel.MapLayer, Mode=OneWay}"
                                           Visibility="{Binding ProfilesViewModel.ElevationProfileVisibility, Mode=OneWay}" />

Running your application, the profile component should be displayed and removed according to the state of the visibility checkbox (ElevationProfileVisibility).

To populate the profile component with the desired profile from the distance tool or selected draw object, add the following code:

    private void UpdateProfiles()
    {
        UpdateLocalProfile();
    }

    private void UpdateLocalProfile()
    {
        if (IsElevationProfileActive)
        {
            if (_mainModel.IsDistanceToolActive)
            {
                if (_distanceTool != null && _distanceTool.Positions.Count >= 2)
                {
                    MapLayer.EleveationProfilePositions = _distanceTool.Positions;
                    return;
                }
            }

            var selected = _drawLayer.ExtendedDrawObjectLayer.SelectedDrawObjectIds.ToArray();
            if (selected.Length == 1)
            {
                var obj = _drawLayer.GetDrawObjectFromStore(selected.First());
                if (obj != null && obj.Points.Length >= 2)
                {
                    var poslist = new List<GeoPos>();
                    foreach (var pt in obj.Points)
                    {
                        poslist.Add(new GeoPos(pt.Latitude, pt.Longitude));
                    }
                    MapLayer.EleveationProfilePositions = poslist;
                    return;
                }
            }
        }

        MapLayer.EleveationProfilePositions = new List<GeoPos>();
    }

The result should be like this:

Local profile from draw objects and distance tool.

Enhanced Elevation Profile, EnhancedElevationProfileControl.

For this control you need to be connected to the EnhancedElevationService. Make sure that your app.config file contains endpoint definitions for the EnahncedElevationService, as described in the Service Configuration section.

Connetion and communication towards the service is managed by the EnhancedElevationServiceEngine class, and profile properties managed through the EnhancedElevationProfileViewModel class, bouth available in the TPG.EnhancedElevation.Profile namespace.

Add properties and perform initialization when the Maria layers are initialized, e.g. in the OnDrawLayerInitialized event handler.

public EnhancedElevationServiceEngine EnhancedElevationServiceEngine { get; set; }
public EnhancedElevationProfileViewModel EnhancedProfileViewModel { get; set; }

. . .

private void OnDrawLayerInitialized()
{
    . . .

    EnhancedElevationServiceEngine = new EnhancedElevationServiceEngine();
    EnhancedElevationServiceEngine.ConnectToElevationService("EnahncedElevationService");
    EnhancedProfileViewModel = new EnhancedElevationProfileViewModel(new GeoUnitsSetting());
    EnhancedProfileViewModel.ProfileParametersChanged += OnEnhancedProfileParametersChanged;
}

private void OnEnhancedProfileParametersChanged(object sender)
{
    UpdateProfiles();
}

Add a user control for the profile properties, see sample project for details.
Add the properties and profile components with bindings to the main window xaml:

<components:EnhancedPropertiesCtrl Grid.Row="1" Grid.Column="1"
                                   Visibility="{Binding ProfilesViewModel.EnhancedPropertiesVisibility, Mode=OneWay}"/>

<profile:EnhancedElevationProfileControl 
        Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
        Name="EnhancedElevationProfileControl" Height="200" 
        Visibility="{Binding ProfilesViewModel.EnhancedProfileVisibility, Mode=OneWay}"
        ShowMinimumAndMaximumElevation="True"
        EnhancedElevationServiceEngine="{Binding ProfilesViewModel.EnhancedElevationServiceEngine}"
        ProfilePossitionInfo="{Binding ProfilesViewModel.EnhancedProfileViewModel.ProfilePossitionInfo, 
                                       Mode=OneWay}" 
        ServiceAddress="{Binding ProfilesViewModel.EnhancedProfileViewModel.ServiceAddress, 
                                 Mode=OneWayToSource}"
        ServiceConnected="{Binding ProfilesViewModel.EnhancedProfileViewModel.ServiceConnected, 
                                   Mode=OneWayToSource}"
        PlotColors="{Binding ProfilesViewModel.EnhancedProfileViewModel.PlotColors}"
        IncludeAverage="{Binding ProfilesViewModel.EnhancedProfileViewModel.IncludeAverage}"
        IncludeMinMax="{Binding ProfilesViewModel.EnhancedProfileViewModel.IncludeMinMax}"
        InterpolationMethod="{Binding ProfilesViewModel.EnhancedProfileViewModel.InterpolationMethod}"
        PixelsPerSample="{Binding ProfilesViewModel.EnhancedProfileViewModel.PixelsPerSample}"
        ResType="{Binding ProfilesViewModel.EnhancedProfileViewModel.ResolutionType}"
        Resolution="{Binding ProfilesViewModel.EnhancedProfileViewModel.Resolution}" />

Then, handle population of the component from the distance tool or selected draw object:

private void UpdateProfiles()
{
    . . .
    UpdateEnhancedProfile();
}

private void UpdateEnhancedProfile()
{
    if (IsEnhancedProfileActive && EnhancedElevationProfileViewModel != null)
    {
        if (_mainModel.IsDistanceToolActive)
        {
            if (_distanceTool != null && _distanceTool.Positions.Count >= 2)
            {
                EnhancedElevationProfileViewModel.ProfilePossitionInfo = new ProfilePossitionInfo
                {
                    Positions = _distanceTool.Positions,
                    TotalDistance = _distanceTool.TotalDistance
                };
                return;
            }
        }

        var selected = _drawLayer.ExtendedDrawObjectLayer.SelectedDrawObjectIds.ToArray();
        if (selected.Length == 1)
        {
            var obj = _drawLayer.GetDrawObjectFromStore(selected.First());
            if (obj != null && obj.Points.Length >= 2)
            {
                var poslist = new List<GeoPos>();
                var dist = 0.0;
                GeoPos? prevPos = null;
                foreach (var pt in obj.Points)
                {
                    var pos = new GeoPos(pt.Latitude, pt.Longitude);
                    if (prevPos.HasValue)
                    {
                        dist += Earth.MetersBetween(prevPos.Value, pos);
                    }

                    poslist.Add(pos);
                    prevPos = pos;
                }

                EnhancedElevationProfileViewModel.ProfilePossitionInfo = new ProfilePossitionInfo
                {
                    Positions = poslist,
                    TotalDistance = dist
                };
                return;
            }
        }

        EnhancedElevationProfileViewModel.ProfilePossitionInfo = new ProfilePossitionInfo();
    }
}

The result should be like this:

Enhanced profile from draw objects and distance tool.

Alternative units, Geo Units Settings

If you want to display the distance or elevation in other than default units, you can define a GeoUnitsSettings property in your view model.

public IGeoUnitsSetting GeoUnitsSettings { get; set; }
. . .
public ElevationProfileViewModel(MariaWindowViewModel mainModel, IMariaMapLayer mapLayer)
{
    GeoUnitsSettings = new GeoUnitsSetting
    {
        DistanceUnit = DistanceUnit.Miles,
        ElevationUnit = ElevationUnit.Feet,
    };

and bind the profile component to it:

<MapLayer:MariaElevationProfileControl 
     . . .
     GeoUnitsSettings="{Binding ElevationProfileViewModel.GeoUnitsSettings, Mode=OneWay}"  />

. . .
<profile:EnhancedElevationProfileControl 
     . . . 
     GeoUnitsSettings="{Binding GeoUnitsSettings, Mode=OneWay}" />

You should now see the profile with alternative units!

You can of course add GUI to change the settings while running as well.

Alternative units

Possition highlighting between profiles and distance tool

Highlighting profile possitions on the distance tool

Moving the mouse pointer horisontally in the profile areas, the corresponding point on the profile is highlighted. This point can be highlihted on the distance tool as well.

In the profile view model, add a property for the distance value:

public double ElevationProfileHighlightDistance
{
    set
    {
        if (_mainModel.IsDistanceToolActive && _distanceTool != null)
        {
            _distanceTool.HighlightDistance = value;
        }
    }
}

and bind the profile components HighlightDistance property to it:

<MapLayer:MariaElevationProfileControl 
     . . .
     HighlightDistance="{Binding ElevationProfileViewModel.ElevationProfileHighlightDistance,
                                 Mode=OneWayToSource}" />
. . .
<profile:EnhancedElevationProfileControl 
     . . . 
     HighlightDistance="{Binding ElevationProfileViewModel.ElevationProfileHighlightDistance,
                                 Mode=OneWayToSource}" />

You should now see the distance highlighted on the distance tool line!

Highlighted Distance from profile

Highlighting distance tool possition on the profile

Implemented for the EnhancedElevationProfileControl only. When hovering the mouse over the distance tool, a mark is shown on the tool line. This point can be marked on the profile.

In the profile view model, add event handler and property for the distance value:

private double _distanceToolHighlightDistance;
. . .

public double DistanceToolHighlightDistance
{
    get { return _distanceToolHighlightDistance; }
    set
    {
        _distanceToolHighlightDistance = value;
        NotifyPropertyChanged(() => DistanceToolHighlightDistance);
    }
}

private void OnToolsLoaded(object sender)
{
    _distanceTool = (DistanceTool)_mainModel.GetToolByName("DistanceTool");
    if (_distanceTool != null)
    {
        _distanceTool.DistanceUpdateHandler += HandleDistanceUpdate;
        _distanceTool.ToolHighlightDistanceUpdate += OnDistanceToolHighlightDistanceUpdate;
    }
}

private void OnDistanceToolHighlightDistanceUpdate(object sender, double distance)
{
    DistanceToolHighlightDistance = distance;
}

and bind the profile components DistanceToolHighlightDistance property to it:

. . .
<profile:EnhancedElevationProfileControl 
     . . . 
     ToolHighlightDistance="{Binding ProfilesViewModel.DistanceToolHighlightDistance, Mode=OneWay}" />

You should now see the possition from the tool marked in the profile!

Highlighted Distance from tool