Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

WPF: OpenWeather

4.98/5 (62 votes)
15 Sep 2017CPOL2 min read 170.2K   8.2K  
WPF-MVVM weather forecast application that displays weather data using the OpenWeatherMap API

Introduction

OpenWeather is a WPF-MVVM weather forecast application that displays three day (current day + two days following) forecast for a particular location. The app makes use of the OpenWeatherMap API.

To get the project code, clone or download the project from GitHub.

Background

I wrote the first version of this app back in 2013, when the OpenWeatherMap API was still in beta. After taking a look at the old code, I decided it was time for a rewrite. As part of the update, the UI has been redesigned and hopefully looks much better than before.

ye olde app UI

Requirements

To use this project effectively, you require the following:

  • OpenWeatherMap App ID
  • Internet connection
  • Visual Studio 2015/17

Once you get your App ID, insert it as the value of the APP_ID constant in the OpenWeatherMapService class.

C#
private const string APP_ID = "PLACE-YOUR-APP-ID-HERE";
...
VB.NET
Private Const APP_ID As String = "PLACE-YOUR-APP-ID-HERE"
...

OpenWeatherMap

The OpenWeatherMap API provides weather data in XML, JSON, and HTML formats. The service is free for limited API calls – less than 60 calls per minute, and maximum 5 day forecast.

This project makes use of the XML response:

XML
<weatherdata>
  <location>
    <name>Nairobi</name>
    <type/>
    <country>KE</country>
    <timezone/>
    <location altitude="0" latitude="-1.2834" longitude="36.8167" 
     geobase="geonames" geobaseid="184745"/>
  </location>
  <credit/>
  <meta>
    <lastupdate/>
    <calctime>0.0083</calctime>
    <nextupdate/>
  </meta>
  <sun rise="2017-08-17T03:34:28" set="2017-08-17T15:38:48"/>
  <forecast>
    <time day="2017-08-17">
      <symbol number="501" name="moderate rain" var="10d"/>
      <precipitation value="5.03" type="rain"/>
      <windDirection deg="54" code="NE" name="NorthEast"/>
      <windSpeed mps="1.76" name="Light breeze"/>
      <temperature day="24.55" min="16.92" max="24.55" 
                   night="16.92" eve="24.55" morn="24.55"/>
      <pressure unit="hPa" value="832.78"/>
      <humidity value="95" unit="%"/>
      <clouds value="scattered clouds" all="48" unit="%"/>
    </time>
    <time day="2017-08-18">
      <symbol number="500" name="light rain" var="10d"/>
      <precipitation value="0.65" type="rain"/>
      <windDirection deg="109" code="ESE" name="East-southeast"/>
      <windSpeed mps="1.62" name="Light breeze"/>
      <temperature day="21.11" min="14.33" max="23.5" 
                   night="17.81" eve="21.48" morn="14.33"/>
      <pressure unit="hPa" value="836.23"/>
      <humidity value="62" unit="%"/>
      <clouds value="clear sky" all="8" unit="%"/>
    </time>
    <time day="2017-08-19">
      <symbol number="500" name="light rain" var="10d"/>
      <precipitation value="2.47" type="rain"/>
      <windDirection deg="118" code="ESE" name="East-southeast"/>
      <windSpeed mps="1.56" name=""/>
      <temperature day="22.55" min="12.34" max="22.55" 
                   night="15.46" eve="20.92" morn="12.34"/>
      <pressure unit="hPa" value="836.28"/>
      <humidity value="56" unit="%"/>
      <clouds value="clear sky" all="0" unit="%"/>
    </time>
  </forecast>
</weatherdata>

Fetching Forecast Data

Getting the forecast data is done by a function in the implementation of an interface named IWeatherService.

C#
public class OpenWeatherMapService : IWeatherService
{
    private const string APP_ID = "PLACE-YOUR-APP-ID-HERE";
    private const int MAX_FORECAST_DAYS = 5;
    private HttpClient client;

    public OpenWeatherMapService()
    {
        client = new HttpClient();
        client.BaseAddress = new Uri("http://api.openweathermap.org/data/2.5/");
    }

    public async Task<IEnumerable<WeatherForecast>> 
           GetForecastAsync(string location, int days)
    {
        if (location == null) throw new ArgumentNullException("Location can't be null.");
        if (location == string.Empty) throw new ArgumentException
           ("Location can't be an empty string.");
        if (days <= 0) throw new ArgumentOutOfRangeException
           ("Days should be greater than zero.");
        if (days > MAX_FORECAST_DAYS) throw new ArgumentOutOfRangeException
           ($"Days can't be greater than {MAX_FORECAST_DAYS}");

        var query = $"forecast/daily?q={location}&type=accurate&mode=xml&units=metric&
                    cnt={days}&appid={APP_ID}";
        var response = await client.GetAsync(query);

        switch (response.StatusCode)
        {
            case HttpStatusCode.Unauthorized:
                throw new UnauthorizedApiAccessException("Invalid API key.");
            case HttpStatusCode.NotFound:
                throw new LocationNotFoundException("Location not found.");
            case HttpStatusCode.OK:
                var s = await response.Content.ReadAsStringAsync();
                var x = XElement.Load(new StringReader(s));

                var data = x.Descendants("time").Select(w => new WeatherForecast
                {
                    Description = w.Element("symbol").Attribute("name").Value,
                    ID = int.Parse(w.Element("symbol").Attribute("number").Value),
                    IconID = w.Element("symbol").Attribute("var").Value,
                    Date = DateTime.Parse(w.Attribute("day").Value),
                    WindType = w.Element("windSpeed").Attribute("name").Value,
                    WindSpeed = double.Parse(w.Element("windSpeed").Attribute("mps").Value),
                    WindDirection = w.Element("windDirection").Attribute("code").Value,
                    DayTemperature = double.Parse
                                     (w.Element("temperature").Attribute("day").Value),
                    NightTemperature = double.Parse
                                       (w.Element("temperature").Attribute("night").Value),
                    MaxTemperature = double.Parse
                                     (w.Element("temperature").Attribute("max").Value),
                    MinTemperature = double.Parse
                                     (w.Element("temperature").Attribute("min").Value),
                    Pressure = double.Parse(w.Element("pressure").Attribute("value").Value),
                    Humidity = double.Parse(w.Element("humidity").Attribute("value").Value)
                });

                return data;
            default:
                throw new NotImplementedException(response.StatusCode.ToString());
        }
    }
}
VB.NET
Public Class OpenWeatherMapService
    Implements IWeatherService

    Private Const APP_ID As String = "PLACE-YOUR-APP-ID-HERE"
    Private Const MAX_FORECAST_DAYS As Integer = 5
    Private client As HttpClient


    Public Sub New()
        client = New HttpClient
        client.BaseAddress = New Uri("http://api.openweathermap.org/data/2.5/")
    End Sub

    Public Async Function GetForecastAsync(location As String,
                                           days As Integer) _
                                           As Task(Of IEnumerable(Of WeatherForecast)) _
                                           Implements IWeatherService.GetForecastAsync
        If location Is Nothing Then Throw New ArgumentNullException("Location can't be null.")
        If location = String.Empty Then Throw New ArgumentException_
                      ("Location can't be an empty string.")
        If days <= 0 Then Throw New ArgumentOutOfRangeException_
                     ("Days should be greater than zero.")
        If days > MAX_FORECAST_DAYS Then Throw New ArgumentOutOfRangeException_
                  ($"Days can't be greater than {MAX_FORECAST_DAYS}.")

        Dim query = $"forecast/daily?q={location}&type=accurate&_
                    mode=xml&units=metric&cnt={days}&appid={APP_ID}"
        Dim response = Await client.GetAsync(query)

        Select Case response.StatusCode
            Case HttpStatusCode.Unauthorized
                Throw New UnauthorizedApiAccessException("Invalid API key.")
            Case HttpStatusCode.NotFound
                Throw New LocationNotFoundException("Location not found.")
            Case HttpStatusCode.OK
                Dim s = Await response.Content.ReadAsStringAsync()
                Dim x = XElement.Load(New StringReader(s))
                Dim data = x...<time>.Select(Function(w) New WeatherForecast With {
                                                 .Description = w.<symbol>.@name,
                                                 .ID = w.<symbol>.@number,
                                                 .IconID = w.<symbol>.@var,
                                                 .[Date] = w.@day,
                                                 .WindType = w.<windSpeed>.@name,
                                                 .WindSpeed = w.<windSpeed>.@mps,
                                                 .WindDirection = w.<windDirection>.@code,
                                                 .DayTemperature = w.<temperature>.@day,
                                                 .NightTemperature = w.<temperature>.@night,
                                                 .MaxTemperature = w.<temperature>.@max,
                                                 .MinTemperature = w.<temperature>.@min,
                                                 .Pressure = w.<pressure>.@value,
                                                 .Humidity = w.<humidity>.@value})
                Return data
            Case Else
                Throw New NotImplementedException(response.StatusCode.ToString())
        End Select
    End Function
End Class

In GetForecastAsync(), I'm fetching the data asynchronously and using LINQ to XML to create a collection of WeatherForecast objects. (In the VB code, I'm using XML axis properties to access XElement objects and to extract values from the necessary attributes.) A command in WeatherViewModel will be used to call this function.

C#
private ICommand _getWeatherCommand;
public ICommand GetWeatherCommand
{
    get
    {
        if (_getWeatherCommand == null) _getWeatherCommand =
                new RelayCommandAsync(() => GetWeather(), (o) => CanGetWeather());
        return _getWeatherCommand;
    }
}

public async Task GetWeather()
{
    try
    {
        var weather = await weatherService.GetForecastAsync(Location, 3);
        CurrentWeather = weather.First();
        Forecast = weather.Skip(1).Take(2).ToList();
    }
    catch (HttpRequestException ex)
    {
        dialogService.ShowNotification
        ("Ensure you're connected to the internet!", "Open Weather");
    }
    catch (LocationNotFoundException ex)
    {
        dialogService.ShowNotification("Location not found!", "Open Weather");
    }
}
VB.NET
Private Property _getWeatherCommand As ICommand
Public ReadOnly Property GetWeatherCommand As ICommand
    Get
        If _getWeatherCommand Is Nothing Then _getWeatherCommand =
                New RelayCommandAsync(Function() GetWeather(), Function() CanGetWeather())
        Return _getWeatherCommand
    End Get
End Property

Public Async Function GetWeather() As Task
    Try
        Dim weather = Await weatherService.GetForecastAsync(Location, 3)
        CurrentWeather = weather.First
        Forecast = weather.Skip(1).Take(2).ToList
    Catch ex As HttpRequestException
        DialogService.ShowNotification
        ("Ensure you're connected to the internet!", "Open Weather")
    Catch ex As LocationNotFoundException
        DialogService.ShowNotification("Location not found!", "Open Weather")
    End Try
End Function

The GetWeatherCommand is fired when the user types in a location and presses the Enter key.

XML
<TextBox x:Name="LocationTextBox" 
 SelectionBrush="{StaticResource PrimaryLightBrush}" Margin="7,0"
            VerticalAlignment="Top" 
            controls:TextBoxHelper.Watermark="Type location & press Enter"
            VerticalContentAlignment="Center"
            Text="{Binding Location, UpdateSourceTrigger=PropertyChanged}">
    <i:Interaction.Behaviors>
        <utils:SelectAllTextBehavior/>
    </i:Interaction.Behaviors>
    <TextBox.InputBindings>
        <KeyBinding Command="{Binding GetWeatherCommand}" Key="Enter"/>
    </TextBox.InputBindings>
</TextBox>

Displaying the Data

The current weather data is displayed in a user control named CurrentWeatherControl. The control displays a weather icon, temperature, weather description, maximum and minimum temperatures, and wind speed.

XML
<UserControl x:Class="OpenWeatherCS.Controls.CurrentWeatherControl"
               xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
               xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
               xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
               xmlns:local="clr-namespace:OpenWeatherCS.Controls"
               xmlns:data="clr-namespace:OpenWeatherCS.SampleData"
               mc:Ignorable="d"
               Height="180" MinHeight="180" MinWidth="300"
               d:DesignHeight="180" d:DesignWidth="300"
               d:DataContext="{d:DesignInstance Type=data:SampleWeatherViewModel, 
                               IsDesignTimeCreatable=True}">
    <Grid Background="{StaticResource PrimaryMidBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="106"/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <!-- Weather icon -->
            <Image Margin="5">
                <Image.Source>
                    <MultiBinding Converter="{StaticResource WeatherIconConverter}" 
                     Mode="OneWay">
                        <Binding Path="CurrentWeather.ID"/>
                        <Binding Path="CurrentWeather.IconID"/>
                    </MultiBinding>
                </Image.Source>
            </Image>
            <!-- Current temperature -->
            <TextBlock Grid.Column="1" 
             Style="{StaticResource WeatherTextStyle}" FontSize="60">
                <TextBlock.Text>
                    <MultiBinding Converter="{StaticResource TemperatureConverter}" 
                     StringFormat="{}{0:F0}°">
                        <Binding Path="CurrentWeather.IconID"/>
                        <Binding Path="CurrentWeather.DayTemperature"/>
                        <Binding Path="CurrentWeather.NightTemperature"/>
                    </MultiBinding>
                </TextBlock.Text>
            </TextBlock>
        </Grid>

        <!-- Weather description -->
        <Grid Grid.Row="1" Background="{StaticResource PrimaryDarkBrush}">
            <TextBlock Grid.Row="1" Style="{StaticResource WeatherTextStyle}"
                       Foreground="#FFE9F949" TextTrimming="CharacterEllipsis" Margin="5,0"
                       Text="{Binding CurrentWeather.Description}"/>
        </Grid>

        <Grid Grid.Row="2" Background="#FF14384F">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>

            <!-- Min and max temperatures -->
            <Border Background="{StaticResource PrimaryLightBrush}" 
                    SnapsToDevicePixels="True">
                <TextBlock Grid.Row="1" Style="{StaticResource WeatherTextStyle}">
                    <Run Text="{Binding CurrentWeather.MaxTemperature, 
                         StringFormat={}{0:F0}°}"/>
                    <Run Text="/" Foreground="Gray"/>
                    <Run Text="{Binding CurrentWeather.MinTemperature, 
                         StringFormat={}{0:F0}°}"/>
                </TextBlock>
            </Border>

            <!-- Wind speed -->
            <StackPanel Grid.Column="1" Orientation="Horizontal" 
                                        HorizontalAlignment="Center">
                <!-- Icon -->
                <Viewbox Margin="5">
                    <Canvas Width="24" Height="24">
                        <Path Data="M4,10A1,1 0 0,1 3,9A1,1 0 0,1 4,8H12A2,
                                 2 0 0,0 14,6A2,2 0 0,0 12,
                                 4C11.45,4 10.95,4.22 10.59,4.59C10.2,5 9.56,
                                 5 9.17,4.59C8.78,4.2 8.78,
                                 3.56 9.17,3.17C9.9,2.45 10.9,2 12,2A4,4 0 0,
                                 1 16,6A4,4 0 0,1 12,10H4M19,
                                 12A1,1 0 0,0 20,11A1,1 0 0,0 19,10C18.72,
                                 10 18.47,10.11 18.29,10.29C17.9,
                                 10.68 17.27,10.68 16.88,10.29C16.5,9.9 16.5,
                                 9.27 16.88,8.88C17.42,8.34 18.17,
                                 8 19,8A3,3 0 0,1 22,11A3,3 0 0,1 19,14H5A1,
                                 1 0 0,1 4,13A1,1 0 0,1 5,12H19M18,
                                 18H4A1,1 0 0,1 3,17A1,1 0 0,1 4,16H18A3,
                                 3 0 0,1 21,19A3,3 0 0,1 18,22C17.17,
                                 22 16.42,21.66 15.88,21.12C15.5,20.73 15.5,
                                 20.1 15.88,19.71C16.27,19.32 16.9,
                                 19.32 17.29,19.71C17.47,19.89 17.72,20 18,
                                 20A1,1 0 0,0 19,19A1,1 0 0,0 18,18Z" 
                              Fill="#FF9B8C5E" />
                    </Canvas>
                </Viewbox>
                <!-- Speed -->
                <TextBlock Grid.Column="1" Style="{StaticResource WeatherTextStyle}"
                           Text="{Binding CurrentWeather.WindSpeed, 
                           StringFormat={}{0:F0} mps}"/>
            </StackPanel>
        </Grid>
    </Grid>
</UserControl>

The rest of the forecast data is displayed in an ItemsControl using a data template which shows the day of the forecast, weather icon, maximum and minimum temperatures, and wind speed.

XML
<DataTemplate x:Key="ForecastDataTemplate">
    <DataTemplate.Resources>
        <Storyboard x:Key="OnTemplateLoaded">
            <DoubleAnimationUsingKeyFrames
                            Storyboard.TargetProperty=
                            "(UIElement.RenderTransform).(TransformGroup.Children)[0].
                            (ScaleTransform.ScaleY)" 
                            Storyboard.TargetName="RootBorder">
                <EasingDoubleKeyFrame KeyTime="0" Value="0"/>
                <EasingDoubleKeyFrame KeyTime="0:0:1" Value="1">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <QuinticEase EasingMode="EaseOut"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </DataTemplate.Resources>
    <Border x:Name="RootBorder" Height="200" Width="140" Margin="0,0,-1,0" 
     SnapsToDevicePixels="True" 
               Background="{StaticResource PrimaryDarkBrush}" 
               RenderTransformOrigin="0.5,0.5">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Border BorderThickness="1,1,1,0"
                       BorderBrush="{StaticResource PrimaryDarkBrush}" 
                       Background="{StaticResource PrimaryMidBrush}">
                <!-- Day of the week -->
                <TextBlock Style="{StaticResource WeatherTextStyle}" FontWeight="Normal"
                              Margin="5" Foreground="#FFADB982"
                              Text="{Binding Date, StringFormat={}{0:dddd}}"/>
            </Border>
            <!-- Weather icon -->
            <Border Grid.Row="1" BorderThickness="0,1,1,0"
                       BorderBrush="{StaticResource PrimaryDarkBrush}"
                       Background="{StaticResource PrimaryDarkBrush}">
                <Image MaxWidth="100" Margin="5,0,5,5">
                    <Image.Source>
                        <MultiBinding Converter="{StaticResource WeatherIconConverter}" 
                         Mode="OneWay">
                            <Binding Path="ID"/>
                            <Binding Path="IconID"/>
                        </MultiBinding>
                    </Image.Source>
                </Image>
            </Border>
            <!-- Min and max temperatures -->
            <Border Grid.Row="2" BorderThickness="1"
                       BorderBrush="{StaticResource PrimaryDarkBrush}"
                       Background="{StaticResource PrimaryLightBrush}">
                <TextBlock Grid.Row="0" Style="{StaticResource WeatherTextStyle}" 
                              Margin="5" Foreground="#FFAEBFAE">
                                <Run Text="{Binding MaxTemperature, StringFormat={}{0:F0}°}"/>
                                <Run Text="/" Foreground="Gray"/>
                                <Run Text="{Binding MinTemperature, StringFormat={}{0:F0}°}"/>
                </TextBlock>
            </Border>
            <Border Grid.Row="3" BorderThickness="1,0,1,1" 
                       BorderBrush="{StaticResource PrimaryDarkBrush}"
                       Background="{StaticResource PrimaryMidBrush}">
                <!-- Wind speed -->
                <StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Center">
                    <Viewbox Margin="0,5,5,5">
                        <Canvas Width="24" Height="24">
                            <Path Data="M4,10A1,1 0 0,1 3,9A1,1 0 0,1 4,8H12A2,2 0 0,0 14,6A2,
                                           2 0 0,0 12,4C11.45,4 10.95,4.22 10.59,
                                           4.59C10.2,5 9.56,5 9.17,
                                           4.59C8.78,4.2 8.78,3.56 9.17,3.17C9.9,
                                           2.45 10.9,2 12,2A4,4 0 0,
                                           1 16,6A4,4 0 0,1 12,10H4M19,12A1,
                                           1 0 0,0 20,11A1,1 0 0,0 19,
                                           10C18.72,10 18.47,10.11 18.29,
                                           10.29C17.9,10.68 17.27,10.68 16.88,
                                           10.29C16.5,9.9 16.5,9.27 16.88,
                                           8.88C17.42,8.34 18.17,8 19,8A3,
                                           3 0 0,1 22,11A3,3 0 0,1 19,14H5A1,
                                           1 0 0,1 4,13A1,1 0 0,1 5,12H19M18,
                                           18H4A1,1 0 0,1 3,17A1,1 0 0,1 4,
                                           16H18A3,3 0 0,1 21,19A3,3 0 0,1 18,
                                           22C17.17,22 16.42,21.66 15.88,
                                           21.12C15.5,20.73 15.5,20.1 15.88,
                                           19.71C16.27,19.32 16.9,19.32 17.29,
                                           19.71C17.47,19.89 17.72,20 18,
                                           20A1,1 0 0,0 19,19A1,1 0 0,0 18,18Z" 
                                     Fill="#FF9B8C5E" />
                        </Canvas>
                    </Viewbox>
                    <!-- Speed -->
                    <TextBlock Grid.Column="1" Style="{StaticResource WeatherTextStyle}" 
                                  Foreground="#FFAEBFAE"
                                  Text="{Binding WindSpeed, StringFormat={}{0:F0} mps}"/>
                </StackPanel>
            </Border>
        </Grid>
    </Border>
</DataTemplate>

The weather icons that are displayed are from VClouds and I'm using a converter to ensure the appropriate icon is displayed.

C#
public class WeatherIconConverter : IMultiValueConverter
{
    public object Convert(object[] values, 
           Type targetType, object parameter, CultureInfo culture)
    {
        var id = (int)values[0];
        var iconID = (string)values[1];

        if (iconID == null) return Binding.DoNothing;

        var timePeriod = iconID.ToCharArray()[2]; // This is either d or n (day or night)
        var pack = "pack://application:,,,/OpenWeather;component/WeatherIcons/";
        var img = string.Empty;

        if (id >= 200 && id < 300) img = "thunderstorm.png";
        else if (id >= 300 && id < 500) img = "drizzle.png";
        else if (id >= 500 && id < 600) img = "rain.png";
        else if (id >= 600 && id < 700) img = "snow.png";
        else if (id >= 700 && id < 800) img = "atmosphere.png";
        else if (id == 800) img = (timePeriod == 'd') ? "clear_day.png" : "clear_night.png";
        else if (id == 801) img = (timePeriod == 'd') ? 
                            "few_clouds_day.png" : "few_clouds_night.png";
        else if (id == 802 || id == 803) img = (timePeriod == 'd') ? 
                              "broken_clouds_day.png" : "broken_clouds_night.png";
        else if (id == 804) img = "overcast_clouds.png";
        else if (id >= 900 && id < 903) img = "extreme.png";
        else if (id == 903) img = "cold.png";
        else if (id == 904) img = "hot.png";
        else if (id == 905 || id >= 951) img = "windy.png";
        else if (id == 906) img = "hail.png";

        Uri source = new Uri(pack + img);

        BitmapImage bmp = new BitmapImage();
        bmp.BeginInit();
        bmp.UriSource = source;
        bmp.EndInit();

        return bmp;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, 
                                object parameter, CultureInfo culture)
    {
        return (object[])Binding.DoNothing;
    }
}
VB.NET
Public Class WeatherIconConverter
    Implements IMultiValueConverter

    Public Function Convert(values() As Object, targetType As Type, parameter As Object,
                            culture As CultureInfo) As Object _
                            Implements IMultiValueConverter.Convert
        Dim id = CInt(values(0))
        Dim iconID = CStr(values(1))

        If iconID Is Nothing Then Return Binding.DoNothing

        Dim timePeriod = iconID.ElementAt(2) ' This is either d or n (day or night)
        Dim pack = "pack://application:,,,/OpenWeather;component/WeatherIcons/"
        Dim img = String.Empty

        If id >= 200 AndAlso id < 300 Then
            img = "thunderstorm.png"
        ElseIf id >= 300 AndAlso id < 500 Then
            img = "drizzle.png"
        ElseIf id >= 500 AndAlso id < 600 Then
            img = "rain.png"
        ElseIf id >= 600 AndAlso id < 700 Then
            img = "snow.png"
        ElseIf id >= 700 AndAlso id < 800 Then
            img = "atmosphere.png"
        ElseIf id = 800 Then
            If timePeriod = "d" Then img = "clear_day.png" Else img = "clear_night.png"
        ElseIf id = 801 Then
            If timePeriod = "d" Then img = "few_clouds_day.png" _
                                Else img = "few_clouds_night.png"
        ElseIf id = 802 Or id = 803 Then
            If timePeriod = "d" Then img = "broken_clouds_day.png" _
                                Else img = "broken_clouds_night.png"
        ElseIf id = 804 Then
            img = "overcast_clouds.png"
        ElseIf id >= 900 AndAlso id < 903 Then
            img = "extreme.png"
        ElseIf id = 903 Then
            img = "cold.png"
        ElseIf id = 904 Then
            img = "hot.png"
        ElseIf id = 905 Or id >= 951 Then
            img = "windy.png"
        ElseIf id = 906 Then
            img = "hail.png"
        End If

        Dim source As New Uri(pack & img)

        Dim bmp As New BitmapImage
        bmp.BeginInit()
        bmp.UriSource = source
        bmp.EndInit()

        Return bmp
    End Function

    Public Function ConvertBack(value As Object, targetTypes() As Type,
                                parameter As Object, culture As CultureInfo) _
                                As Object() Implements IMultiValueConverter.ConvertBack
        Return Binding.DoNothing
    End Function
End Class

The choice of which icon to display is based on the list of weather condition codes specified here.

Conclusion

While the OpenWeatherMap API has some limitations for free usage, it is still quite a suitable and well documented API. Hopefully, you have learnt something useful from this article and can now more easily go about using the API with your XAML applications.

History

  • 1st August, 2013: Initial post
  • 20th August, 2017: Updated code and article

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)