Back to Basis : La Geolocalisation et la MediaLibrary avec Windows Phone

Hier soir, j’ai eu l’occasion de partager via un petit meeting Lync avec quelques étudiants quelques réflexions et trucs et utiles pratiques concernant la Geolocalisation et la MediaLibrary et je vous propose d’y revenir ici.

La Geolocalisation

Pour rappel, la géolocalisation, c’est le positionnement d’un objet quelconque sur une carte ou un plan. Cet objet peut être de différente sorte, et son positionnement est défini par des coordonnées (ou des points) avec plusieurs propriétés (dont la Latitude et la Longitude qui sont les propriétés les plus utilisées). Dans le SDK Windows Phone, les coordonnées sont matérialisées par un objet qui s’appelle GeoCoordinates.
Pour utiliser la géolocalisation dans votre application Windows Phone, il est indispensable de sélectionner dans le manifest la capacité adequate, à savoir ID_CAP_LOCATION.

Comment s’y prendre pour récupérer sa position courante ?

En utilisant l’objet Geolocator du SDK, on procède ainsi :

var locator = new Geolocator();
var position = await locator.GetGeopositionAsync();

L’objet position renvoyé comprend 2 propriétés essentielles : CivicAddress et Coordinate.
C’est assez pratique si vous êtes dans un scenario ou vous souhaitez pouvoir récupérer la position courante et mettre une adresse physique. Si vous souhaitez en savoir davantage, je vous recommande la MSDN.

Le geocodage (ou geocoding)

Le géocodage c’est un processus permettant de pouvoir obtenir des coordonnées géographiques à partir d’une adresse. C’est généralement très pratique, car, la plupart des systèmes ou APIs se servent des points pour pouvoir effectuer des recherches et le plus souvent les trier par proximité.
Voici un exemple de méthode permettant de Geocoder en récupérer une liste de positions uniquement.

public  async Task<IEnumerable<GeoCoordinate>> Geocode(string address)
{
            var tcs = new TaskCompletionSource<IEnumerable<GeoCoordinate>>();
 
            var query = new GeocodeQuery { SearchTerm = address };
 
            EventHandler<QueryCompletedEventArgs<IList<MapLocation>>> queryQueryCompleted = null;
            queryQueryCompleted += (s, a) =>
            {
                query.QueryCompleted -= queryQueryCompleted;
                var locations = a.Result.Select(location => location.GeoCoordinate).ToList();
                tcs.SetResult(locations);
 
                //Ou alors n'importe quel autre traitement 🙂
            };
 
            query.QueryCompleted += queryQueryCompleted;
            query.QueryAsync();
            return await tcs.Task;
        }

Bon, pas de panique, j’ai déjà eu l’occasion de parler brièvement du TaskCompletionSource. J’explique en terme écrits :
Pour pouvoir « géocoder », je dois créer une requête de géocodage en précisant le terme de ma recherche. Ensuite, j’exécute ma recherche. Avec l’objet GeocodeQuery du sdk, la requête est asynchrone et les résultats sont notifiés dans l’évènement QueryCompleted. Le Task Completion Source me permet donc de renvoyer mes résultats QUE quand j’aurai donc des résutlats (là ou je défini le résultat de ma tache (tcs.SetResult).
Une question que l’on pourrait se poser est : Pourquoi j’ai une liste de résultat et pas qu’un seul ?
C’est tout simplement parce que le terme de votre recherche peut renvoyer plusieurs résultats. Par exemple, si vous rechercher Paris, il y a plusieurs localisation qui correspondent à ce terme. Si vous recherche « Paris, France », votre recherche est réduite (mais aurez quand même un tableau avec une seule ligne comme résultat . Petite précision : les résultats retournés par la requête sont une liste de MapLocation. (dans mon exemple, je n’ai renvoyé que les coordonnées, car, il n’y a que ça qui m’intéressait).

Le Geocodage Inversé

C’est tout simplement le contraire du Geocodage  (elle était facile :D). C’est un moyen de pouvoir mieux identifier des coordonnées en leur donnant une adresse physique (civique). C’est vrai qu’il est plus clair de dire que je me trouve à 6 Rue du Sentier plutôt que de dire Je me trouve à Latitude 48.8681577 et Longitude 2.3455756.
La méthode pour l’utiliser est sensiblement pareille que du geocodage sauf que certains objets changent.

public  async Task<List<string>> ReverseGeocode(double latitude, double longitude)
        {
            var tcs = new TaskCompletionSource<List<string>>();
 
            var query = new ReverseGeocodeQuery();
            query.GeoCoordinate = new GeoCoordinate(latitude, longitude);
            EventHandler<QueryCompletedEventArgs<IList<MapLocation>>> queryQueryCompleted = null;
            queryQueryCompleted += (s, a) => 
            {
                query.QueryCompleted -= queryQueryCompleted;
                
                var mapAddresses = a.Result.Select(location => GetAddressLine(location.Information.Address)).ToList();
                tcs.SetResult(mapAddresses);
            };

            query.QueryCompleted += queryQueryCompleted;
            query.QueryAsync();
            
            return await tcs.Task;
        }

La méthode GetAddressLine est une petite méthode simple que j’ai créée pour récupérer l’adresse formatée.
public string GetAddressLine(MapAddress mapAddress)
{
return string.Format(“{0},{1},{2}”,
mapAddress.Street,
mapAddress.PostalCode,
mapAddress.Country);
}

La cartographie

Julien et Rudy ont déjà pu parler à de nombreuses reprises de l’objet Map, de Binding et autres petites choses les concernant. Je n’y reviendrai donc pas aujourd’hui :-). Si vous souhaitez quand même revenir sur les bases, il y a un très bon article de la MSDN disponible. Tout comme pour la géolocalisation, n’oubliez pas la capacité indispensable (ID_CAP_MAP).
Donc, lors de cette soirée, le point que j’ai pu aborder essentiellement était “les pushpins”. N’hésitez pas à leur donner une apparence qui vous est propre !

wp_ss_20131129_0001

Cela est rendu possible en modifiant le Template du contrôle Pushpin. Et pourquoi pas le style ? Olivier nous l’a expliqué pourquoi sur son blog. Nathalie aussi reviendra bientôt dessus au travers un article assez complet.

Ainsi donc, pour modifier le template de mon pushpin, je créé un ControlTemplate dans mes ressources en prenant le soin de bien préciser la cible (TargetType) :


<ControlTemplate x:Key="PushpinStationTemplate" TargetType="toolkit:Pushpin">
            <Button Style="{StaticResource ButtonNoStyle}" 
                Margin="0" Width="52" Height="60">
                <Grid HorizontalAlignment="Center" VerticalAlignment="Center" Width="52" Height="60">
                    <Image 
				        Source="/Assets/Pushpins/nokia-pushpin-white.png" 
				        HorizontalAlignment="Center" 
				        VerticalAlignment="Center"/>
                        <Image 
				        Source="{Binding Logo}" 
                        HorizontalAlignment="Center" 
                        VerticalAlignment="Center" 
                        Width="30" 
                        Height="30" 
                        Margin="11,10,11,20"/>  
                </Grid>
            </Button>
        </ControlTemplate>

Ce que l’on oublie aussi de temps à autre, c’est qu’il est tout à fait possible de créer des pushpins animés.

<ControlTemplate x:Key="PushpinMeTemplate" TargetType="toolkit:Pushpin">
            <Grid Width="100" Height="100">
                <Grid.Triggers>
                    <EventTrigger RoutedEvent="Grid.Loaded">
                        <BeginStoryboard>
                            <Storyboard AutoReverse="True" RepeatBehavior="Forever">
                                <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Width)" Storyboard.TargetName="ellipse">
                                    <EasingDoubleKeyFrame KeyTime="0" Value="15"/>
                                    <EasingDoubleKeyFrame KeyTime="0:0:2" Value="100"/>
                                </DoubleAnimationUsingKeyFrames>
                                <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="ellipse">
                                    <EasingDoubleKeyFrame KeyTime="0" Value="15"/>
                                    <EasingDoubleKeyFrame KeyTime="0:0:2" Value="100"/>
                                </DoubleAnimationUsingKeyFrames>
                                <PointAnimation Duration="0" To="0.5,0.5" Storyboard.TargetProperty="(UIElement.RenderTransformOrigin)" Storyboard.TargetName="ellipse"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                </Grid.Triggers>
                <Ellipse x:Name="ellipse" Fill="#ffb1ca00" 
                     HorizontalAlignment="Center" Height="100" Stroke="Black" 
                     VerticalAlignment="Center" Width="100" Opacity="0.4"
                     RenderTransformOrigin="0 0"
                     />
                <Ellipse 
                Fill="#ffb1ca00" 
                HorizontalAlignment="Center" Height="15" 
                Stroke="Black" VerticalAlignment="Center" Width="15"/>
            </Grid>
        </ControlTemplate>

Attention à ne pas en abuser et à faire en sorte que vos animations soient efficaces, justifiées.
Le pushpin juste ci-dessus a un point central qui grossi et reprend sa forme initiale. Ça peut être pratique pour :
– représenter ma position courante et faire en sorte que l’utilisateur ne la perde pas de vue (on peut aussi ne pas faire d’animation et utiliser un pushpin différent des principaux  )
– animer l’arrivée des pushpins ou alors, les animer de temps à autre.

Les possibilités sont immenses !

Après avoir créé vos templates, pour les appliquer à vos pushpins, vous procédez ainsi :

<toolkit:Pushpin 
                            x:Name="MyPushpin" 
                            Template="{StaticResource PushpinMeTemplate}"
                            GeoCoordinate="48.8576,2.337999" />
 
                        <toolkit:Pushpin 
                            x:Name="SamplePushpin" 
                            Template="{StaticResource PushpinStationTemplate}"
                            GeoCoordinate="48.8576,2.40" />

La MediaLibrary

Quand on cherche à créer des applications, on est souvent à la recherche de données distantes alors que … bien souvent, on en dispose de beaucoup en local ! C’est le cas de vos photos, vos playlists, vos musiques (pas encore vos vidéos). Si vous souhaitez les utiliser dans une application Windows Phone, il faudra absolument ne pas oublier les capacités nécessaires ID_CAP_MEDIALIB_[AUDIO|PHOTO|PLAYLIST].

Pour récupérer les photos stockées dans votre téléphone :

using (var library = new MediaLibrary())
            {
                var images = library.Pictures;
            }

Pour récupérer vos musiques, il faut appeler la propriété Songs de l’objet library :

library.Songs

et pour vos playLists

library.Playlists

Avec les méthodes exposées, vous avez la possibilité de pouvoir sauvegarder Photos ou Musique, mais avec quelques particularités. Concernant les musiques, il faut absolument que la musique que vous souhaitez sauvegarder dans votre library soit stockée DANS l’isolated storage de votre application. Ce n’est pas possible de télécharger une musique distante. Il en est de même pour les images de vos albums ou artistes (pour les métadonnées de la musique).
Pour les images, c’est un peu différent, vous avez la possibilité de passer dans votre méthode un stream, et cela peut être un stream distant :-).

Voilà donc pour la petite piqûre rapide de rappel en espérant que ça vous serve ! Et comme un exemple est souvent beaucoup plus parlant, voici un exemple téléchargeable reprenant les points abordés dans cet article !

Bon développement et A bientôt !