none
Bug Report: WP7 control fails to display tiles when tracking location RRS feed

  • Question

  • When using GPS to continuously reposition the WP7 map control with the device location at the center of the screen, tile updating can fail.

    Scenario:

    1. Mango application with Version 7.0.0.0 Microsoft.Phone.Controls.Maps.dll
    2. Map is being continuously repositioned using the 1Hz GPS heartbeat.
    3. Problem occurs at walking speeds, ferry boat speeds, and car speeds.
    4. Problem occurs with or without additional tile overlays above the Bing base maps.
    5. Problem occurs with either road or aerial base layer.

    Initially the map is drawn correctly.  As the user location changes incrementally to an area outside previously downloaded tiles, either: (a) more tiles are not fetched, or (b) tiles are fetched but not displayed, resulting in a fuzzy display of previously cached lower zoom layers.

    BUT!  If any manual user touch activity happens on the map (panning or zooming), then the missing tiles are immediately drawn. 

    I'm guessing that the "zoom out, then pan to a new location, and then zoom back in" behavior of most route finding applications prevents expression of this problem, since a change in zoom seems to always trigger correct display of all layers.

    Also, I am unable to duplicate this problem on the emulator.  It only shows up on actual devices (Radar and HD7). 

    Perhaps the problem is related to free processor time?  At a 1Hz GPS update rate, the phone control perhaps contains some logic which prevents downloads when a view change is in progress, and since the view is continuously changing further tile downloads are never allowed?

     

    • Moved by Ricky_Brundritt Saturday, March 10, 2012 12:24 PM (From:Bing Maps: Map Control and Web services Development)
    Sunday, November 27, 2011 8:15 AM

Answers

  • The map rendering itself is completed by a set of MultiScaleImage controls. These controls give the very smooth and fluid look but are quite resource intensive. For one in the Silverlight web control they stop working if the control is not in view (minimised, out screen, different tab etc). Additionally a lot of work has been done to reduce it's hunger for tiles by oveririding the core method to get a tiles URI.

    In the context of the phone I have a commercial app - Transhub - that is doing something simliar to you, once on a train/bus/ferry you can track your position in real time, centre the map on your location unless you move it manually. I recall I had your issue, but can't remember what i did to fix it. Could it be you're calling SetView too often?

    For our MVVP pattern I added a "MapView" property on our mapcontrol, when set it sets the properties on the map like so:

           public MapView MapView
            {
                get { return (MapView)GetValue(MapViewProperty); }
                set { SetValue(MapViewProperty, value); }
            }
    
            private static void MapViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var control = d as MapControl;
                if (control == null) return;
    
                control.MapViewChanged((MapView)e.NewValue);
            }
    
            public void MapViewChanged(MapView mapView)
            {
                if (mapView == null || (mapView.Latitude == 0 && mapView.Longitude == 0)) return;
    
                //if user has interacted then don't change map until they force it
                if (userInteracted && !mapView.ForceView) return;
    
                userInteracted = false;
    
                var center = new GeoCoordinate(mapView.Latitude, mapView.Longitude);
                //allow the user to zoom in/out a bit but beyond 14 reset.
                if (map.ZoomLevel > 14 && !mapView.ForceZoomLevel)
                {
                    map.Center = center;
                }
                else
                {
                    map.SetView(center, mapView.ZoomLevel);
                }
            }


    I then have a helper class for the GPS, listed below, and simply hookup the coordinate property changed to set the mapview like so:

                _locationHelper = new LocationHelper(settingsProvider){Track = true};
                _locationHelper.PropertyChanged += (o, e) =>
                {
                    if (e.PropertyName == "Coordinate")
                    {
                        Coordinate = _locationHelper.Coordinate;
                        MapView = new MapView { Latitude = coordinate.Latitude, Longitude = coordinate.Longitude, ZoomLevel = 16, ForceZoomLevel = true, ForceView = firstGpsCoordinate };
                        firstGpsCoordinate = false;
                    }
                };

    Full listing for the LocationHelper, I think this could sort out your issues:

    using System;
    using System.ComponentModel;
    using System.Device.Location;
    using System.Windows;
    using SoulSolutions.TransHub.Common;
    using WindowsPhoneEssentials.Storage;
    
    namespace SoulSolutions.TransHub.Utilities
    {
        public class LocationHelper : INotifyPropertyChanged, IDisposable
        {
    
            private GeoCoordinateWatcher _watcher;
            private readonly UserSettings _userSettings;
            private readonly ISettingsProvider _settingsProvider;
            
            public LocationHelper(ISettingsProvider settingsProvider)
            {
                _settingsProvider = settingsProvider;
                _userSettings = settingsProvider.GetSettings<UserSettings>();
            }
    
            private GeoCoordinate coordinate;
            public GeoCoordinate Coordinate
            {
                get { return coordinate; }
                set
                {
                    if (coordinate == value) return;
                    coordinate = value;
                    onPropertyChanged("Coordinate");
                }
            }
    
            public bool Track { get; set; }
    
            public void LocateMe()
            {
                //reset
                coordinate = new GeoCoordinate();
                if (!_userSettings.AllowAccessToLocation)
                {
                    var result = MessageBox.Show("Would you like to \"Allow\" [your app name] access to your current location? We do not share your location. Full details in the settings screen.", "Location Disabled",
                                             MessageBoxButton.OKCancel);
    
                    if (result != MessageBoxResult.OK) return;
    
                    //The user granted us access
                    _userSettings.AllowAccessToLocation = true;
                    _settingsProvider.SaveSettings(_userSettings);
                }
    
                if (_watcher != null) return;
    
                _watcher = new GeoCoordinateWatcher(Track ? GeoPositionAccuracy.High : GeoPositionAccuracy.Default) { MovementThreshold = 1.0 };
                _watcher.StatusChanged += WatcherStatusChanged;
                _watcher.PositionChanged += WatcherPositionChanged;
                _watcher.Start();
            }
    
            private void WatcherPositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
            {
                if (_watcher == null) return;
    
                if (Track && _watcher.Status == GeoPositionStatus.Ready)
                {
                    Coordinate = _watcher.Position.Location;
                }
            }
    
            private void WatcherStatusChanged(object sender, GeoPositionStatusChangedEventArgs geoPositionStatusChangedEventArgs)
            {
                if (_watcher == null) return;
                switch (_watcher.Status)
                {
                    case GeoPositionStatus.Disabled:
    
                        // The Location Service is disabled or unsupported.
                        // Check to see whether the user has disabled the Location Service.
                        if (_watcher.Permission == GeoPositionPermission.Denied)
                        {
                            // The user has disabled the Location Service on their device.
                            MessageBox.Show("Unable to use your location as you have turned off location services. To enable this function turn on Use Location in the settings menu", "Location Disabled",
                                                         MessageBoxButton.OK);
                        }
                        else
                        {
                            MessageBox.Show("Location is not functioning on this device. Please check your phone settings and try again.", "Location Disabled",
                                                         MessageBoxButton.OK);
                        }
                        StopWatcher(true);
                        break;
    
                    case GeoPositionStatus.NoData:
                        MessageBox.Show("Unable to locate you at this current time.", "Location Data not Available",
                                                     MessageBoxButton.OK);
                        StopWatcher();
                        break;
    
                    case GeoPositionStatus.Ready:
                        Coordinate = _watcher.Position.Location;
                        if (!Track) StopWatcher();
                        break;
                }
            }
    
            private void StopWatcher(bool disabled = false)
            {
                if (_watcher == null) return;
    
                if (!disabled)
                {
                    _watcher.StatusChanged -= WatcherStatusChanged;
                    _watcher.Stop();
                    _watcher.Dispose();
                }
                _watcher = null;
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            private void onPropertyChanged(string propertyName)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
    
            public void Dispose()
            {
                StopWatcher();
            }
        }
    }
    


    Windows Live MVP - www.soulsolutions.com.au - catch me on http://twitter.com/soulsolutions

    Sunday, March 11, 2012 5:36 AM
    Moderator

All replies

  • Are you using GeoCoordinateWatcher's PositionChanged event to update the location of GPS tracker in your application? if yes, currently there is known issue where the gps tracker position is not updated. You might need to use map1.SetView(map1.BoundingRectangle); in the event as workaround.

    Also take a look on this article http://new.efficientcoder.net/2010/09/gps-simulator-for-windows-phone-7.html, which might help you.


    MSFT
    Hemant Goyal
    • Marked as answer by Ricky_Brundritt Thursday, December 1, 2011 11:47 AM
    • Unmarked as answer by jaybo_nomad Monday, December 12, 2011 10:05 PM
    Monday, November 28, 2011 5:05 PM
  • Yes Hemant, I'm using GeoCoordinateWatcher.PositionedChanged to update the map view.

    I've run further tests and confirmed that the problem is indeed related to the load on the map control.  If I update the map position once every four seconds, then tiles are displayed correctly.  As soon as I switch back to updates every second, then tile updates due to downloads stop.

    So presumably there is logic in the map control that goes something like this:

    1. SetView() or Map.Center changed by client application
    2. Start approximately 1 second animation to smoothly reposition the map
    3. If animation is in progress, delay processing tile requests UNLESS zoom has changed or map is panned due to user touch input.

    So changing Map.Center at 1Hz effectively blocks tile updates. 


    • Edited by jaybo_nomad Tuesday, November 29, 2011 12:11 AM
    • Marked as answer by Ricky_Brundritt Thursday, December 1, 2011 11:47 AM
    • Unmarked as answer by Ricky_Brundritt Thursday, December 1, 2011 11:47 AM
    Tuesday, November 29, 2011 12:04 AM
  • Yeah! something like you described is happening here. Could you try the workaround above? It should fix the issue.
    MSFT
    Hemant Goyal
    Tuesday, November 29, 2011 9:22 PM
  • The problem is not because GeoCoordinateWatcher isn't updating.  I am continuously getting new GPS coordinates, and the map is repositioning properly. 

    It just isn't fetching or displaying new tiles.

    Monday, December 12, 2011 10:06 PM
  • Use myMap.SetView(map1.BoundingRectangle) in the GeoCoordinateWatcher's PositionChanged event to update the map tiles.

     


    MSFT
    Hemant Goyal
    Monday, December 12, 2011 10:13 PM
  • In my second response above, I mentioned that regardless of whether I'm using SetView() or Map.Center, the problem occurs. 

    Internal to the map control, animations must take precedence over tile downloads.

    Monday, December 12, 2011 11:39 PM
  • The map rendering itself is completed by a set of MultiScaleImage controls. These controls give the very smooth and fluid look but are quite resource intensive. For one in the Silverlight web control they stop working if the control is not in view (minimised, out screen, different tab etc). Additionally a lot of work has been done to reduce it's hunger for tiles by oveririding the core method to get a tiles URI.

    In the context of the phone I have a commercial app - Transhub - that is doing something simliar to you, once on a train/bus/ferry you can track your position in real time, centre the map on your location unless you move it manually. I recall I had your issue, but can't remember what i did to fix it. Could it be you're calling SetView too often?

    For our MVVP pattern I added a "MapView" property on our mapcontrol, when set it sets the properties on the map like so:

           public MapView MapView
            {
                get { return (MapView)GetValue(MapViewProperty); }
                set { SetValue(MapViewProperty, value); }
            }
    
            private static void MapViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var control = d as MapControl;
                if (control == null) return;
    
                control.MapViewChanged((MapView)e.NewValue);
            }
    
            public void MapViewChanged(MapView mapView)
            {
                if (mapView == null || (mapView.Latitude == 0 && mapView.Longitude == 0)) return;
    
                //if user has interacted then don't change map until they force it
                if (userInteracted && !mapView.ForceView) return;
    
                userInteracted = false;
    
                var center = new GeoCoordinate(mapView.Latitude, mapView.Longitude);
                //allow the user to zoom in/out a bit but beyond 14 reset.
                if (map.ZoomLevel > 14 && !mapView.ForceZoomLevel)
                {
                    map.Center = center;
                }
                else
                {
                    map.SetView(center, mapView.ZoomLevel);
                }
            }


    I then have a helper class for the GPS, listed below, and simply hookup the coordinate property changed to set the mapview like so:

                _locationHelper = new LocationHelper(settingsProvider){Track = true};
                _locationHelper.PropertyChanged += (o, e) =>
                {
                    if (e.PropertyName == "Coordinate")
                    {
                        Coordinate = _locationHelper.Coordinate;
                        MapView = new MapView { Latitude = coordinate.Latitude, Longitude = coordinate.Longitude, ZoomLevel = 16, ForceZoomLevel = true, ForceView = firstGpsCoordinate };
                        firstGpsCoordinate = false;
                    }
                };

    Full listing for the LocationHelper, I think this could sort out your issues:

    using System;
    using System.ComponentModel;
    using System.Device.Location;
    using System.Windows;
    using SoulSolutions.TransHub.Common;
    using WindowsPhoneEssentials.Storage;
    
    namespace SoulSolutions.TransHub.Utilities
    {
        public class LocationHelper : INotifyPropertyChanged, IDisposable
        {
    
            private GeoCoordinateWatcher _watcher;
            private readonly UserSettings _userSettings;
            private readonly ISettingsProvider _settingsProvider;
            
            public LocationHelper(ISettingsProvider settingsProvider)
            {
                _settingsProvider = settingsProvider;
                _userSettings = settingsProvider.GetSettings<UserSettings>();
            }
    
            private GeoCoordinate coordinate;
            public GeoCoordinate Coordinate
            {
                get { return coordinate; }
                set
                {
                    if (coordinate == value) return;
                    coordinate = value;
                    onPropertyChanged("Coordinate");
                }
            }
    
            public bool Track { get; set; }
    
            public void LocateMe()
            {
                //reset
                coordinate = new GeoCoordinate();
                if (!_userSettings.AllowAccessToLocation)
                {
                    var result = MessageBox.Show("Would you like to \"Allow\" [your app name] access to your current location? We do not share your location. Full details in the settings screen.", "Location Disabled",
                                             MessageBoxButton.OKCancel);
    
                    if (result != MessageBoxResult.OK) return;
    
                    //The user granted us access
                    _userSettings.AllowAccessToLocation = true;
                    _settingsProvider.SaveSettings(_userSettings);
                }
    
                if (_watcher != null) return;
    
                _watcher = new GeoCoordinateWatcher(Track ? GeoPositionAccuracy.High : GeoPositionAccuracy.Default) { MovementThreshold = 1.0 };
                _watcher.StatusChanged += WatcherStatusChanged;
                _watcher.PositionChanged += WatcherPositionChanged;
                _watcher.Start();
            }
    
            private void WatcherPositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
            {
                if (_watcher == null) return;
    
                if (Track && _watcher.Status == GeoPositionStatus.Ready)
                {
                    Coordinate = _watcher.Position.Location;
                }
            }
    
            private void WatcherStatusChanged(object sender, GeoPositionStatusChangedEventArgs geoPositionStatusChangedEventArgs)
            {
                if (_watcher == null) return;
                switch (_watcher.Status)
                {
                    case GeoPositionStatus.Disabled:
    
                        // The Location Service is disabled or unsupported.
                        // Check to see whether the user has disabled the Location Service.
                        if (_watcher.Permission == GeoPositionPermission.Denied)
                        {
                            // The user has disabled the Location Service on their device.
                            MessageBox.Show("Unable to use your location as you have turned off location services. To enable this function turn on Use Location in the settings menu", "Location Disabled",
                                                         MessageBoxButton.OK);
                        }
                        else
                        {
                            MessageBox.Show("Location is not functioning on this device. Please check your phone settings and try again.", "Location Disabled",
                                                         MessageBoxButton.OK);
                        }
                        StopWatcher(true);
                        break;
    
                    case GeoPositionStatus.NoData:
                        MessageBox.Show("Unable to locate you at this current time.", "Location Data not Available",
                                                     MessageBoxButton.OK);
                        StopWatcher();
                        break;
    
                    case GeoPositionStatus.Ready:
                        Coordinate = _watcher.Position.Location;
                        if (!Track) StopWatcher();
                        break;
                }
            }
    
            private void StopWatcher(bool disabled = false)
            {
                if (_watcher == null) return;
    
                if (!disabled)
                {
                    _watcher.StatusChanged -= WatcherStatusChanged;
                    _watcher.Stop();
                    _watcher.Dispose();
                }
                _watcher = null;
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            private void onPropertyChanged(string propertyName)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
    
            public void Dispose()
            {
                StopWatcher();
            }
        }
    }
    


    Windows Live MVP - www.soulsolutions.com.au - catch me on http://twitter.com/soulsolutions

    Sunday, March 11, 2012 5:36 AM
    Moderator