Geolocation mit Windows 8

Als nächstes steht auf meiner Liste der Sensoren der Geolocation Sensor. Um hier nicht mit dem Emulator arbeiten zu müssen, installiere ich auf meinem Surface Tablet den Remote Debugger. Eine Anleitung dafür findet man auf dem Blog meines Kollegen Tim Heuer. Das Thema Geolocation ist ebenfalls in der Dokumentation vorhanden. Wenn auch nicht ganz so ausführlich, dafür aber extrem einfach zum nachbauen.

Dann kann unsere Reise ja auch schon beginnen und wir starten damit ein neues Projekt anzulegen. Wie im Beispiel zuvor lege ich dafür eine C# XAML Applikation an und starte mit dem Blank Solution Template.

Wir setzen einen Button auf die MainPage.xaml über den Designer und geben ihm den Pregnanten namen “btnTracking” und hinterlegen einen entsprechenden Click Handler.

image

Und das gleiche machen wir ein weiteres mal um das Tracking anzuhalten. Nun brauchen wir noch 2 Elemente. Eine Liste mit dem Status des Gerätes. Und eine weitere Liste mit den Positionen. Also fügen wir zwei ListView Controls unserer BlankPage.xaml hinzu. Das fertige XAML sieht also wie folgt aus:

 <Page
    x:Class="GeolocationSimple.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:GeolocationSimple"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Button Name="btnStartTracking" Content="Start Tracking" HorizontalAlignment="Left" Margin="36,32,0,0" 
                VerticalAlignment="Top" Height="81" Width="171" Click="btnTracking_Click"/>
        <Button Name="btnStopTracking" Content="Stop Tracking" HorizontalAlignment="Left" Height="79" 
                Margin="226,34,0,0" VerticalAlignment="Top" Width="171" Click="btnStopTracking_Click"/>
        <ListView Name="listStatus" HorizontalAlignment="Left" Height="554" Margin="78,167,0,0" 
                  VerticalAlignment="Top" Width="532"/>
        <ListView Name="listLocation" HorizontalAlignment="Left" Height="554" Margin="682,167,0,0" 
                  VerticalAlignment="Top" Width="596"/>
    </Grid>
</Page>

 

Das dazugehörige Codebehind File sollte also nun 2 Eventhandler haben und gut.

 using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238

namespace GeolocationSimple
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
        }

        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {

        }

        private void btnTracking_Click(object sender, RoutedEventArgs e)
        {

        }

        private void btnStopTracking_Click(object sender, RoutedEventArgs e)
        {

        }
    }
}

Als nächstes wollen wir uns dem Geolocator widmen.

Dazu legen wir ein Feld an, das nach dem InitializeComponent Vorgang gesetzt wird.

     …
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        private Windows.Devices.Geolocation.Geolocator _location;

        public MainPage()
        {
            this.InitializeComponent();
            _location = new Windows.Devices.Geolocation.Geolocator();
        }
    …

Und schon haben wir den Zugriff auf den Geolocator. Als erstes wollen wir nun den Status des GPS Moduls abfragen. Als erstes setzen wir die beiden Buttons und Ihren entsprechenden State in der OnNavigatedTo Methode die der Einstiegspunkt für unsere App ist.

         /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            btnStartTracking.IsEnabled = true;
            btnStopTracking.IsEnabled = false;
        }

Als nächstes wollen wir uns dem btnStart zuwenden und die entsprechende Logik hineterlegen.

 

         private void btnTracking_Click(object sender, RoutedEventArgs e)
        {
            _location.PositionChanged += new TypedEventHandler<Windows.Devices.Geolocation.Geolocator, 
                                         Windows.Devices.Geolocation.PositionChangedEventArgs>(OnPositionChanged);
            _location.StatusChanged += new TypedEventHandler<Windows.Devices.Geolocation.Geolocator, 
                                         Windows.Devices.Geolocation.StatusChangedEventArgs>(OnStatusChanged);

            btnStartTracking.IsEnabled = false;
            btnStopTracking.IsEnabled = true;
        }
 

Als erstes fügen wir zwei neue Delegaten hinzu. Die setzen wir auf die Events des Geolocators. Die beiden Events an denen wir interessiert sind, ist zum einen die Position und zum anderen den Status des Gerätes. Ich habe auch hier wieder den vollständigen Namespace ausgeschrieben. So dass man Ihn leichter finden kann.

In der OnStatusChanged Methode erstellen holen wir uns den CoreDispatcher der in Windows.UI.Core lebt. Er sorgt dafür, dass wir wann immer sich der Status ändert wir das entsprechend unterscheiden können. Dafür fragen wir den Status der Eventargs e ab. Cool daran ist, dass wir mit dem Switch-Case Snippet und dem args.Status sowie einem Shift+Enter das komplette Statement von Visual Studio automatisch ausgespuckt bekommen.

         private async void OnStatusChanged(Windows.Devices.Geolocation.Geolocator sender, 
                                           Windows.Devices.Geolocation.StatusChangedEventArgs args)
        {
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => 
            {
                switch (args.Status)
                {
                    case Windows.Devices.Geolocation.PositionStatus.Disabled:
                        break;
                    case Windows.Devices.Geolocation.PositionStatus.Initializing:
                        break;
                    case Windows.Devices.Geolocation.PositionStatus.NoData:
                        break;
                    case Windows.Devices.Geolocation.PositionStatus.NotAvailable:
                        break;
                    case Windows.Devices.Geolocation.PositionStatus.NotInitialized:
                        break;
                    case Windows.Devices.Geolocation.PositionStatus.Ready:
                        break;
                    default:
                        break;
                }
            });
        }

Die Liste listStatus wollen wir nun mit den entsprechenden Staten befüllen.

         private async void OnStatusChanged(Windows.Devices.Geolocation.Geolocator sender, 
                                           Windows.Devices.Geolocation.StatusChangedEventArgs args)
        {
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => 
            {
                switch (args.Status)
                {
                    case Windows.Devices.Geolocation.PositionStatus.Disabled:
                        listStatus.Items.Add("Diabled");
                        break;
                    case Windows.Devices.Geolocation.PositionStatus.Initializing:
                        listStatus.Items.Add("Initializing");
                        break;
                    case Windows.Devices.Geolocation.PositionStatus.NoData:
                        listStatus.Items.Add("No data");
                        break;
                    case Windows.Devices.Geolocation.PositionStatus.NotAvailable:
                        listStatus.Items.Add("Not available");
                        break;
                    case Windows.Devices.Geolocation.PositionStatus.NotInitialized:
                        listStatus.Items.Add("Not initialized");
                        break;
                    case Windows.Devices.Geolocation.PositionStatus.Ready:
                        listStatus.Items.Add("Ready");
                        break;
                    default:
                        break;
                }
            });
        }

Als nächstes wollen wir nun die eigentlichen Werte abfragen.

         private async void OnPositionChanged(Windows.Devices.Geolocation.Geolocator sender, 
                                            Windows.Devices.Geolocation.PositionChangedEventArgs args)
        {
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
                Windows.Devices.Geolocation.Geoposition pos = args.Position;

                var latitude = pos.Coordinate.Latitude.ToString();
                var longitude = pos.Coordinate.Longitude.ToString();

                listLocation.Items.Add("lat:" + latitude + ", " + "long" + longitude);
            });
        }

Auch hier verwenden wir wieder den CoreDispatcher und fragen in diesem Szenario die Position ab, die sich in den args des PostionChangedEvents verbergen. Wir benötigen die Longitude und die Latitude. Die wir der liste hinzufügen.

Noch eben schnell den Button btnStopTracking editiert und dann sind wir auch schon fertig.

         private void btnStopTracking_Click(object sender, RoutedEventArgs e)
        {
            _location.PositionChanged -= new TypedEventHandler<Windows.Devices.Geolocation.Geolocator, 
                                             Windows.Devices.Geolocation.PositionChangedEventArgs>(OnPositionChanged);
            _location.StatusChanged -= new TypedEventHandler<Windows.Devices.Geolocation.Geolocator, 
                                           Windows.Devices.Geolocation.StatusChangedEventArgs>(OnStatusChanged);

            btnStartTracking.IsEnabled = true;
            btnStopTracking.IsEnabled = false;
        }

 

Wir lassen die Applikation laufen. Und auch hier kommt das Security Model zum tragen. Deshalb wie schon zu vor in der CameraCaptureApp stellen wir die entsprechende Berechtigung der App im Manifest ein.

image

Die fertige App sieht nun also wie folgt aus:

image

Wir wollen das ganze jetzt einmal auf dem Slate testen. Dazu baue ich eine Remote Verbindung auf das Slate auf.

image

Aus der Liste der Maschinen selektiere ich das Slate.

image

Beim ersten verbinden, muss eine Developer Lizenz gestartet werden. Und schon sollte Ihre App auf dem Remote Gerät laufen.

Das ist zwar schon ganz nett, aber jetzt wollen wir unsere Position auch auf einer Karte anzeigen. Dazu verwende ich das Bing Maps SDK. Dazu wird ein entsprechender Key benötigt. Dazu fügen wir zu unserem Projekt eine Referenz über Add Referzen hinzu.

image image

Unter Windows Extensions fügt man einfach eine Referenz auf Bing Maps für C# hinzu. Unter umständen muss man nun im Configuration Manager die Target Plattform ändern. In meinem Fall ändere ich das Target auf x86 um.

image

Als nächstes fügen wir die Bing Maps dem Grid hinzu.

image

Nun muss nur noch der Key eingetragen werden. Das geht über das Crediantials Attribute. Der Vollständige XAML Code sieht so aus:

 <Page
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:GeolocationSimple"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:Maps="using:Bing.Maps"
    x:Class="GeolocationSimple.MainPage"
    mc:Ignorable="d">

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Button x:Name="btnStartTracking" Content="Start Tracking" HorizontalAlignment="Left" Margin="36,32,0,0" 
                VerticalAlignment="Top" Height="81" Width="171" Click="btnTracking_Click"/>
        <Button x:Name="btnStopTracking" Content="Stop Tracking" HorizontalAlignment="Left" Height="79" 
                Margin="226,34,0,0" VerticalAlignment="Top" Width="171" Click="btnStopTracking_Click"/>
        <ListView x:Name="listStatus" HorizontalAlignment="Left" Height="188" Margin="1054,136,0,0" 
                  VerticalAlignment="Top" Width="285"/>
        <ListView x:Name="listLocation" HorizontalAlignment="Left" Height="254" Margin="1059,371,0,0" 
                  VerticalAlignment="Top" Width="280"/>
        <Maps:Map x:Name="bingMap" Credentials="BingMapsKey" HorizontalAlignment="Left" Margin="36,136,0,0" 
                  VerticalAlignment="Top" Height="489" Width="1018"/>
    </Grid>
</Page>

Und nun wollen wir unsere Positionsdaten auf die Map bringen. Dazu gehen wir zurück in das Code Behind File.

Als erstes müssen wir die Geolocation Informationen von Windows 8 auslesen und in Bing Koordinaten konvertieren. Dann einen entsprechenden Pushpin anlegen. Als nächstes können wir die Position des Pushpins bestimmen. Und schließlich zeichnen wir den Pushpin auf die Karte. Als letzter Schritt Zoomen wir in der Karte auf die Richtige Position. Der Code dafür sieht wie folgt aus.

         private async void OnPositionChanged(Windows.Devices.Geolocation.Geolocator sender, 
                                             Windows.Devices.Geolocation.PositionChangedEventArgs args)
        {
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
                //Retrieve the Geolocation from Geolocator
                Windows.Devices.Geolocation.Geoposition pos = args.Position;

                //Create a new pushpin
                Bing.Maps.Pushpin pin = new Bing.Maps.Pushpin();
                pin.Text = "Meine Position";
                //Get the geolocation from windows 8 and convert to bing maps location
                Bing.Maps.Location location = new Bing.Maps.Location(pos.Coordinate.Latitude, 
                                                                               pos.Coordinate.Longitude);
                //Set the position of the pushpin
                Bing.Maps.MapLayer.SetPosition(pin, location);
                //Add the pushpin
                bingMap.Children.Add(pin);
                //Zoom Bing Maps to location
                bingMap.SetView(location, 18.0f);

                //Convert the postiong for list
                var latitude = pos.Coordinate.Latitude.ToString();
                var longitude = pos.Coordinate.Longitude.ToString();
                //Set the Value in list
                listLocation.Items.Add("lat:" + latitude + ", " + "long" + longitude);
            });
        }
    }

Die fertige App sieht dann wie folgt aus:

image

Und Fertig! Nun könnte man anfangen richtig Spaß zu haben und eigene Shapes auf die Karte zu zeichnen. In dieser Blog Post von Piotr Walat gibt es eine schöne Beschreibung dazu.