Sunday 19 September 2010

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

In this post, I will take a look at creating a web service that will feed weather data to our Silverlight application that we have created in the previous posts
yr.noFirstly a little about where we are going to get the weather data from. yr.no is the joint online weather service from the Norwegian Meteorological Institute (met.no) and the Norwegian Broadcasting Corporation (NRK) and it offers weather forecasts in English (in addition to Norwegian Nynorsk and Norwegian Bokmål) for more than 700,000 places in Norway and 6.3 million places worldwide.

Yr.no supplies its weather data in XML format and you can get this XML by adding forecast.xml to the URL that is generated when you look for a location. For example for my current location in Stavanger, the generated address is http://www.yr.no/place/Norway/Rogaland/Stavanger/Stavanger/ and the XML for this http://www.yr.no/place/Norway/Rogaland/Stavanger/Stavanger/forecast.xml. This will generate most of the information in English.

To get this information in Norwegian you change the place to sted and use varsel.xml instead.

There are additional services provided by eKlima which you can use instead of yr.no if you wish. You can find more information on eKlima here.What I am going to do in this post, is creating the web service. So firstly we will create a web blank web application and a new web service to it.

In my project I have created a common library that contains a business layer, data layer and some classes. I have created a BaseInformation class which contains the basic information that is used by the map to create pushpins. This allows us to create more different objects which can use this class and make a generic method for adding pushpins with different data types, images etc.
1: public class BaseInformation
2:    {
3:        public int ID { get; set; }
4:        public string Title { get; set; }
5:        public double Latitude { get; set; }
6:        public double Longitude { get; set; }
7:        public string ImageLocation { get; set; }
8:        public string Description { get; set; }
9:        public string ToolTipImage { get; set; }
10:    }
The WeatherInfo class contains information specific to weather data and we will use this in our custom tooltip that we will be adding to the pushpin when its created on the map.
1:     public class WeatherInfo : BaseInformation
2:     {
3:         public string TempC { get; set; }
4:         public string FromTime { get; set; }
5:         public string ToTime { get; set; }
6:         public string SymbolName { get; set; }
7:         public string SymbolNumber { get; set; }
8:         public string WindData { get; set; }
9:         public string WindImage { get; set; }
10:     }
We have a simple class with an even simpler method that returns a generic list of strings that contains the XML addresses to download the information from yr.no.
1:    public IEnumerable<string> GetData()
2:         {
3:             return new List<string>
4:                        {
5:                            "http://www.yr.no/place/Norway/Rogaland/Sandnes/Sandnes/varsel.xml",
6:                            "http://www.yr.no/sted/Norge/Rogaland/Stavanger/Stavanger/varsel.xml",
7:                            "http://www.yr.no/place/Norway/Rogaland/Stavanger/Forus/varsel.xml",
8:                        };
9:         }
In the data layer we will create a new method that we will use to download the XML and manipulate it to our needs. The method stub looks like the following
1:       public static WeatherInfo GetWeatherInfo(string feed)
2:         {
3:            
4:         }
So first we need to download the XML file from yr.no. We use the XmlDocument class and use the Load method to load the XML.
1:             var xDoc = new XmlDocument();
2:             xDoc.Load(feed);
Since the XML file is quite large and contains multiple different elements we will create some datasets from the different elements and then use LINQ to join these datasets together and create the objects that we need.

So first creating the datasets from XML. This is a quick method which takes the name of the tag you want to turn into a dataset and the XMLDocument.
1:        private static DataSet GetDataSet(XmlDocument xDoc, string tagName)
2:         {
3:             var forecast = xDoc.GetElementsByTagName(tagName);
4: 
5:             var ds = new DataSet();
6:             var readerStream = new StringReader(forecast[0].OuterXml);
7:             ds.ReadXml(readerStream);
8:             return ds;
9:         }
We will create datasets from the following XML elements tabular, location, and forecast and we will join these together using LINQ. The following LINQ query is not the best in the world due its level of complexity so I apologise in advance if your eyes burn out of your skull and you decide to run off screaming “Oh the humanity”
1: from time in ds.Tables["time"].AsEnumerable()
2:                     join temp in ds.Tables["temperature"].AsEnumerable()
3:                         on time.Field<int>("time_id") equals
4:                         temp.Field<int>("time_id")
5:                     join symbol in ds.Tables["symbol"].AsEnumerable()
6:                         on time.Field<int>("time_id") equals symbol.Field<int>("time_id")
7:                     join windDirection in ds.Tables["windDirection"].AsEnumerable()
8:                         on time.Field<int>("time_id") equals windDirection.Field<int>("time_id")
9:                     join windSpeed in ds.Tables["windSpeed"].AsEnumerable()
10:                        on time.Field<int>("time_id") equals windSpeed.Field<int>("time_id")
That is the basic LINQ query and we will use that as the basis to generate a WeatherInfo object using the second part of the query.
1:  select new WeatherInfo
2:                     {
3:                         ID = time.Field<int>("time_id"),
4:                         TempC = temp.Field<string>("value") +"C",
5:                         FromTime = time.Field<string>("from"),
6:                         ToTime = time.Field<string>("to"),
7:                         SymbolNumber = symbol.Field<string>("number"),
8:                         SymbolName = symbol.Field<string>("name"),
9:                         WindData = string.Format("{0} {1} {2} m/s", windDirection.Field<string>("name"), windSpeed.Field<string>("name"), windSpeed.Field<string>("mps"))
10:                     }).FirstOrDefault();
The handy thing here is the cast of the WeatherInfo at the start to create an object that we can populate. Also the helper method FirstOrDefault(). If there is no records, it will create a blank object and we will return that. The Full method looks like this
1:     private static WeatherInfo GetWeatherInfoFromDataSet(DataSet ds)
2:         {
3:             return (from time in ds.Tables["time"].AsEnumerable()
4:                     join temp in ds.Tables["temperature"].AsEnumerable()
5:                         on time.Field<int>("time_id") equals
6:                         temp.Field<int>("time_id")
7:                     join symbol in ds.Tables["symbol"].AsEnumerable()
8:                         on time.Field<int>("time_id") equals symbol.Field<int>("time_id")
9:                     join windDirection in ds.Tables["windDirection"].AsEnumerable()
10:                         on time.Field<int>("time_id") equals windDirection.Field<int>("time_id")
11:                     join windSpeed in ds.Tables["windSpeed"].AsEnumerable()
12:                        on time.Field<int>("time_id") equals windSpeed.Field<int>("time_id")
13:                     select new WeatherInfo
14:                     {
15:                         ID = time.Field<int>("time_id"),
16:                         TempC = temp.Field<string>("value") +"C",
17:                         FromTime = time.Field<string>("from"),
18:                         ToTime = time.Field<string>("to"),
19:                         SymbolNumber = symbol.Field<string>("number"),
20:                         SymbolName = symbol.Field<string>("name"),
21:                         WindData = string.Format("{0} {1} {2} m/s", windDirection.Field<string>("name"), windSpeed.Field<string>("name"), windSpeed.Field<string>("mps"))
22:                     }).FirstOrDefault();
23:         }
In the GetWeatherInfo method we will finalise any properties in the object that we haven’t set before this. Such as the latitude and longitude. Now because in Norway the decimal separator is a comma (,) rather than the decimal point (.) this can cause some issues when doing conversions between strings and doubles. So we will use the NumberFormatInfo class to specify how the conversion should take place.
1: weatherInfo.Latitude = double.Parse(dr["latitude"].ToString(), NumberFormatInfo.InvariantInfo);
2: weatherInfo.Longitude = double.Parse(dr["longitude"].ToString(), NumberFormatInfo.InvariantInfo);
The full method for GetWeatherInfo is as follows
1: public static WeatherInfo GetWeatherInfo(string feed)
2:         {
3:             var xDoc = new XmlDocument();
4:             xDoc.Load(feed);
5: 
6:             var ds = GetDataSet(xDoc, "tabular");
7: 
8:             var weatherInfo = GetWeatherInfoFromDataSet(ds);
9: 
10:             var coDs = GetDataSet(xDoc, "location");
11:             var weatherDescDs = GetDataSet(xDoc, "forecast");
12: 
13:             weatherInfo.Description = weatherDescDs.Tables["time"].Rows[0]["body"].ToString();
14:             weatherInfo.Description = weatherInfo.Description.Replace(@"<strong>", "");
15:             weatherInfo.Description = weatherInfo.Description.Replace(@":</strong>", " -");
16: 
17:             weatherInfo.WindImage = "images/windsock.png";
18: 
19:             var dr = coDs.Tables["location"].Rows[1];
20: 
21:             weatherInfo.Latitude = double.Parse(dr["latitude"].ToString(), NumberFormatInfo.InvariantInfo);
22:             weatherInfo.Longitude = double.Parse(dr["longitude"].ToString(), NumberFormatInfo.InvariantInfo);
23: 
24:             weatherInfo.Title = coDs.Tables["location"].Rows[0]["name"].ToString();
25:             weatherInfo.ToolTipImage = "images/temp.png";
26:             weatherInfo.ImageLocation = string.Format("images/{0}.png", weatherInfo.SymbolNumber);
27: 
28:             return weatherInfo;
29:         }
In the Business layer we have a simple method that creates a list of these weatherInfo objects based on the list of feeds that I talked about earlier.
1:  public static List<WeatherInfo> GetAllWeatherInfo()
2:         {
3:             var allFeeds = new FakeWeatherFeeds().GetData();
4: 
5:             var weatherInfos = allFeeds.Select(WeatherInfoDl.GetWeatherInfo).ToList();
6: 
7:             return weatherInfos;
8:         }
Instead of a foreach loop I am using some LINQ to pass in the method and create the list. And finally the web service method looks like the following.
1: [WebMethod]
2:         public List<WeatherInfo> GetAllWeather()
3:         {
4:             return WeatherInfoBl.GetAllWeatherInfo();
5:         }
In the next post we will put this altogether with the Silverlight map application and binding the list of objects to the map.

Friday 17 September 2010

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

Following on from the previous post will now look at adding pushpins to the map.

PushPins inherit from the UIElement class meaning that any UIElement can be used as a pushpin. So that means we can use Images as pushpins. So first lets add a normal pushpin to the map.

We are going to add a button to our map and wire up an event handler to add a pushpin to the map. In MainPage.xaml in your grid add a button

In the Button_Click event we are going to create a new MapLayer and add the pushpin to it. The pushpin is going to mark the location of the Empire State Building in downtown New York. It uses the following decimal co-ordinates. In MainPage.xaml.cs



using Microsoft.Maps.MapControl;

private void Button_Click(object sender, RoutedEventArgs e)
{
MapLayer mapLayer = new MapLayer();
bingMap.Children.Add(mapLayer);

Location location = new Location { Latitude = 40.748687, Longitude = -73.985549 };

Pushpin pushpin = new Pushpin {Location = location};

mapLayer.Children.Add(pushpin);

}


What we have done here is created a new MapLayer object and added that to the children collection of the parent Map. We then create a location object with the co-ordinates of the Empire State Building and create a new pushpin at this location. We finally add the pushpin to the map layer which shows it on the map.



This is a bit simple and being honest a bit boring. So lets mix it up a bit.


450px-Empire_State_Building_from_the_Top_of_the_RockSince we are using the Empire State Building, I am going to use an image of this renowned landmark and use that as its pushpin. So a quick look on wikipedia for an image we find the one on the left (http://en.wikipedia.org/wiki/File:Empire_State_Building_from_the_Top_of_the_Rock.jpg). We will use this image and load it as the pushpin in our map.

Just like before we are going to add a new button to our map. But we are going to encapsulate both buttons in a StackPanel so that we can arrange them easily.



I have downloaded the image and created a new folder in my Silverlight application called images and placed the image in there. I renamed it to Empire.jpg just so I wouldn’t have as much to type. If you want to use the image directly from Wikipedia you will need the following URL http://upload.wikimedia.org/wikipedia/commons/c/c7/Empire_State_Building_from_the_Top_of_the_Rock.jpg and use the UriKind.Absolute option.


In MainPage.xaml you will need to replace your button code with the following.


In MainPage.xaml.cs


So in this code sample, we are doing something very similar to the previous sample. First you create a new layer and as before add it to the parent map. Next you create a new image with a source from either a relative or absolute Url and set its height and width. The difference now is that we use the MapLayer methods to set the position for the Image we created and finally add the image to the maplayer object.

In the next post, I will go through getting weather data from yr.no and using LINQ to create weather data objects that can be rendered on the map using the above methods.

Tuesday 14 September 2010

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

So I have shown before in a previous post how to use the AJAX version of the Bing Maps control. Now I am going to show the new version of the map control that is in Silverlight. Microsoft have released the Bing Maps Silverlight Control SDK which you use to add map control to your website.

Now in comparison to the AJAX version, this new version is smooth. In fact its much easier to program with and you can do a lot of nice things with it. One of the biggest advantages is the fact that pushpins on the map can be anything that inherits from the UIElement class. This means you can have controls, user controls or images as pushpins. This functionality in itself extends the possibilities of customising the UI experience for your users. Additionally there is added benefit of the rich UI features of Silverlight that can allow to extend aspects of the map to your liking.

So how do you start all this great stuff.

  1. Go to the download site and download and install the SDK
  2. Surf to the Bing Maps Account portal and create a new account
  3. Generate a new developer key
  4. Open Visual Studio and get started!
    1. Note that for all this I am using Visual Studio 2010 and Silverlight 4. You can download the Silverlight 4 Tools for VS 2010 from here

So lets do that

1

First we are going to create a new Silverlight Application. If you get prompted download the Silverlight Developer runtime and install it.

Next choose the option to host the Silverlight application in an ASP.NET Web application. This just adds the boilerplate code for you to show your application in a web page.

Now you should have a nice simple application with your basic Silverlight application loaded and standard ASP.NET web application created.

You will need to add the Bing Map DLLs as references in your project. Right click on the references folder and browse to where you installed the SDK. If you chose the default installation path you will find it in your Program Files directory or (x86) directory on 64bit Windows in Bing Maps Silverlight Control. You will find the DLLs you need in the V1\Libraries directory. You will need both of them which are

  • Microsoft.Maps.MapControl.Common
  • Microsoft.Maps.MapControl

Once they are added we can use the Bing Maps Silverlight Control in our Silverlight application.

Add the namespace to your XAML code

xmlns:Maps="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"

You can reference the maps by using the Maps prefix. Inside in the grid add a map control

<Grid x:Name="LayoutRoot" Background="White">
   <
Maps:Map Name="bingMap" CredentialsProvider="Your key here"></Maps:Map>
</
Grid>
 
You need to create a new key in the Bing Maps Account portal if you haven’t already done so and then copy it into the CredentialsProvider so that your map will work correctly otherwise you will see an invalid credentials warning on your map.
Your code should look like this
<UserControl x:Class="MashupMapApp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:Maps="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">

<
Grid x:Name="LayoutRoot" Background="White">
<
Maps:Map Name="bingMap" CredentialsProvider="your key"></Maps:Map>
</
Grid>
</
UserControl>

Pressing F5 to debug should bring up the Silverlight map control in its test page.


In the next post, I will go through adding standard and custom pushpins to the map.