Tuesday Jun 17, 2008

JavaFX Script and JSON Weather service

Brian Goetz recently completed the JavaFX script class "javafx.async.AsyncJsonCall" that allows one to issue a JSON webservice call in the background. Once the call is complete, a call back function "onDone" is called that indicates whether or not the call succeeded. This blog includes an example of how this JSON call might be done.

I decided to use the GeoNames JSON webservices and more particularily the "Weather Station with most recent weather observation". This service provides the current weather conditions at the particular weather station provided. The weather station is identified by the International Civil Aviation Organization (ICAO) code. Typically, in the US, this code is the letter 'K' followed by the more popular IATA code for the airport. For example, JFK international in New York has an IATA code of "JFK" so the ICAO code is KJFK. In the example below, I used my home airport of Orlando, Florida USA which has the ICAO code, "KMCO".

Click here for more details on on the GEO Names JSON Web Services and for the ICAO codes. For more information on the JSON grammar, check out www.json.org.

First, I declare a variable to hold the URL to the GEONames Web Service. Just to keep things simple for now I have hard coded in the ICAO code for Orlando, "KMCO".

"http://ws.geonames.org/weatherIcaoJSON?ICAO=KMCO"; 
Then, I declare an object to hold the values from the JSON Object that is returned. For now, I have to manually populate this object within my code, but eventually there will be mechanism in the JavaFX Script JSON framework to do this automatically.

Next, I declare the "AsyncJsonCall" object literal, passing in the "url", and providing a function implementation for the "onDone" attribute. "onDone" will be called once the call is completed and the "success" boolean parameter indicates whether it succeeded or not. If it failed,  the attribute "failureText" inside the "AsyncJsonCall" object will contain a string describing the failure.
If the call succeeds, then the "document" attribute  from the "AsyncJsonCall" object will contain the top level javafx.json.JSONObject. From this object,  I fetch the "weatherObservation" object, and from that, fetch all the members for my "weather" class.

To show the power of JavaFX Script binding, I draw a simple Frame, and output the members of my weather object. Because "AsyncJsonCall" runs in the background, there may be a delay before the actual data is displayed. Using the JavaFX binding feature, I bound the members of my "weather" object to the "Text" gui nodes, so that when the "weather" object eventually is populated, the screen will update automatically.

An example of the JSON Object that is returned:

{
  "weatherObservation":
    {
      "clouds":"scattered clouds",
      "weatherCondition":"n/a",
      "observation":"KMCO 171753Z 27010KT 10SM SCT045 SCT055 BKN140 BKN250 32/19 A2993 RMK AO2 SLP133 T03220189 10322 2024
4 58018",
      "windDirection":270,
      "ICAO":"KMCO",
      "seaLevelPressure":1013.3,
      "elevation":29,
      "countryCode":"US",
      "lng":-81.3333333333333,
      "temperature":"32.2",
      "dewPoint":"18.9",
      "windSpeed":"10",
      "humidity":45,
      "stationName":"Orlando, Orlando International Airport",
      "datetime":"2008-06-17 17:53:00",
      "lat":28.4166666666667
    }
}

The JavaFX Code to process the JSON Object returned from the JSON Weather Service:

 =============================

import javafx.async.\*;
import javafx.json.\*;
import java.net.URL;
import java.lang.System;
import javafx.gui.\*;

// ICAO code in US is typically 'K' + IATA airport code
// MCO is Orlando FL US IATA code, so ICAO is KMCO.
// New York - JFK is KJFK.
// See http://en.wikipedia.org/wiki/List_of_airports_by_ICAO_code
var url = "http://ws.geonames.org/weatherIcaoJSON?ICAO=KMCO";

class Weather {
    public attribute station:String;
    public attribute clouds:String;
    public attribute windDirection:Number;
    public attribute windSpeed:Number;
    public attribute temperature:Number;
    public attribute dewPoint:Number;
    public attribute humidity:Number;
    public attribute observation:String;
}
var weather:Weather = Weather{};
var call:AsyncJsonCall;
call = AsyncJsonCall{
    url: url 
    onDone: function(success : Boolean) : Void {
        System.out.println("JSON Call is done: result = {success}");
        if(success) {
            var json = call.document;
            var observation = json.getValue("weatherObservation") as JSONObject;
            var pair:Pair;
            weather.station = '{observation.getValue("stationName")}';
            weather.clouds = '{observation.getValue("clouds")}';
            weather.windDirection = observation.getPair("windDirection").getValueAsNumber();
            weather.windSpeed = observation.getPair("windSpeed").getValueAsNumber();
            weather.temperature = observation.getPair("temperature").getValueAsNumber();
            weather.dewPoint = observation.getPair("dewPoint").getValueAsNumber();
            weather.humidity = observation.getPair("humidity").getValueAsNumber();
            weather.observation = '{observation.getValue("observation")}';
        }else {
            System.out.println("failure = {call.failureText}");
        }
    }
}

Frame {
    title: "JSON Weather";
    closeAction: function() { java.lang.System.exit(0); }
    visible: true
    width: 500
    height: 500
    content: Canvas {
        content: VBox {
            spacing: 10
            content: [
                HBox {
                    content: [
                        Text {translateX:10 translateY:10 content: "Station:"},
                        Text {
                            translateX:10 
                            translateY:10 
                            content: bind weather.station 
                            fill: Color.BLUE
                        }
                    ]
                },
                HBox {
                    content: [
                        Text {translateX:10 translateY:10 content: "Clouds:"},
                        Text {
                            translateX:10 
                            translateY:10 
                            content: bind weather.clouds 
                            fill: Color.BLUE
                        }
                    ]
                }, 
                HBox {
                    content: [
                        Text {translateX:10 translateY:10 content: "Wind Direction:"},
                        Text {
                            translateX:10 
                            translateY:10 
                            content: bind "{weather.windDirection} degrees" 
                            fill: Color.BLUE}
                    ]
                },
                HBox {
                    content: [
                        Text {translateX:10 translateY:10 content: "Wind Speed:"},
                        Text {
                            translateX:10 
                            translateY:10 
                            content: bind "{weather.windSpeed} knots" 
                            fill: Color.BLUE}
                    ]
                }, 
                HBox {
                    content: [
                        Text {translateX:10 translateY:10 content: "Temperature:"},
                        Text {
                            translateX:10 
                            translateY:10 
                            content: bind "{weather.temperature}C degrees" 
                            fill: Color.BLUE}
                    ]
                }, 
                                HBox {
                    content: [
                        Text {translateX:10 translateY:10 content: "Dew Point:"},
                        Text {
                            translateX:10 
                            translateY:10 
                            content: bind "{weather.dewPoint}C degrees" 
                            fill: Color.BLUE}
                    ]
                }, 
                HBox {
                    content: [
                        Text {translateX:10 translateY:10 content: "Humidity:"},
                        Text {
                            translateX:10 
                            translateY:10 
                            content: bind "{weather.humidity}%" 
                            fill: Color.BLUE}
                    ]
                }, 
                HBox {
                    content: [
                        Text {translateX:10 translateY:10 content: "METAR Observation:"},
                        Text {
                            translateX:10 
                            translateY:10 
                            content: bind "{weather.observation}" 
                            fill: Color.RED}
                    ]
                },                 
            ]
        }
    }
    
}

=============================

There are a few other features of the  "javafx.async.AsyncJsonCall"  class that are useful, like showing the progress of the webservice call.  I will demonstrate these in a future blog post.


        
    

Saturday May 03, 2008

JavaFX Script and JDBC

Describes a new JDBC framework for JavaFX Script.[Read More]

Sunday Apr 20, 2008

Bounding Bridge

Recently, I have been heads down preparing JavaFX demos for JavaONE. I ran into an interesting binding issue that I will share with you.
I call this the "Bounding Bridge".

First I have a Main class that contains a "matchCase" boolean attribute. Next, I have a Search class that also contains a "matchCase" boolean attribute. The Search class contains a CheckBox gui object that has a boolean selected field. What I want to have happen is that if the user selected the CheckBox then the "matchCase" attribute in Main will be true. Also, if the program sets the "matchCase" attribute in Main the CheckBox should be checked.

 Broken Binding

My first implementation looked like this (stripped down to essentials):

========== Main.fx =========

import javax.swing.\*;
import javafx.gui.\*;
public class Main extends Component {
    public attribute matchCase: Boolean;
    public function createJComponent(): JComponent {
        var canvas = Canvas {
            content: Search {
               matchCase: bind matchCase with inverse
            }
        };
        canvas.getJComponent();
    }
}
Frame {
    visible: true
    title: "JavaFXPad"
    content: Main{}
}

========== Search.fx ========= 

import javafx.gui.\*;

public class Search extends CustomNode {
    public attribute matchCase: Boolean;
   
    public function create(): Node {
           ComponentView {
                component: CheckBox {
                    text: "Match Case",
                    font: Font.font("VERDANA", FontStyle.BOLD, 11)
                    selected: bind matchCase with inverse
                }
            }
    }
 }

===================

This all compiles, however when I run it I get:

Exception in thread "main" com.sun.javafx.runtime.BindingException: Both components of bijective bind must be mutable

The error is that the targets of the binds from CheckBox and  Main both point to the same "matchCase" attribute in Search so there is a collision  of sorts. To fix this, I implemented a "bridge" pattern.

The only class that needs to change is Search . The bridge consists of introducing a new boolean attribute, "pMatchCase" that in turn is bound to the selected attribute in CheckBox.  The bridging is done by adding an on replace block to both the "matchCase" and "pMatchCase" attributes, with each respective on replace block causing the other attribute to be updated when it is updated.

Bridged Binding

========== Search.fx =========

import javafx.gui.\*;
import javax.swing.\*;

public class Search extends CustomNode {
    public attribute matchCase: Boolean on replace {
        pMatchCase = matchCase;
    };
    public attribute pMatchCase: Boolean on replace {
        matchCase = pMatchCase;
    };
   
    public function create(): Node {
           ComponentView {
                component: CheckBox {
                    text: "Match Case",
                    font: Font.font("VERDANA", FontStyle.BOLD, 11)
                    selected: bind pMatchCase with inverse
                }
            }
    }
 }

 

===================
 

 

Monday Mar 31, 2008

JavaFX Scripting api.

I read with interest Michael Heinrichs' series of posts showing how to interact with JavaFX Script objects from Java code using the Scripting API. To dig a little deeper, it is also possible to retrieve error messages from the compiled JavaFX Scripts.

The Scripting API, formally known as Scripting for the Java™ Platform, provides the basic framework for implementing the compilation and invocations as described in Micheal's blog entries. But what happens if the script has a compilation error or warning. How does your Java code get these messages. The answer is the javax.tools package with support for Diagnostics.

Both the javax.tools and javax.script packages are included in Java 6. For compatibility with Java 5, these packages are built into the JavaFX libraries. To use the Diagnostic feature, you must first cast the javax.script.ScriptEngine down to a JavaFXScriptEngine as Michael has done in his examples. The JavaFXScriptEngine contains versions of compile and eval methods that take a DiagnosticCollector argument. This object allows the JavaFX compiler to store any warnings, or errors encountered during the compilation phase.

To illustrate this, here is Michael's Java example from his blog entry, Creating JavaFX objects in Java programs, Code Sample 2, with modifications for capturing the Diagnostics. I have purposely changed the attribute in the MyJavaFXClass Object Literal declaration from the valid "property" attribute to an invalid "text" attribute shown in red in the code sample.

 1 import javax.script.ScriptEngineManager;
2 import javax.script.ScriptEngineManager;
3 import com.sun.javafx.api.JavaFXScriptEngine;
4 import javax.tools.Diagnostic;
5
import javax.tools.DiagnosticCollector;
6 import java.util.Iterator;
7 import java.util.List;
8 import javax.script.ScriptException;
9
10 public class Main {
11
12 public static void main(String[] args) {
13 ScriptEngineManager manager = new ScriptEngineManager();
14 JavaFXScriptEngine fxEngine =
15 (JavaFXScriptEngine) manager.getEngineByName("javafx");
16
17 DiagnosticCollector diags = new DiagnosticCollector();
18 try {
19 Object o = fxEngine.eval("MyJavaFXClass { text: \\"JavaFX class created in Java\\" }",
20 diags);
21 fxEngine.invokeMethod(o, "printProperty");
22
23 } catch (ScriptException e) {
24 List<Diagnostic> errorList = diags.getDiagnostics();
25 Iterator<Diagnostic> iter = errorList.iterator();
26 while (iter.hasNext()) {
27 Diagnostic d = iter.next();
28 System.out.println(
29 d.getKind().toString() + ": Line:" +
30 d.getLineNumber() + " Col:" + d.getColumnNumber() + "\\n'" +
31 d.getMessage(null) + "'");
32
}
33 } catch (Exception ex) {
34 ex.printStackTrace();
35 }
36 }
37 }

Code Sample 2: Constructing MyJavaFXClass in Java program, added Diagnostics

When this example is run, the following out appears:

ERROR: Line:1 Col:140
'cannot find symbol
symbol  : variable text
location: class MyJavaFXClass'


 

About

jimclarke

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today