Andrej Tozon's blog

In the Attic

NAVIGATION - SEARCH

Live Europe Weather Map in action

As promised, here’s my Live Europe Weather Map in action. This is still just a basic example of displaying icon pushpins on a map, and because I’m using Virtual Earth ASP.NET Map Control, I don’t need to worry about how map is handled and pushpins are displayed, I only need to provide data for the pushpins.

Live Europe Weather Map 

The first version of the Weather Map from a couple of years ago used to scrape the data off a Html Page. Recently, the National Environmental Agency started publishing weather data in a more collection-friendly data formats, so I switched to these.

The current version of Live European Weather Map collects Xml files, containing weather observations for European Capitals and additional cities.

Data Collection

I decided to go with LINQ to SQL option for shaping my data entity. Some may argue that LINQ to SQL isn’t a serious technology, but for small, compact projects like this, it gives me just what I need:

1. Instead of generating my data classes manually, I can create a table in my database (SQL Express in this case) and pull my classes out of that.

2. I can create my data entity by drag and dropping a table from the data source to the LINQ to SQL Class Designer in Visual Studio 2008, and my data class would get generated automatically. Here's what I’m currently collecting for weather conditions:

image

3. I can use this class to collect the data and store in into the database, or I can simply just store the collected data into ASP.NET Cache and forget the database completely.

Of course, I could’ve done it some other way (plenty of options), but this one seemed the easiest and quickest.

Weather data collection is done in a background, with a HttpModule and a timer, set to fire the collection process every 60 minutes (configurable through web.config):

   1: public void Init(HttpApplication application)
   2: {
   3:     int interval = GetCollectIntervalSetting();
   4:  
   5:     if (timer == null &&  interval > 0)
   6:     {
   7:         timer = new Timer(new TimerCallback(BackgroundCollectionCallback), application.Context, 0, interval);
   8:     }
   9: }
  10:  
  11: private void BackgroundCollectionCallback(object sender)
  12: {
  13:     WeatherCollector.UpdateConditions();
  14: }

Using LINQ to SQL, collected data is inserted into the database:

   1: public static void UpdateConditions()
   2: {
   3:     List<WeatherCondition> items = Collect();
   4:  
   5:     WeatherDataContext context = new WeatherDataContext();
   6:     context.ExecuteCommand("delete dbo.WeatherCondition");
   7:     context.WeatherConditions.InsertAllOnSubmit(items);
   8:     context.SubmitChanges();
   9: }

… and later read from it:

   1: public static WeatherCondition[] Read()
   2: {
   3:     WeatherDataContext context = new WeatherDataContext();
   4:     var conditions = from item in context.WeatherConditions
   5:                      select item;
   6:     return conditions.ToArray();
   7: }

… just to finally get shown on the map:

   1: protected void Page_Load(object sender, EventArgs e)
   2: {
   3:     if (IsPostBack)
   4:     {
   5:         return;
   6:     }
   7:  
   8:     AddWeatherPins();
   9: }
  10:  
  11: private void AddWeatherPins()
  12: {
  13:     WeatherCondition[] conditions = WeatherCollector.Read();
  14:  
  15:     for (int i = 0; i < conditions.Length; i++)
  16:     {
  17:         WeatherCondition condition = conditions[i];
  18:         if (condition.ConditionIcon == null || condition.ConditionIcon.Length == 0)
  19:         {
  20:             continue;
  21:         }
  22:  
  23:         AddPin(condition);
  24:     };
  25: }
  26:  
  27: private void AddPin(WeatherCondition condition)
  28: {
  29:     StringBuilder sb = new StringBuilder();
  30:     sb.AppendFormat("<p><b>{0} {1}</b>, {2}", condition.Temperature, condition.TemperatureUnit, condition.Condition);
  31:     if (condition.Weather.Length > 0)
  32:     {
  33:         sb.AppendFormat(", {0}", condition.Weather);
  34:     }
  35:     sb.Append("<br />");
  36:     if (condition.WindStrength.HasValue)
  37:     {
  38:         sb.AppendFormat("Wind: {0}, {1} {2}<br />", condition.WindDirection, condition.WindStrength, condition.WindStrengthUnit);
  39:     }
  40:     if (condition.Humidity.HasValue)
  41:     {
  42:         sb.AppendFormat("Humidity: {0} {1}<br />", condition.Humidity, condition.HumidityUnit);
  43:     }
  44:     if (condition.Visibility.HasValue)
  45:     {
  46:         sb.AppendFormat("Visibility: {0} {1}<br />", condition.Visibility, condition.VisibilityUnit);
  47:     }
  48:     if (condition.Pressure.HasValue)
  49:     {
  50:         sb.AppendFormat("Pressure: {0} {1} {2}<br />", condition.Pressure, condition.PressureUnit, condition.PressureText);
  51:     }
  52:     sb.AppendFormat("<br />Updated: {0}</p>", condition.UpdatedTime);
  53:  
  54:     Shape pin = new Shape(ShapeType.Pushpin, new LatLongWithAltitude((double)condition.Latitude, (double)condition.Longitude));
  55:     pin.Title = string.Format("{0}, {1}", condition.City, condition.CountryCode);
  56:     pin.CustomIcon = string.Format("images/{0}.png", condition.ConditionIcon);
  57:     pin.Description = sb.ToString();
  58:     map.AddShape(pin);
  59: }

Note that a large portion of the code is spent for describing pushpin’s popup content.

Again, this is just a drag’n’dropped Virtual Earth Map Control put to work here, without any specific client or server-side events. Looking forward to explore those in the future.