Bing Maps ScreenI’ve been constantly impressed with how productive the development environment for Windows Phone 7 is. Having access to most of .NET 3.5, along with Reactive Extensions for .NET (Rx) allows us to produce incredibly elegant code for common scenarios.

In the MyTours application I’m developing, I wanted to show the user their location on a map, and have the map track along with their movement. Thing is, I didn’t want the entire map to move and redraw constantly, so what I wanted to do was:

  • Move the pointer showing the user’s location every half second to wherever the phone moved; BUT
  • Only move the map itself when the user has moved more than, say 100m.

The actual movement is incredibly simple to do. I have a map control with the Center bound to MapCenter in the ViewModel, and a custom Pushpin (just two concentric circles) with the Location bound to a “YouAreHere” property in the ViewModel. You can see these bindings in the highlighted lines:

<m:Map Name="TourMap"
CredentialsProvider="{StaticResource BingMapCredentials}"
Center="{Binding MapCenter, Mode=TwoWay}"
ZoomLevel="{Binding ZoomLevel, Mode=TwoWay}" >
    <m:MapLayer x:Name="YouAreHereLayer">
            <m:Pushpin x:Name="targetPin" Location="{Binding YouAreHere}">
                <m:Pushpin.Template>
                    <ControlTemplate>
                        <Grid Name="ppLocation" Width="20" Height="20" Visibility="{Binding IsTracking,Converter={StaticResource boolToVisibilityConverter}}">
                            <Ellipse Fill="Red" Width="20" Height="20" HorizontalAlignment="Center"  VerticalAlignment="Center" Opacity="0.2"  />
                            <Ellipse Fill="Red" Width="4" Height="4" HorizontalAlignment="Center"  VerticalAlignment="Center" Opacity="0.8"  />
                        </Grid>
                    </ControlTemplate>
                </m:Pushpin.Template>
            </m:Pushpin>
    </m:MapLayer>
</m:Map>

(There’s more stuff on the map, but I’ve simplified it for this demo)

YouAreHere and MapCenter are just plain old notifying properties (using a little helper method in my ViewModelBase that fires PropertyChanged). They are properties of the view model that is the DataContext of the page holding the map. I use Caliburn Micro to wire my views (xaml files) to view models (c# objects).

private GeoCoordinate _mapCenter;
private GeoCoordinate _youAreHere;

public GeoCoordinate MapCenter
{
    get { return _mapCenter; }
    set { SetNotifyingProperty(() => MapCenter, ref _mapCenter, value); }
}

public GeoCoordinate YouAreHere
{
    get { return _youAreHere; }
    set { SetNotifyingProperty(() => YouAreHere, ref _youAreHere, value); }
}

Then when the user wants to start tracking their location, I use some sexy Rx goodness to do most of the heavy lifting:

public void ShowMyLocation()
{
    if (_isTracking)
        return;

    _watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High);

    // sample every half second
    var sampledPositionEvents =
        LocationHelpers.GetPositionChangedEventStream(_watcher)
        .Sample(TimeSpan.FromMilliseconds(500));

    // move the YouAreHere pin on every half-second result
    sampledPositionEvents.Subscribe(
        args => YouAreHere = args.Position.Location);

    // change the map center on 100m+ changes
    sampledPositionEvents.Where(
        args => MapCenter.GetDistanceTo(args.Position.Location) > 100).Subscribe(
        args => MapCenter = args.Position.Location);

    // just old fashioned event handling for status changes
    _watcher.StatusChanged += StatusChanged;
    _watcher.Start();
}

Explaining some of the highlighted lines above for clarity:

  • Line 10 borrows some code that generates an event stream from the Phone’s GeoCoordinateWatcher.
  • Line 11 uses the built-in Rx Sample method to only return a result every 500 milliseconds.
  • Lines 14 and 15 subscribe to the half-second samples and always update the users location indicator on the map.
  • Line 19 further filters down the half-second samples and only returns those samples where we are 100 metres or more from our MapCenter.
  • Line 20 subscribes to those 100m+ events and moves the map center (which, in turn means newer samples are no longer 100m away).

Of course we could have coded this all with good old fashioned event handlers and timers, but it’s nowhere near as compact or sexy looking.