Getting Weather Forecast from Yahoo!

By Sergey Malenkov, November 11, 2008

This simple JavaFX application displays weather forecasts retrieved from Yahoo! Weather RSS feed. Any location can be requested by its Location ID. For example, ENXX0004 for Tallinn (Estonia), RSXX0091 for St. Petersburg (Russia), EZXX0012 for Prague (Czech Republic), USCA0693 for Menlo Park, and USCA1018 for Santa Clara.

Understanding the Code

This example shows how to parse XML in the JavaFX programming language.

Information about weather forecasts is obtained by performing an HTTP GET request by using the JavaFX asynchronous HTTP API, as shown in Figure 1. Note that the input stream should be closed even if an error occurs.

Source Code
HttpRequest {
  location: url
  onDone: function() {
    // check for errors after parsing
    if (forecast.isEmpty()) 
      location = "Loading error"
  }
  onInput: function(input) {
    try {
      // parse input stream
      // see Figure 2
    } finally {
      input.close()
    }
  }
}.enqueue()
  

Figure 1: Requesting a Weather Forecast

The response document is parsed by using the JavaFX XML pull parser to extract information about the location and the forecast for that location. The sample code is shown in Figure 2.

Source Code
PullParser {
  var wind: Wind;
  var temp: String;
  var speed: String;
  input: input
  onEvent: function(event) {
    if ((event.type == PullParser.START_ELEMENT) and (event.qname.prefix == "yweather")) {
      if (event.qname.name == "location") {
        location = event.getAttributeValue(QName{name:"city"});
        def region = event.getAttributeValue(QName{name:"region"});
        if (0 < region.length()) {
          location = "{location}, {region}"
        }
      }
      else if (event.qname.name == "units") {
        temp  = event.getAttributeValue(QName{name:"temperature"});
        speed = event.getAttributeValue(QName{name:"speed"})
      }
      else if (event.qname.name == "wind") {
        wind = Wind {
          angle: event.getAttributeValue(QName{name:"direction"})
          speed: event.getAttributeValue(QName{name:"speed"})
          unit:  speed
        }
      }
      else if (event.qname.name == "condition") {
        insert Forecast {
          day: "Now"
          high: event.getAttributeValue(QName{name:"temp"})
          text: event.getAttributeValue(QName{name:"text"})
          code: event.getAttributeValue(QName{name:"code"})
          unit: temp
          wind: wind
        } into forecast
      }
      else if (event.qname.name == "forecast") {
        insert Forecast {
          day:  event.getAttributeValue(QName{name:"day"})
          low:  event.getAttributeValue(QName{name:"low"})
          high: event.getAttributeValue(QName{name:"high"})
          text: event.getAttributeValue(QName{name:"text"})
          code: event.getAttributeValue(QName{name:"code"})
          unit: temp
        } into forecast
      }
    }
  }
}.parse()
  

Figure 2: Parsing the Server Response

Customizing the Code

The Config class contains the following variables that affect the appearance of the weather widget.

WIDTH
Preferred width of the widget, but actual width depends on scene width
HEIGHT
Height of the widget
SPACE
Default spacing between components
SMALL_FONT
Font used for small text: forecast name, temperature, and wind speed
LARGE_FONT
Font used for large text: location and short description
(it is calculated automatically)
ROUND
Diameter of the arc at the four corners of the rectangles
FONT_COLOR
Foreground color used for all text
DARK_COLOR
Main background color
LIGHT_COLOR
Lighter background color for creating the gradient filling

Source Code
public def WIDTH = 320;
public def HEIGHT = 170;

public def SPACE = 2;
public def SPACE_DOUBLE = 2 * SPACE;

public def SMALL_FONT = Font {
  size: 12
}
public def LARGE_FONT = Font {
  size: SPACE + 2 * SMALL_FONT.size
}

public def ROUND = SMALL_FONT.size;
public def OFFSET = HEIGHT - 6 * SPACE - 2 * LARGE_FONT.size;

public def FONT_COLOR  = Color.WHITE;
public def DARK_COLOR  = Color.DARKBLUE;
public def LIGHT_COLOR = Color.LIGHTBLUE;
  

Figure 3: Variables to Change