App-Entwicklung für Windows Phone 8–Part 2

Von Maximilian Sachs (Gastblogger) Autor Feed 22. January 2013 07:00

Dies ist der 2. Teil unserer Serie, in der wir das GeoFotoalbum App entwickeln. Was für diesen Artikel im Fokus steht, findet Ihr im folgenden Abschnitt:

Inhalt des Artikels

  • Auslesen der Geodaten von gespeicherten Bildern auf dem Telefon
  • Berechnung der Entfernung des Geräts zum Entstehungsort der Bilder
  • Erweitern unserer Liste, sodass sie die Entfernung ebenfalls ausgegeben wird

 

clip_image004

 

Auslesen der Geodaten

Wir beginnen mit dem Auslesen der Geodaten aus dem Bild. Da diese als Exif-Daten in der Bilddatei gespeichert sind, müssen wir uns zuerst eine Möglichkeit schaffen, genau diese Art von Daten auszulesen. Da dies jedoch nicht wirklich trivial ist und auch für die erste App vielleicht etwas zu weit führen würde, werden wir hier auf eine Bibliothek zurückgreifen, die uns den Zugriff deutlich erleichtert.

Wir haben uns dabei für die hervorragende ExifLib entschieden, mittels derer wir unser Ziel möglichst einfach erreichen können, ohne selbst irgendeine Art von Daten per Hand auslesen zu müssen. Die Bibliothek stammt von Simon McKenzie und ist hier zu finden: http://www.codeproject.com/Articles/36342/ExifLib-A-Fast-Exif-Data-Extractor-for-NET-2-0

Der Einfachheit halber werden wir in diesem Blogeintrag auch keine Klassenbibliothek erstellen (denn die Mitgelieferte ist leider in der Form nicht mit Windows Phone 8 kompatibel), sondern werden uns für dieses Beispiel darauf beschränken, einfach die beiden Dateien ExifReader.cs und ExifTags.cs in die Projektmappe zu ziehen, sodass sie über unserer MainPage.xaml-Datei aufscheinen.

clip_image006

Wie Ihr euch vielleicht erinnert, haben wir uns in Teil 1 ein TODO in der Klasse Foto hinterlassen (Einlesen von Längen und Breitengrad), welchem wir uns nun zuwenden.

Anstelle des Kommentars fügen wir folgende Zeilen Code ein, um mithilfe unserer gerade hinzugefügten Bibliothek die Metadaten jedes Bildes auszulesen und uns Längengrad und Breitengrad pro Foto zu merken:

ExifReader reader = new ExifReader(picture.GetImage());
double[] tmplat, tmplong;
if (reader.GetTagValue<double[]>(ExifTags.GPSLatitude, out tmplat) &&
    reader.GetTagValue<double[]>(ExifTags.GPSLongitude, out tmplong))
{
  laengengrad = tmplong[0] + tmplong[1] / 60 +
      tmplong[2] / (60 * 60);
  breitengrad = tmplat[0] + tmplat[1] / 60 +
      tmplat[2] / (60 * 60);

  string tmp;
  if ((reader.GetTagValue<string>(ExifTags.GPSLongitudeRef, out tmp) &&
      tmp == "W"))
  {
    laengengrad *= -1;
  }

  if ((reader.GetTagValue<string>(ExifTags.GPSLatitudeRef, out tmp) &&
      tmp == "S"))
  {
    breitengrad *= -1;
  }
}

 

Berechnen der Distanzen

Als nächstes steht die Berechnung der Distanzen auf dem Plan. Um dies zu erreichen, erstellen wir eine weitere private Member-Variable der Klasse Foto, welche die Entfernung unseres Geräts zu dem jeweiligen Entstehungsort des Fotos beinhaltet.

private double entfernung = 0;

Auch hier müssen wir leider in den sauren Apfel beißen und ein weiteres Problem lösen, welches eigentlich für unsere erste App uninteressant ist – die Berechnung der Entfernung des Bildes. Wie bereits bei dem Auslesen der Exif-Daten, werden wir hier erneut auf schon bestehenden Code zurückgreifen. Ein weiteres Mal wählten wir dabei einen CodeProject-Artikel als Quelle, dieser stammt von Gary Dryden und ist hier zu finden:

http://www.codeproject.com/Articles/12269/Distance-between-locations-using-latitude-and-long

Keine Sorge, dies sollte nun auch schon das letzte Stück Code gewesen sein, welches wir von äußeren Quellen verwenden. Dieses Bisschen war jedoch leider notwendig, um unserer App eine interessante Funktion verleihen zu können, anhand welcher wir Euch die neuen Funktionen von Windows Phone 8 möglichst eindrucksvoll demonstrieren können!

Wir erstellen eine neue Klasse DistanceBetweenLocations und kopieren den folgenden Code hinein:

using System;

public class DistanceBetweenLocations
{
  public static double Calc(double Lat1,
                double Long1, double Lat2, double Long2)
  {
    double dDistance = Double.MinValue;
    double dLat1InRad = Lat1 * (Math.PI / 180.0);
    double dLong1InRad = Long1 * (Math.PI / 180.0);
    double dLat2InRad = Lat2 * (Math.PI / 180.0);
    double dLong2InRad = Long2 * (Math.PI / 180.0);

    double dLongitude = dLong2InRad - dLong1InRad;
    double dLatitude = dLat2InRad - dLat1InRad;

    // Intermediate result a.
    double a = Math.Pow(Math.Sin(dLatitude / 2.0), 2.0) +
               Math.Cos(dLat1InRad) * Math.Cos(dLat2InRad) *
               Math.Pow(Math.Sin(dLongitude / 2.0), 2.0);

    // Intermediate result c (great circle distance in Radians).
    double c = 2.0 * Math.Asin(Math.Sqrt(a));

    // Distance.
    // const Double kEarthRadiusMiles = 3956.0;
    const Double kEarthRadiusKms = 6376.5;
    dDistance = kEarthRadiusKms * c;

    return dDistance;
  }

  public static double Calc(string NS1, double Lat1, double Lat1Min,
         string EW1, double Long1, double Long1Min, string NS2,
         double Lat2, double Lat2Min, string EW2,
         double Long2, double Long2Min)
  {
    double NS1Sign = NS1.ToUpper() == "N" ? 1.0 : -1.0;
    double EW1Sign = EW1.ToUpper() == "E" ? 1.0 : -1.0;
    double NS2Sign = NS2.ToUpper() == "N" ? 1.0 : -1.0;
    double EW2Sign = EW2.ToUpper() == "E" ? 1.0 : -1.0;
    return (Calc(
        (Lat1 + (Lat1Min / 60)) * NS1Sign,
        (Long1 + (Long1Min / 60)) * EW1Sign,
        (Lat2 + (Lat2Min / 60)) * NS2Sign,
        (Long2 + (Long2Min / 60)) * EW2Sign
        ));
  }
}

Nun müssen wir auf die Änderung des Standortes unseres Telefons hören, um stets die aktuelle Distanz anzuzeigen.

Diesen Code fügen wir der Klasse Foto hinzu um bei Veränderung der Position benachrichtigt zu werden:

#region Standortsbestimmung
public void updatePosition(GeoCoordinate position)
{
  entfernung = DistanceBetweenLocations.Calc(position.Latitude,
    position.Longitude, breitengrad, laengengrad);

  RaisePropertyChanged("Entfernung");
}
#endregion

Die Methode updatePosition soll nun jedes Mal aufgerufen werden, wenn sich die Position verändert hat – sobald dies passiert, aktualisieren wir direkt alle Entfernungswerte unserer Bilder und wissen so immer ganz genau, welches Bild sich am nächsten zu unserem Gerät befindet.

clip_image002 Exkurs: Regions? Bei Regions handelt es sich – wie der Name bereits vermuten lässt – um Regionen in Eurem Code. Der Einsatz dieser Direktiven hilft dabei den Code zu strukturieren und hat den gewaltigen Vorteil, dass Ihr bestimmte Bereiche Eures Codes einfach komplett zusammenfalten („Code-Folding“) könnt, um mehr Platz zu schaffen und den Fokus auf die momentan wichtigen Bereiche zu richten.

Damit dies nun geschieht, müssen wir einige Änderungen an dem Code-Behind-File der MainPage vornehmen (MainPage.xaml.cs).

Zuerst fügen wir der Klasse eine neue Member-Variable hinzu, den sogenannten GeoCoordinateWatcher:

private GeoCoordinateWatcher watcher;

Damit wir diesen nun einsetzen können, benötigen wir eine Methode, die uns den Watcher initialisiert und ihm eine Möglichkeit verleiht, den Entfernungswert unserer Bilder zu verändern (indem wir die updatePosition-Methode aufrufen). Dazu benutzen wir folgenden Code, welcher genau das für uns tut:

public void InitializeGeoWatcher()
{
  watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High)
  {
    MovementThreshold = 20
  };

  watcher.PositionChanged += this.watcher_PositionChanged;
  watcher.Start();
}

private void watcher_PositionChanged(object sender,
  GeoPositionChangedEventArgs<GeoCoordinate> e)
{
  foreach (Foto foto in fotos)
  {
    foto.updatePosition(e.Position.Location);
  }
}

Dem Konstruktor der MainPage fügen wir als letzte Zeile folgenden Code hinzu:

InitializeGeoWatcher();

Nun ist das Ziel erreicht. Wir initialiseren den von Windows Phone bereitgestellten Service, welcher uns benachrichtigt, sobald sich die Position des Geräts verändert. Sobald dies nun geschieht, lassen wir die Fotos ihre Entfernung selbstständig neu bestimmen, indem wir ihre Update-Methode mit der neuen Position aufrufen.

Damit nun jedoch auch noch die Darstellung funktioniert, ist noch ein bisschen Arbeit zu tun. Hierfür erstellen wir die Eigenschaft Entfernung, welche uns später für die textuelle Ausgabe im User Interface unserer Liste dient.

public string Entfernung
{
  get
  {
    if(entfernung < 10.00) // Ausgabe in m
      return (entfernung * 1000).ToString("0") + " m";
    else // Ausgabe in km
      return entfernung.ToString("0") + " km";
  }
}

Erweitern der Liste

Das Template unserer Liste ändern wir wie folgt:

<phone:LongListSelector.ItemTemplate>
  <DataTemplate>
    <Grid Margin="2 5 2 5">
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition />
      </Grid.ColumnDefinitions>
      <Image Grid.Column="0" Source="{Binding Vorschaubild}" 
              HorizontalAlignment="Center" VerticalAlignment="Center" />
      <TextBlock Grid.Column="1" Text="{Binding Entfernung}" Margin="10 0 0 0" 
                  HorizontalAlignment="Left" VerticalAlignment="Center" />
      <TextBlock Grid.Column="2" Text="{Binding Name}" Margin="10 0 0 0" 
                  HorizontalAlignment="Left" VerticalAlignment="Center" />
    </Grid>
  </DataTemplate>
</phone:LongListSelector.ItemTemplate>

Wie bereits angekündigt, fügen wir hier im Kern eigentlich nur ein weiteres Feld hinzu, um die neu errechnete Entfernung zusätzlich anzeigen zu können.

Nun wird es wirklich langsam Zeit für einen ersten Test unseres neuen Codes, oder?

Dummerweise gibt es da jedoch noch ein kleines Problem, denn die Fotos, die sich standardmäßig auf unserem Gerät befinden, besitzen leider keine Geodaten und sind somit für unsere App uninteressant. Glücklicherweise lässt sich dieses Problem jedoch einfach beheben:

  • Entweder wir schießen einfach ein paar Bilder mit der Kamera des Geräts selbst
  • Oder wir erstellen einige künstliche Bilder im Emulator – dies zeigen wir Euch im Folgenden:

Um virtuell Fotos schießen zu können, öffnen wir den Emulator und klicken auf die 2 Pfeile nach rechts. clip_image008

Es öffnet sich nun ein Dialog, welcher einige zusätzliche Tools beinhaltet, dort verwenden wir den Reiter Ortung. Durch Klicken auf die Karte wird unsere Position auf die angezeigte Stelle gesetzt.

Jetzt öffnen wir im Emulator die Kamera App und machen Fotos, wobei wir nach jedem Foto unsere Position verändern und ein paar Sekunden warten.

Wenn wir nun unsere App öffnen würden, müsste alles funktionieren, richtig? Leider falsch, denn wir würden immer noch nur „0 m“ Abstände sehen.

Dies hat 2 Gründe:

  • Wir müssen unserer App noch die Berechtigungen für den Zugriff auf die Geodaten erteilen.
    Dies geschieht - wie auch schon in Teil 1 - in der WMAppManifest.xml. Dabei ist „ID_CAP_LOCATION“ die Berechtigung, die wir zulassen müssen.
  • Unser User Interface wird momentan noch nicht vom Code benachrichtigt, wenn sich der Wert ändert. Wie wir bereits in Teil 1 in einem Exkurs kurz angerissen haben, sind dafür spezielle Interfaces notwendig, welche implementiert werden müssen, um dann die korrekten Events feuern zu können, welche schlussendlich das Interface über die Änderung informieren. Dafür ist nicht übermäßig viel Code notwendig und den zeigen wir Euch im Folgenden:

Wir beginnen direkt damit unsere Foto-Klasse das INotifyPropertyChanged-Interface implementieren zu lassen. Dafür erweitern wir die Klassendefinition auf von public class Photo auf public class Photo : INotifyPropertyChanged und fügen der Klassen im Weiteren folgenden Code hinzu:

#region PropertyChanged
public event PropertyChangedEventHandler PropertyChanged;

private void RaisePropertyChanged(string propertyName)
{
  if (PropertyChanged != null)
    PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion

Am Ende der Methode updatePosition fügen wir folgende Zeile ein:

RaisePropertyChanged("Entfernung");

Dies sorgt nun dafür, dass die Bindings im User Interface, welche auf das Property „Entfernung“ hören, direkt benachrichtigt werden, sobald RaisePropertyChanged aufgerufen wird (und dies tun wir natürlich sobald sich die Entfernung ändert).

Wenn wir nun die Position im Emulator über den Ortung-Reiter ändern, sehen wir, dass sich die Entfernung der Bilder anpasst und die momentane Entfernung des Aufenthaltsortes zu dem Ursprungsort des Bildes wiederspiegelt. Das Ganze sollte sich in der App dann als akkurate Wiedergabe der Entfernungen wiederspiegeln und in etwa so aussehen:

clip_image009

Das ist doch schon einmal nicht schlecht, oder? Smile Im nächsten Teil werden wir dann den Schwerpunkt auf den Umgang mit Live-Tiles legen, um auch bei nichtaktiver App interessante Informationen anzeigen zu können!

 

Blogeinträge und Ressourcen

Am Ende dieses Blogeintrags verweisen wir noch einmal auf eine Liste der bisher veröffentlichten Blogeinträge:

und auf den Link zum Download der aktuellen Projektversion (beinhaltet den kompletten Stand der App bis zum Ende dieses Eintrags):

 

Zu guter Letzt

Ein weiteres Mal bedanken wir uns für Eure Aufmerksamkeit und das Durchhalten bis zum Schluss (das nächste Mal versuchen wir wirklich, uns etwas kürzer zu halten Smile with tongue out ) und würden uns sehr freuen, euch auch beim nächsten Teil wieder begrüßen zu dürfen! In diesem Sinne: Danke noch einmal und bis zum nächsten Mal! Smile

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading

Datenschutzhinweis: Sie stimmen durch "Kommentar speichern" der Speicherung Ihrer Angaben durch Microsoft Österreich für die Beantwortung der Anfrage zu. Sie erhalten dadurch keine unerwünschten Werbezusendungen. Ihre Emailadresse wird auf Ihren Wunsch dazu verwendet Sie über neue Kommentare zu informieren.

Microsoft respektiert den Datenschutz. Datenschutz & Cookies

Entwickler Wettbewerbe:

Wettbewerbe

Aktuelle Downloads

Visual Studio Downloads
 
Windows Azure Free Trial
Instagram
CodeFest.at on Facebook

Datenschutz & Cookies · Nutzungsbedingungen · Impressum · Markenzeichen
© 2013 Microsoft. Alle Rechte vorbehalten · BlogEngine.NET 2.7.0.0 · Diese Website wird für Microsoft von atwork gehostet.
powered by atwork