Update the location of pushpins on Silverlight bing maps control

Go To StackoverFlow.com

1

My question is about using bing maps with windows phone 7. Here is a summary of what I need to do

  • poll a service every x seconds to retrieve a set of coordinates
  • if this is the first time the service is polled

    • plot these coordinates as custom pushpins on the map (I am using Image and MapLayer)

      PinObject pin = new PinObject() //Custom object
                      {
                          Id = groupMember.Id,
                          PushPin = new Image()
                          {
                              Source = new System.Windows.Media.Imaging.BitmapImage(new Uri("blackpin.png", UriKind.Relative)),
                              Opacity = 0.8,
                              Stretch = System.Windows.Media.Stretch.None
                          },
                          PinLocation = new GeoCoordinate(groupMember.Latitude, groupMember.Longitude)
                      }; 
      imageLayer.AddChild(pin.PushPin, pin.PinLocation); //Initialized in constructor
                      pinObjects.Add(pin);// Add pin object to a list to provide a handle to the objects
      
    • auto set the map zoomlevel so that all the plotted points are visible (I would assume using a LocationRect.CreateLocationRect should do)

                  var coords = pinObjects.Select(p => p.PinLocation).ToList();                        
                  myMap.SetView(LocationRect.CreateLocationRect(coords));
      
    • else based on the new coordinates obtained, update the locations of each of the pushpins on the map PinObject pObj = pinObjects.FirstOrDefault(p => p.Id == groupMember.Id);

       MapLayer.SetPosition(pObj.PushPin, new GeoCoordinate(groupMember.Latitude, groupMember.Longitude));
      

The pins load fiine and the call to the service to get their new locations loads fine as well, the problem is that their location on the map is never updated so basically they just sit still even though all this work is going on in the background, I have debugged so I know it works. How do I reset the location of the pins, if using an Image won't do, can I use a Pushpin object? How would this work?

Thanks in advance.

2012-04-04 20:50
by Obi Onuorah
I can't see in your code where you're ensuring your position changes are executed on the UI layer - bperreault 2012-04-05 00:19
Sorry BPerreault, I don't understand your question - Obi Onuorah 2012-04-05 10:58
Hi, so i use this.Dispatcher.BeginInvoke(() => {}); but it still doesn't update the pushpin location on the ma - Obi Onuorah 2012-04-06 00:41
Hmm. ok, I have another option I use when things are stubborn, maybe it will help. I'll post it below - bperreault 2012-04-06 13:41


0

I've found that the best way to ensure the pushpins get updated is to call call SetView() on the map again. You can pass in the existing view to basically force a refresh. Eg; MyMapControl.SetView(MyMapControl.BoundingRectangle);

2012-04-05 01:59
by MrMDavidson
Thanks MrMDavidson, I have tried this but it still doesn't work. I would assume that since the pins are on the MapLayer, we should be updating this or the pins them selves but obviously I am wrong. Any other thoughts - Obi Onuorah 2012-04-05 15:22
I'm binding my pushpins to a MapItemsControl ItemsSource property with Pushpin items created in the DataTemplate. The push pins bind a GeoCoordinate property to their Location property. After an update of the property I call SetView() as mentioned in my answer and it all works fine. Can you try posting your XAML - MrMDavidson 2012-04-08 03:06


0

Here is an option similar to Dispatcher.BeginInvoke but it works better for me in some cases. When I really need to get off the current thread with some work I will use a private static class UICallbackTimer to offset execution just a slight amount. (typos and untested code, just pulling out pieces here you'll have to debug in your code)

UICallbackTimer is not my code but it's available on the Internet. You can get information on this class by searching "private static class UICallbackTimer"

Here is the code to execute it.

UICallbackTimer.DelayExecution(TimeSpan.FromSeconds(.01),
                                       () => (

MapLayer.SetPosition(pObj.PushPin, new GeoCoordinate(groupMember.Latitude, groupMember.Longitude))

);

and here is the class ( I place it inside the current object, so that it remains private to my class) Add using statement for System.Threading

private static class UICallbackTimer
    {
        private static bool _running = false;
        private static int runawayCounter = 0;

        public static bool running()
        {
            if (_running && runawayCounter++ < 10)
                return _running;

            runawayCounter = 0;
            _running = false;
            return _running;
        }

        public static void DelayExecution(TimeSpan delay, Action action)
        {
            _running = true;
            System.Threading.Timer timer = null;
            SynchronizationContext context = SynchronizationContext.Current;
            timer = new System.Threading.Timer(
                (ignore) =>
                {
                    timer.Dispose();
                    _running = false;
                    context.Post(ignore2 => action(), null);
                }, null, delay, TimeSpan.FromMilliseconds(-1));
        }
    }
2012-04-06 13:48
by bperreault
Hmm, interesting! I'll need to do some resaerch on this. Thanks for pointing it ou - Obi Onuorah 2012-04-13 11:11


0

Great Question!

Here is some realllly ugly code, but at least its working and something to start from.

I got the main structure from here. I'd appreciate it if someone could post an answer with proper binding and less code behind.

 <Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <!--RESOURCES-->
    <Grid.Resources>
        <DataTemplate x:Key="LogoTemplate">
            <maps:Pushpin Location="{Binding PinLocation}" />
        </DataTemplate>
        <maps:MapItemsControl x:Name="GroupAPins"
                    ItemTemplate="{StaticResource LogoTemplate}"
                    ItemsSource="{Binding PinsA}">
        </maps:MapItemsControl>
    </Grid.Resources>
    <Grid x:Name="ContentPanel" Grid.Row="0" Margin="12,0,12,0"/>
</Grid>

public partial class MapPage : PhoneApplicationPage
{
    private ObservableCollection<PinData> _pinsA = new ObservableCollection<PinData>();
    private Map MyMap;
    public ObservableCollection<PinData> PinsA { get { return this._pinsA; } }  

    public MapPage()
    {
        InitializeComponent();

        this.DataContext = this;
        //Create a map.
        MyMap = new Map();
        MyMap.CredentialsProvider = new ApplicationIdCredentialsProvider("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
        //Remove the List of items from the resource and add it to the map
        this.LayoutRoot.Resources.Remove("GroupAPins");
        MyMap.Children.Add(GroupAPins);
        MyMap.Center = new GeoCoordinate(40.74569634433956, -73.96717071533204);
        MyMap.ZoomLevel = 5;
        //Add the map to the content panel.
        ContentPanel.Children.Add(MyMap);
        loadAPins_fromString();
    }

    //ADD PIN TO COLLECTION  
    private void addPin(String lat, String lon)
    {
        PinData tmpPin;
        tmpPin = new PinData()
        {
            PinLocation = new GeoCoordinate(System.Convert.ToDouble(lat), System.Convert.ToDouble(lon))
        };
        _pinsA.Add(tmpPin);

        var timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromSeconds(1);
        timer.Tick += delegate(object sender, EventArgs args)
                        {
                            PinsA.Remove(tmpPin);
                            tmpPin.PinLocation.Latitude += 1;
                            PinsA.Add(tmpPin);

                        };
        timer.Start();

    }

    //LOAD PINS ONE BY ONE  
    private string loadAPins_fromString()
    {
        //BAD  
        addPin("42.35960626034072", "-71.09212160110473");
        //addPin("51.388066116760086", "30.098590850830067");
        //addPin("48.17972265679143", "11.54910385608672");
        addPin("40.28802528051879", "-76.65668606758117");

        var coords = PinsA.Select(p => p.PinLocation).ToList();
        MyMap.SetView(LocationRect.CreateLocationRect(coords));

        return "A PINS LOADED - STRING";
    }
}

public class PinData 
{
    public GeoCoordinate PinLocation{get;set;}
} 
2012-04-12 21:54
by Quincy
This looks good, I'll give it a shot. Although I already have this working to some extent, it's still abit jumpy as the map zoomlevel decreases with every call to MyMap.SetView(LocationRect.CreateLocationRect(coords));! I'll definitely come back with my working code as soon as I get it to work smoothly. Thank - Obi Onuorah 2012-04-13 11:10
Ads