Monday, 4 October 2010

Bing Maps Silverlight control weather mash-up with yr.no - Part 4

In this post, we will be adding the web service created in the previous post to the Silverlight application and using it to put pushpins on the map that represent weather data

So first off we will need to add the web reference to the application. Right click on the Silverlight project and click Add Web Reference. You can discover the one in the solution or else point it to where the web reference is located. I have named my reference InformationService.

Once its added, you should right click on the web reference and click update reference to ensure the proxy classes and addition code is created properly. Also if you attempt to build now and get an error about an assembly being missing (specifically relating to Serialization) right click on the web reference and click Configure Service Reference. Uncheck the Reuse types in referenced assemblies to fix this. This is a relatively rare error so just in case!

To use this in code you will need to add a using statement like so
1: using MashupMapApp.InformationService;
In the MainPage.xaml, we are going to create a new button that will use the web service to get the weather information and display it on the map. So the button is defined within the StackPanel like so
1: <Button Name="Search" Width="150" Background="Black" Click="SearchClick" Height="50" Foreground="White">
2:  <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Left">
3:   <Image Source="images/2.png" Height="40" Width="40" VerticalAlignment="Center" HorizontalAlignment="Left" Opacity="1" MaxHeight="40" MaxWidth="40"></Image>
4:   <TextBlock Text="Været" VerticalAlignment="Center" HorizontalAlignment="Right" TextAlignment="Center" Width="109" Foreground="Black"  ></TextBlock>
5:  </StackPanel>
6: </Button>
Once you create the SearchClick method stub we will add some code to access the web service. So we need to create a SoapClient and then we need to add method handlers to the completed event and finally call the ASync method

So the method looks like the following
1:   private void SearchClick(object sender, RoutedEventArgs e)
2:         {
3:             var soapClient = new InfoServiceSoapClient();
4:             soapClient.GetAllWeatherCompleted += GetAllWeatherCompleted;
5:             soapClient.GetAllWeatherAsync();
6:         }
If you have ReSharper you can use it to create the GetAllWeatherCompleted method stub. Additionally I think there is that type of functionality in VS2010 at this stage too!

In the GetAllWeatherCompleted we will get the results of the asynchronous call to the web service which is the list of weather data. But first we need to set up the map again with a new layer like before.
1: var mapLayer = new MapLayer();
2: bingMap.Children.Add(mapLayer);
Next we will get the list of WeatherInfo objects which is contained in e.Result which in this case is an ObservableCollection
1: var weatherInfoList = e.Result;
The next thing is just to add each pushpin to the map and we can do this with a simple function wrapped in an even simpler foreach loop
1: foreach (var weatherInfo in weatherInfoList)
2:  {
3:   AddPushPin(mapLayer, weatherInfo);
4:  }
I will explain the AddPushPin method later in this post. First I will finish off the method GetAllWeatherCompleted. Finally I have some bit of tidy up code, which centres the map on the last item in the list and zooms the map in a bit. So the full method looks like this
1: void GetAllWeatherCompleted(object sender, GetAllWeatherCompletedEventArgs e)
2:         {
3:             var mapLayer = new MapLayer();
4:             bingMap.Children.Add(mapLayer);
5: 
6:             ObservableCollection<WeatherInfo> weatherInfoList = e.Result;
7: 
8:             foreach (var weatherInfo in weatherInfoList)
9:             {
10:                 AddPushPin(mapLayer, weatherInfo);
11:             }
12: 
13:             bingMap.CopyrightVisibility = Visibility.Collapsed;
14:             bingMap.LogoVisibility = Visibility.Collapsed;
15: 
16:             var itemLocation = weatherInfoList.LastOrDefault();
17:             bingMap.SetView(new Location(itemLocation.Latitude, itemLocation.Longitude), 9.0);
18:         }
In my App.xaml, I have a custom style which I will use to display the tooltip image. It uses databinding so that you just need to pass in the data context and the items will be databound to whatever they are specified to. This style sits in the application resources.
1:  <Style x:Key="WeatherInfoBox" TargetType="ToolTip">
2:   <Setter Property="Background" Value="Transparent" />
3:   <Setter Property="BorderBrush" Value="Transparent" />
4:   <Setter Property="BorderThickness" Value="0" />
5:   <Setter Property="Template">
6:    <Setter.Value>
7:     <ControlTemplate>
8:      <Border CornerRadius="5">
9:      <Border.Background>
10:       <SolidColorBrush Color="Black" Opacity="0.4"/>
11:      </Border.Background>
12:      <ContentPresenter Margin="5">
13:       <ContentPresenter.Content>
14:        <StackPanel Margin="5" MaxWidth="200">
15:         <TextBlock Text="{Binding Title}" FontWeight="Bold" FontSize="16" Foreground="White"/>
16:          <StackPanel Orientation="Horizontal">
17:           <Image Source="{Binding ToolTipImage}" Width="40" Height="40" />
18:           <TextBlock Text="{Binding TempC}" Foreground="White" FontWeight="Bold" FontSize="15" VerticalAlignment="Center" />
19:          </StackPanel>
20:          <StackPanel Orientation="Horizontal">
21:           <Image Width="40" Height="40" Source="{Binding WindImage}"/>
22:           <TextBlock Text="{Binding WindData}" Foreground="White" FontSize="12" VerticalAlignment="Center" TextWrapping="Wrap"/>
23:          </StackPanel>
24:          <TextBlock Text="{Binding Description}" Foreground="White" TextWrapping="Wrap"/>
25:         </StackPanel>
26:        </ContentPresenter.Content>
27:       </ContentPresenter>
28:      </Border>
29:     </ControlTemplate>
30:    </Setter.Value>
31:   </Setter>
32:  </Style>
As you can see certain values are databound to the names of properties within my WeatherInfo object. We will use the TooltipService object to apply the tooltip to the pushpin.

So now onto the AddPushPin method. This is the final method of the application. This as the name suggests actually puts you on the map. So like before we create a new image and set its location. The only additional idea here is the ToolTip that will be added to the pushpin.
1:  private static void AddPushPin(MapLayer mapLayer, WeatherInfo weatherInfo)
2:         {
3:             var pushPinImage = new Image
4:             {
5:                 Source =
6:                     new BitmapImage(
7:                     new Uri(weatherInfo.ImageLocation, UriKind.Relative)),
8:                 Width = 65,
9:                 Height = 65,
10:                 Opacity = 1
11:             };
12: 
13: 
14:             ToolTipService.SetToolTip(pushPinImage, new ToolTip
15:             {
16:                 DataContext = weatherInfo,
17:                 Style = Application.Current.Resources["WeatherInfoBox"] as Style
18:             });
19: 
20: 
21:             var location = new Location { Latitude = weatherInfo.Latitude, Longitude = weatherInfo.Longitude };
22: 
23: 
24:             MapLayer.SetPosition(pushPinImage, location);
25:             MapLayer.SetPositionOrigin(pushPinImage, PositionOrigin.Center);
26: 
27:             mapLayer.Children.Add(pushPinImage);
28:         }
04-10-2010 21-41-02Building your application will put this on screen and give you something like this (weather in Stavanger at the moment has people predicting how long its before we need to build an ark).

Finally the source code is being made available from here, sorry its taken a bit longer than expected.  Some notes, you will need to add your own key as described here and also you may need to re-add the Microsoft Map references if they are installed some place differently.

1 comment:

Tormod said...

Great mashup complemented by no less than four blog posts. And finally, the downloadable source :-) Thanks!