
Part 2 – Track User’s location in UWP
Guidelines for location-aware apps and tracking user’s location
In Part 1 of the tutorial, we learned how to get an API key for using MapControl, How to add a basic MapControl to a project and how we can show user location on it. In this section, we will learn how can we track user location and show it continuously on the MapControl.
For tracking the user’s location we need to first take a look at the great documentation of Microsoft for location-aware apps that I will mention some of them below belong the tutorial but reading it is worse than nothing.
The important things we should know before starting
The first call of use GeoLocator should be in UI Thread. The first call can be GetGeopositionAsync or register the PositionChanged event.
And the second important thing you should remember is Altitude = 0! I will say what I mean.
Implement, Configure and make a new ViewModel for user location tracking
First of all, I made a class to work easily with the GeoLocator. Create a folder named as Helper and add a class named “GeoLocatorHelper.cs” and here is the code for this file:
using System; using Windows.ApplicationModel.ExtendedExecution; using Windows.Devices.Geolocation; using Windows.Foundation; using Windows.System; using Windows.UI.Core; using Windows.UI.Popups; using Windows.UI.Xaml; class GeoLocatorHelper { public static event EventHandler<Geoposition> LocationFetched; public static event EventHandler<Geocoordinate> LocationChanged; public static bool IsLocationBusy { get; set; } private static ExtendedExecutionSession session; public static Geolocator Geolocator = new Geolocator() { DesiredAccuracy = PositionAccuracy.High, ReportInterval = 500 }; private static async void StartLocationExtensionSession() { try { session = new ExtendedExecutionSession(); session.Description = "Location Tracker"; session.Reason = ExtendedExecutionReason.LocationTracking; var result = await session.RequestExtensionAsync(); if (result == ExtendedExecutionResult.Denied) { //TODO: handle denied session.Revoked += new TypedEventHandler<object, ExtendedExecutionRevokedEventArgs>(ExtendedExecutionSession_Revoked); } } catch { } } private static void ExtendedExecutionSession_Revoked(object sender, ExtendedExecutionRevokedEventArgs args) { try { if (session != null) { session.Dispose(); session = null; } } catch { } } static GeoLocatorHelper() { IsLocationBusy = false; StartLocationExtensionSession(); GetUserLocation(); } public static async void GetUserLocation() { try { if (IsLocationBusy) return; IsLocationBusy = true; var access = await Geolocator.RequestAccessAsync(); if(access != GeolocationAccessStatus.Allowed) { var msg = new MessageDialog("We couldn't access location. Click ok to go to location settings,"); msg.Commands.Add(new UICommand("Ok", async delegate { await Launcher.LaunchUriAsync(new Uri("ms-settings:privacy-location", UriKind.RelativeOrAbsolute)); Window.Current.Activated += Current_Activated; })); msg.Commands.Add(new UICommand("Cancel", delegate { })); var a = await msg.ShowAsync(); } Geolocator.PositionChanged += Geolocator_PositionChanged; Geolocator.StatusChanged += GeoLocate_StatusChanged; var res = Geolocator.GetGeopositionAsync(); if (res != null) res.Completed += new AsyncOperationCompletedHandler<Geoposition>(LocationGetComplete); IsLocationBusy = false; } catch { } } private static void Current_Activated(object sender, WindowActivatedEventArgs e) { if (e.WindowActivationState == CoreWindowActivationState.CodeActivated) { Window.Current.Activated -= Current_Activated; GetUserLocation(); } } private static void Geolocator_PositionChanged(Geolocator sender, PositionChangedEventArgs args) { try { LocationChanged?.Invoke(null, args.Position.Coordinate); } catch (Exception ex) { } } private static void GeoLocate_StatusChanged(Geolocator sender, StatusChangedEventArgs args) { if (args.Status == PositionStatus.NoData || args.Status == PositionStatus.NotAvailable) { GetUserLocation(); } } private static void LocationGetComplete(IAsyncOperation<Geoposition> asyncInfo, AsyncStatus asyncStatus) { try { var res = asyncInfo.GetResults(); LocationFetched?.Invoke(null, res); LocationChanged?.Invoke(null, res.Coordinate); IsLocationBusy = false; } catch { } } }
Now, we need to modify the helper class based on our use case.
The first thing we should modify is the GeoLocator accuracy and the report interval.
public static Geolocator Geolocator = new Geolocator() { DesiredAccuracy = PositionAccuracy.High, ReportInterval = 500 };
GeoLocator accuracy modification
We can easily modify accuracy and report interval by changing values in the line of code mentioned above. If you want to create an app that needs a high accuracy like a navigation app like Windows Maps or WinGo Maps, So you need to use high for DesiredAccuracy but if you want to, for example, find user’s city or country so use default and keep this in mind that your desitions are so effective and will may cause better battery performance while using your app. Keep in mind only use location when you need, In this class because of our need to track the user’s location so I registered the PositionChanged and StatusChanged events, so if you don’t need to track the user’s location continuously, do not register them and only call GetGeopositionAsync.
GeoLocator report interval modification
The report interval is another value that sets how long does the user’s location is valid for your app in milliseconds. If you want to update the user’s location faster you can use small int values like the 500 I used that means it will let me know the new user’s location every 0.5 seconds but keep in mind this value should not be very small because it can easily make your app a battery drainer.
Using GeoLocatorHelper class in our codes
After these changes, we are good to go. Now we should do some changes in our ViewModel to use our helper class.
First, we no longer need the Geolocator property so remove it, also, we no longer need old codes of CallLoadPage function and LoadPage function so remove them too.
For getting started simply add these lines to your ViewModel constructor and create a method for them.
GeoLocatorHelper.LocationChanged += GeoLocatorHelper_LocationChanged; GeoLocatorHelper.LocationFetched += GeoLocatorHelper_LocationFetched;
So, now your ViewModel should look like this :
using System; using System.ComponentModel; using Windows.Devices.Geolocation; using Windows.UI.Core; using Windows.UI.Xaml; namespace UWPMapControl.ViewModel { class MapViewVM : INotifyPropertyChanged { private Geopoint _userloc; public event PropertyChangedEventHandler PropertyChanged; public Geopoint UserLocation { get => _userloc; set { _userloc = value; UpdateUI("UserLocation"); } } CoreDispatcher Dispatcher { get; set; } private void UpdateUI(string PropName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropName)); } public MapViewVM() { Dispatcher = Window.Current.Dispatcher; GeoLocatorHelper.LocationChanged += GeoLocatorHelper_LocationChanged; GeoLocatorHelper.LocationFetched += GeoLocatorHelper_LocationFetched; } private void GeoLocatorHelper_LocationFetched(object sender, Geoposition e) { } private void GeoLocatorHelper_LocationChanged(object sender, Geocoordinate e) { } } }
Now, we have all the things we need to track the user’s location.
Fetch and Update the user’s location
As mentioned above we registered two events of the GeoLocatorHelper class. First one is LocationChanged, and it will notify you that the user’s location changed and the second event is the LocationFetched, that notify you the user’s location after calling GetUserLocation method of the class. This event helps you to get the user’s location after the launch of the application and after clicking refresh location button.
Now we need to code for this events and it seems to be easy so far. We need to only set the UserLocation property with the new values coming from the events. So, we will write these lines for our events.
private async void GeoLocatorHelper_LocationFetched(object sender, Geoposition e) { await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, delegate { UserLocation = e.Coordinate.Point; }); } private async void GeoLocatorHelper_LocationChanged(object sender, Geocoordinate e) { await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, delegate { UserLocation = e.Point; }); }
and also we need to a OneWay mode in our view:
<Grid maps:MapControl.Location="{Binding UserLocation, Mode=OneWay}"> <Ellipse Width="12" Height="12" Fill="DarkGreen"/> <Ellipse Width="60" Height="60" Fill="DarkGreen" Opacity="0.7"/> </Grid>
Now, if you run the application you can see a prompt for accessing location and after accepting it you can see the location pointer will be the point to your location.
But, if you think you’re done your wrong. If you try this app on a mobile or a device with a GPS hardware which is not present in Windows PCs and tablets you will see that location pointer starts moving around the map, but the fix is actually easy. As I mentioned before remember Altitude = 0. For any kind of pointer and element you want to add to the map you should set it’s Altitude = 0, and here is how you can do that:
private async void GeoLocatorHelper_LocationFetched(object sender, Geoposition e) { await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, delegate { var point = e.Coordinate.Point.Position; UserLocation = new Geopoint(new BasicGeoposition() { Latitude = point.Latitude, Longitude = point.Longitude, Altitude = 0 }); }); } private async void GeoLocatorHelper_LocationChanged(object sender, Geocoordinate e) { await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, delegate { var point = e.Point.Position; UserLocation = new Geopoint(new BasicGeoposition() { Latitude = point.Latitude, Longitude = point.Longitude, Altitude = 0 }); }); }
by doing this easy change you will fix the most annoying issue with elements on the map.
Hope you enjoy this tutorial. You can find the source code of this part here.