Title: Get a weather forecast from openweathermap.org in C#
This example displays a weather forecast from openweathermap.org. To get a forecast or other data, you build a URL describing your request and navigate to it. You then parse the returned XML or JSON data to see what openweathermap.org has to say.
The first step in using openweathermap.org is to get an API ID. You can get one for free here. You will need to sign up for a free account.
After you get an API ID, you can use it in your URLs.
This example begins with the following code.
// Enter your API key here.
// Get an API key by making a free account at:
// http://home.openweathermap.org/users/sign_in
private const string API_KEY = "12345678901234567890123456789012";
// Query URLs. Replace @LOC@ with the location.
private const string CurrentUrl =
"http://api.openweathermap.org/data/2.5/weather?" +
"@QUERY@=@LOC@&mode=xml&units=imperial&APPID=" + API_KEY;
private const string ForecastUrl =
"http://api.openweathermap.org/data/2.5/forecast?" +
"@QUERY@=@LOC@&mode=xml&units=imperial&APPID=" + API_KEY;
This code defines your API ID. Replace the number shown here with your ID.
The two string constants hold template URLs to get current weather conditions and forecast conditions. The code will later replace the pieces @QUERY@ and @LOC@ with values for a specific query.
When the program starts, the following Load event handler executes.
// Query codes.
private string[] QueryCodes = { "q", "zip", "id", };
// Fill in query types. These should match the QueryCodes.
private void Form1_Load(object sender, EventArgs e)
{
cboQuery.Items.Add("City");
cboQuery.Items.Add("ZIP");
cboQuery.Items.Add("ID");
cboQuery.SelectedIndex = 1;
}
The QueryCodes array holds query strings to get data by city name (q), zip code, or ID.
The form's Load event handler initializes the cboQuery ComboBox control with values corresponding to the query codes. Those values must match up with the ones in the QueryCodes array.
When you click Forecast, the following code executes.
// Get a forecast.
private void btnForecast_Click(object sender, EventArgs e)
{
// Compose the query URL.
string url = ForecastUrl.Replace("@LOC@", txtLocation.Text);
url = url.Replace("@QUERY@", QueryCodes[cboQuery.SelectedIndex]);
// Create a web client.
using (WebClient client = new WebClient())
{
// Get the response string from the URL.
try
{
DisplayForecast(client.DownloadString(url));
}
catch (WebException ex)
{
DisplayError(ex);
}
catch (Exception ex)
{
MessageBox.Show("Unknown error\n" + ex.Message);
}
}
}
This code takes the forecast URL template and replaces @LOC@ with the location that you entered. It then replaces @QUERY@ with the appropriate query code.
Next, the creates a WebClient and makes it navigate to the URL. The code then either displays the forecast or an error message. I'll say more about those methods in a moment. First, the following shows a typical response somewhat abbreviated.
<weatherdata>
<location>
<name>Washington D.C.</name>
<type/>
<country>US</country>
<timezone/>
<location altitude="0" latitude="38.895" longitude="-77.0367" geobase="geonames" geobaseid="420008701"/>
</location>
<credit/>
<meta>
<lastupdate/>
<calctime>0.0043</calctime>
<nextupdate/>
</meta>
<sun rise="2018-07-30T10:08:00" set="2018-07-31T00:20:33"/>
<forecast>
<time from="2018-07-30T15:00:00" to="2018-07-30T18:00:00">
<symbol number="500" name="light rain" var="10d"/>
<precipitation unit="3h" value="0.11" type="rain"/>
<windDirection deg="124.5" code="SE" name="SouthEast"/>
<windSpeed mps="10.36" name="Fresh Breeze"/>
<temperature unit="imperial" value="74.68" min="74.68" max="76.74"/>
<pressure unit="hPa" value="1026.45"/>
<humidity value="82" unit="%"/>
<clouds value="overcast clouds" all="92" unit="%"/>
</time>
<time from="2018-07-30T18:00:00" to="2018-07-30T21:00:00">
<symbol number="500" name="light rain" var="10d"/>
<precipitation unit="3h" value="0.0075" type="rain"/>
<windDirection deg="125.004" code="SE" name="SouthEast"/>
<windSpeed mps="10.27" name="Fresh Breeze"/>
<temperature unit="imperial" value="73.36" min="73.36" max="74.9"/>
<pressure unit="hPa" value="1025.8"/>
<humidity value="83" unit="%"/>
<clouds value="broken clouds" all="80" unit="%"/>
</time>
...
</forecast>
</weatherdata>
The following code parses the data to get the forecast.
// Display the forecast.
private void DisplayForecast(string xml)
{
// Load the response into an XML document.
XmlDocument xml_doc = new XmlDocument();
xml_doc.LoadXml(xml);
// Get the city, country, latitude, and longitude.
XmlNode loc_node = xml_doc.SelectSingleNode("weatherdata/location");
txtCity.Text = loc_node.SelectSingleNode("name").InnerText;
txtCountry.Text = loc_node.SelectSingleNode("country").InnerText;
XmlNode geo_node = loc_node.SelectSingleNode("location");
txtLat.Text = geo_node.Attributes["latitude"].Value;
txtLong.Text = geo_node.Attributes["longitude"].Value;
txtId.Text = geo_node.Attributes["geobaseid"].Value;
lvwForecast.Items.Clear();
char degrees = (char)176;
foreach (XmlNode time_node in xml_doc.SelectNodes("//time"))
{
// Get the time in UTC.
DateTime time =
DateTime.Parse(time_node.Attributes["from"].Value,
null, DateTimeStyles.AssumeUniversal);
// Get the temperature.
XmlNode temp_node = time_node.SelectSingleNode("temperature");
string temp = temp_node.Attributes["value"].Value;
ListViewItem item = lvwForecast.Items.Add(time.DayOfWeek.ToString());
item.SubItems.Add(time.ToShortTimeString());
item.SubItems.Add(temp + degrees);
}
}
This method loads the returned XML data into an XmlDocument object. It then gets various elements in the object to find the location's city name, country, latitude, longitude, and ID.
NOTE: City names can be hard to use. Typically, you use the city's name and country name separated by a comma as in Springfield, US. Unfortunately, there doesn't seem to be a good way to differentiate among different cities with the same name. A better approach is to use city ID or ZIP code.
After displaying the basic information, the program loops through the XML document's time elements. For each of those elements, the method finds the from value. All openweathermap time values are in UTC, so the code parses the time as a universal time. Later, when the code gets the time's value, it gets it in local time so it makes more sense.
Next, the code gets the entry's temperature element. It then adds the weekday, time, and temperature to the lvwForecast ListView control.
If there is an error, the web response object contains additional error information. The following code shows how the program displays that information.
// Display an error message.
private void DisplayError(WebException exception)
{
try
{
StreamReader reader = new StreamReader(exception.Response.GetResponseStream());
XmlDocument response_doc = new XmlDocument();
response_doc.LoadXml(reader.ReadToEnd());
XmlNode message_node = response_doc.SelectSingleNode("//message");
MessageBox.Show(message_node.InnerText);
}
catch (Exception ex)
{
MessageBox.Show("Unknown error\n" + ex.Message);
}
}
This code creates a StreamReader attached to the response stream. It reads the text from the reader and uses it to create an XML document. It then displays that object's message element.
Unfortunately, the response seems to sometimes return XML code (as when the city doesn't exist) and sometimes JSON code (as when the API ID is invalid). If you find a way to make it always return XML code, let me know. Or if you modify this method to handle both XML and JSON, which I'm too lazy to do right now, let me know.
Download the example to experiment with it and to see additional details.
|