Skip to main content

Groovy Weather: POGOs, Gson, and Open Weather by Ken Kousen of Making Java Groovy + 45% savings!

Posted by manning_pubs on January 7, 2014 at 4:39 AM PST

Groovy Weather: POGOs, Gson, and Open Weather" by Ken Kousen of Making Java Groovy is 45% off with promotional code kousenjn at manning.com.

The Open Weather Map web site provides weather information as a RESTful web service in JSON form. In this blog post, I show how to access the site using Groovy and convert the results into Groovy classes using Gson, the JSON parser from Google. The code includes Spock tests and a Gradle build file.

Groovy

Groovy is one of the family of non-Java languages that compile to bytecodes that run on the JVM. Groovy, Scala, and Clojure are all rising in popularity these days, along with hybrid languages like JRuby and Jython. Groovy is by far the closest to Java in that set, making it easy for Java developers to learn and use. Groovy is unusual in that it is not design to replace Java; virtually any Groovy project of any significant size includes Java code in it as well.

Here is a brief list of Groovy features, most of which I’ll demonstrate in the code to come (in no particular order):

  • Groovy is optionally typed. You can declare a variable of type String or double or Employee if you want, or you can use the keyword def if you don't know or care about the type. Groovy supports duck typing ("if it walks like a duck and it quacks like a duck, it’s a duck") at run time, meaning I can invoke any method I want on a def reference and if the method exists, I’m good.

  • Groovy uses closures, which can be thought of as method bodies with parameters. Closures are first-class objects in Groovy, and can be declared, passed into methods as arguments, access other variables currently in scope, and more. They are similar to the lambdas introduced in Java 8, with some minor differences that won't affect the code in this article.

  • Attributes in Groovy classes are private by default, while methods are public by default. Classes are public by default as well.

  • Groovy automatically generates public getter and setter methods for each attribute in a class.

  • Groovy supplies a native syntax for collections.

  • The map syntax is also available in constructors to set attribute values on new objects.

  • Groovy does runtime metaprogramming through a Meta-Object Protocol (MOP) and compile-time metaprogramming through Abstract Syntax Tree (AST) Transformations.

The combination of all these features makes code in Groovy much shorter and easier to read than the corresponding Java versions.

Accessing the Open Weather Map service

The Groovy JDK is the collection of Java library classes that have been enhanced by Groovy metaprogramming to have all the methods you wish were there in Java. As an example, the java.lang.String class in the Groovy JDK has a method called toURL that converts the string to an instance of the java.net.URL class. Then, the java.net.URL class in the Groovy JDK has a method called getText that retrieves the data at that particular URL as a string.

Given that, here is how you access the Open Weather Map site with an HTTP GET request:

[prettify]'http://api.openweathermap.org/data/2.5/weather?q=marlborough,ct'.toURL().text[/prettify]

Groovy supports both single-quoted and double-quoted strings. The former are simply instances of java.lang.String. The latter are Groovy strings that support interpolation, which I'll use later. I'm also using the standard Groovy idiom of accessing a property (here, the text property of URL), which Groovy automatically converts to invoking the getter method (here, getText).

The result is a JSON response, returned as a single, unformatted string. Let me modify the code above slightly to print out something much easier to read. I'll take advantage of a Groovy library class called groovy.json.JsonOutput to do the formatting.

import groovy.json.*

String url = 'http://api.openweathermap.org/data/2.5/weather?q=marlborough,ct'
String jsonTxt = url.toURL().text
println JsonOutput.prettyPrint(jsonTxt)

The result is shown here:

{
    "coord": {
        "lon": -72.46,
        "lat": 41.63
    },
    "sys": {
        "message": 1.0819,
        "country": "United States of America",
        "sunrise": 1389096985,
        "sunset": 1389130583
    },
    "weather": [
        {
            "id": 800,
            "main": "Clear",
            "description": "Sky is Clear",
            "icon": "01d"
        }
    ],
    "base": "gdps stations",
    "main": {
        "temp": 261.56,
        "humidity": 30,
        "pressure": 1016,
        "temp_min": 260.93,
        "temp_max": 263.15
    },
    "wind": {
        "speed": 2.06,
        "gust": 7.2,
        "deg": 232
    },
    "clouds": {
        "all": 0
    },
    "dt": 1389122020,
    "id": 4835003,
    "name": "Hartford",
    "cod": 200
}

The JSON response isn't terribly complicated, as JSON responses go, but it does distribute its data over a fair number of sub-objects.

Parsing the JSON response using Gson

Normally when I want to parse JSON data, I use the groovy.json.JsonSlurper class from the Groovy API. That class has a parseText method, which I can use to convert the above structure into a nested set of maps and lists.

The code looks like:

import groovy.json.*

String url = 'http://api.openweathermap.org/data/2.5/weather?q=marlborough,ct'
String jsonTxt = url.toURL().text
println new JsonSlurper().parseText(jsonTxt)

The result in this case is:

[id:4835003, dt:1389122020, clouds:[all:0], coord:[lon:-72.46, lat:41.63], wind:[gust:7.2, speed:2.06, deg:232], cod:200, sys:[message:1.0819, sunset:1389130583, sunrise:1389096985, country:United States of America], name:Hartford, base:gdps stations, weather:[[id:800, icon:01d, description:Sky is Clear, main:Clear]], main:[humidity:30, pressure:1016, temp_max:263.15, temp_min:260.93, temp:261.56]]

That’s not bad, and if all I wanted was a value or two, I could easily get it from this structure. For example, if I want the current temperature, I can add one line to the above script:

import groovy.json.*

String url = 'http://api.openweathermap.org/data/2.5/weather?q=marlborough,ct'
String jsonTxt = url.toURL().text
def json = new JsonSlurper().parseText(jsonTxt)
println json.main.temp

That returns 261.56, the current temperature (in Kelvin, of all things; a problem I'll deal with later). I want to convert the whole structure to a set of Groovy classes, however, and for that I'll return to the Java world. One of the best JSON parsers around is supplied by Google as the Google Gson project.

Converting JSON to Groovy classes using Gson

I added the Gson library through a Gradle build (discussed below), but you can simply download the jar file if you prefer. The library includes a class called com.google.gson.Gson, which has a fromJson method. The fromJson method takes two arguments: the JSON string, and a class that maps to it. The method deserializes the JSON response into an instance of the specified class. The javadocs can be found here.

What I need, therefore, is a Groovy class that maps to the required JSON structure. That's not hard, but not trivial either, because the JSON response has a fair amount of nested structure.

Fortunately, Groovy POGOs (Plain Old Groovy Objects) makes the implementation straightforward. In fact, to keep everything together I decided to put all the required Groovy classes into a single file, called Model.groovy. In Java, you can only have one public class per source file. In Groovy, you can put in as many as you like. They all compile to separate .class files, of course.

With that in mind, here’s the Model.groovy file.

class Model {
    Long dt
    Long id
    String name
    Integer cod

    Coordinates coord
    Main main
    System sys
    Wind wind
    Clouds clouds
    Weather[] weather
}

class Main {
    BigDecimal temp
    BigDecimal humidity
    BigDecimal pressure
    BigDecimal temp_min
    BigDecimal temp_max
}

class Coordinates {
    BigDecimal lat
    BigDecimal lon

    String toString() { "($lat, $lon)" }
}

class Weather {
    Integer id
    String main
    String description
    String icon
}

class System {
    String message
    String country
    Long sunrise
    Long sunset
}

class Wind {
    BigDecimal speed
    BigDecimal deg
}

class Clouds {
    BigDecimal all
}

The JSON structure is as untyped as it gets. All the type information is something I inferred from the documentation, which is a bit thin, or, in the case of the nested data types, made up to match the structure. Gson cares about the names of the attributes rather than the types, because the names have to match the keys pointing to the sub-objects in the JSON structure.

(You might wonder if there was an automated tool that could generate the class structure, but if you’ve ever worked with similar tools in the Object-Relational Mapping space you know that such tools often generate as many problems as the solve. Plus, there's no type information in the JSON input, so some developer intervention is needed anyway.)

This is actually enough information to make the conversion process work, except for the fact that I need to do some processing of the actual data. For the record, though, I can get some output from just this. To demonstrate that, I merely add the following to all the class definitions in the model (except for the Coordinates class, where I already provided a reasonable toString method):

import groovy.transform.*
import com.google.gson.Gson

@ToString(includeNames=true)
class Model {
  ...
}

String url = 'http://api.openweathermap.org/data/2.5/weather?q=marlborough,ct'
String jsonTxt = url.toURL().text
Gson gson = new Gson()
println gson.fromJson(jsonTxt, Model)

The result is:

Model(dt:1388695625, id:4835003, name:Hartford, cod:200, coord:(41.63, -72.46), main:Main(temp:264.61, humidity:78, pressure:1015, temp_min:262.04, temp_max:268.15), sys:System(message:0.1053, country:United States of America, sunrise:1388665004, sunset:1388698297), wind:Wind(speed:6.7, deg:10), clouds:Clouds(all:90), weather:[Weather(id:600, main:Snow, description:light snow, icon:13d), Weather(id:721, main:Haze, description:haze, icon:50d)])

The @ToString annotation triggers an AST transformation that generates a toString method for each class. The includeNames property of the annotation makes the toString method print out the names of the properties along with their values.

Note one other subtlety, by the way. In Java, the second argument to the fromJson method would have to be Model.class. In Groovy, I can simply use Model. The class reference is assumed automatically.

Now that I have the results in the form of a set of Groovy classes, I can try to make sense of the data.

Converting the data to desired units

As mentioned above, the returned temperatures are all in Kelvin. The documentation also says that the wind speed is in mps (meters per second), and the date values are all in "time of data receiving in unixtime GMT". Since I'm planning on using this in the US, I'd like to convert to English units (sorry). I also need to do something about the dates and times.

All of the conversion methods are added to the Model class.

The temperature conversion is easy enough:

def convertTemp(temp) {
    9 * (temp - 273.15) / 5 + 32
}

In Groovy, the return keyword is optional. The convertTemp method takes a single, untyped argument (presumably of type BigDecimal, but I chose not to worry about it) and because of the def keyword in the return type the method returns pretty much anything. In fact, after subtracting 273.15 to convert from Kelvin to Celsius, the method then converts Celsius to Fahrenheit in the usual way.

The date conversion comes from the fact that Unix time is measured in seconds elapsed from the beginning of the epoch, which began at midnight, January 1, 1970 GMT. Java already has a constructor in the java.util.Date class that takes a long representing the number of milliseconds elapsed from that moment. The conversion to a Java Date is therefore:

def convertTime(t) {
    new Date(t*1000)  // Unix time in sec, Java time in ms
}

The method takes a Unix time value in seconds, multiplies by 1000 to make it milliseconds, and plugs it into the appropriate [prettify]Date[/prettify] constructor.

(As an aside, Java 8 finally (finally!) deals with the mess that is date/time processing in the Java SDK. I expect that to drive adoption as much or more than the cool lambdas, functional methods, lazy collections, and so on.)

The final conversion is to change meters per second into miles per hour. Back when I was in high school (and dinosaurs roamed the Earth), I learned something called the "Factor Label Method", which meant I only had to remember a single length conversion. At the time, I learned that there are 2.54 centimeters in 1 inch. With that in mind, the conversion is:

def convertSpeed(mps) {
    // 1 m/sec * 60 sec/min * 60 min/hr * 100 cm/m * 1 in/2.54 cm * 1 ft/12 in * 1 mi/5280 ft
    mps * 60 * 60 * 100 / 2.54 / 12 / 5280
}

Obviously, all three of those methods need to be tested. I'll get to that in a moment, but first let me add a series of getter methods that use the conversions.

class Model {
  // ... as before ...

  def getTime() { convertTime dt }
  def getTemperature() { convertTemp main.temp }
  def getLow() { Math.floor(convertTemp(main.temp_min)) }
  def getHigh() { Math.ceil(convertTemp(main.temp_max)) }
  def getSunrise() { convertTime sys.sunrise }
  def getSunset() { convertTime sys.sunset }
  def getSpeed() { convertSpeed wind.speed }
}

Again, I’m using optional parentheses and using def as the return types only to make the code cleaner.

The last part of the puzzle is to provide a toString method for Model that takes advantage of Groovy’s multiline strings to build a nice, formatted output. Here is that method:

String toString() {
    """
    Name         : $name
    Time         : $time
    Location     : $coord
    Weather      : ${weather[0].main} (${weather[0].description})
    Icon         : http://openweathermap.org/img/w/${weather[0].icon}.png
    Current Temp : $temperature F (high: $high F, low: $low F)
    Humidity     : ${main.humidity}%
    Sunrise      : $sunrise
    Sunset       : $sunset
    Wind         : $speed mph at ${wind.deg} deg
    Cloudiness   : ${clouds.all}%
    """
}

Groovy uses three double-quotes for a multi-line string that allows interpolation. The syntax for interpolation is ${…}, but if the evaluation is based on just a single variable you can leave out the braces. All of the resulting whitespace is significant, carriage returns and all.

Rather than drive this system from a script, here is the OpenWeather class:

import com.google.gson.Gson

class OpenWeather {
    String base = 'http://api.openweathermap.org/data/2.5/weather?q='
    Gson gson = new Gson()

    String getWeather(city='Marlborough', state='CT') {
        String jsonTxt = "$base$city,$state".toURL().text
        gson.fromJson(jsonTxt, Model).toString()
    }
}

The other interesting addition here is the use of Groovy’s default arguments. If I supply a city and a state, they'll be used, but if not the values will take the arguments listed in the method declaration.

So far, so good, but now I really need those tests.

Spock tests for the Open Weather system

The only real business logic in this system is in the type conversions added to the Model class. To test them, I'll use the Spock testing framework. Note that the URL immediately redirects to a Google code project at https://code.google.com/p/spock/, but that if you really want to understand the framework you need to look at the associated Github repository at https://github.com/spockframework/spock .

FYI, according to Peter Neiderweiser, the creator of the Spock framework, the word Spock comes from the words "specification" and "mock". That sounds good, but I don’t buy it. I think he just wanted to call it Spock and the rest is rationalization (of which I approve). It does have the distinction of being the most popular Groovy-based project that doesn't start with a G (in contrast to Grails, Griffon, Gradle, Gaelyk, GPars, …).

Spock tests are all created by inheritance by extending the spock.lang.Specification class. The tests themselves are written in the form of blocks that describe what is to be done. This is best explained with an example, so here is the Spock test for the Model class:

import spock.lang.Specification

class ModelSpec extends Specification {
    Model model = new Model()

    def 'convertTemp converts from Kelvin to F'() {
        expect:
        32 == model.convertTemp(273.15)
        212 == model.convertTemp(373.15)
    }

    def 'convertSpeed converts from meters/sec to miles/hour'() {
        expect:
        (2.23694 - model.convertSpeed(1)).abs() < 0.00001
    }

    def 'convertTime converts from Unix time to java.util.Date'() {
        given:
        Calendar cal = Calendar.instance
        cal.set(1992, Calendar.MAY, 5)
        Date d = cal.time
        long time = d.time / 1000  // Java time in ms, Unix time in sec

        when:
        Date date = model.convertTime(time)

        then:
        d - date < 1
    }
}

Spock test names are written as strings expressing what you're trying to accomplish. They normally return def and take no arguments. Inside the test method, an expect block indicates that all expressions must evaluate to true for the test to pass. The when and then blocks are a stimulus/response pair, where again all the expressions in the then block must be true for the test to pass.

For the temperature conversion, I asserted that 273.15K is 0 Celsius and therefore 32 Fahrenheit, and that adding 100 degrees results in 212 Fahrenheit.

The speed conversion came from typing "1 meter per second in mph" into Google, which gave the result shown. Of course, now that I know that value, I can replace my whole Factor Label approach with it, but I liked that approach and it’s been over 30 years since I’ve had a chance to use it. As long as the test passes, I think I can treat this as an eccentricity rather than a deep flaw. Your mileage may vary.

For the time conversion, I invoked the Calendar.getInstance() method via property access in the normal Groovy way and then set the date to be my son's birthday. Then I extracted a Date from the Calendar by using the getTime method (seriously, couldn't that have been called getDate? What would that have hurt? The Java 8 date/time improvements can’t get here soon enough), and then extracted the elapsed epoch time from the Date also using the getTime method (sigh). After adjusting for the change from ms to sec, I was able to perform the test.

I also need a test for the OpenWeather class, but since the weather keeps changing I wasn't sure exactly what to test. I decided to at least check the latitude and longitude and see that they made sense. The resulting test is:

import spock.lang.Specification

class OpenWeatherSpec extends Specification {
    OpenWeather ow = new OpenWeather()

    def 'default city and state return weather string'() {
        when:
        String result = ow.weather
        println result

        then:
        result  // not null is true in Groovy
        result.contains('41.63')
        result.contains('-72.46')
    }

    def 'Boston, MA works'() {
        when:
        String result = ow.getWeather('Boston','MA')
        println result

        then:
        result
        result.contains('42.36')
        result.contains('-71.06')
    }

    def "The weather is always great in Honolulu"() {
        when:
        String result = ow.getWeather('Honolulu', 'HI')
        println result

        then:
        result
        result.contains('21.3')
        result.contains('-157.86')
    }
}

The first test uses the default location (Marlborough, CT). The other tests check that other locations work, too.

Running the Open Weather system

Here is my use_open_weather.groovy script, to check the weather in various locations.

OpenWeather ow = new OpenWeather()
println ow.weather  // called Marlborough, CT, but really Hartford

// Home of Paul King, co-author of _Groovy in Action_ and my personal hero
println ow.getWeather('Brisbane','Australia')

// Home of Guillaume Laforge, head of the Groovy project
// (also one of my heroes, along with Dierk Koenig, Graeme Rocher, Tom Brady, David Ortiz, ...)
println ow.getWeather('Paris','France')

// Have to check the weather in Java, right?
println ow.getWeather('Java','Indonesia')

// Any weather stations in Antarctica?
println ow.getWeather('', 'Antarctica')

As I write this, it's late in the day on 1/7/14 and we're now recovering from the so-called "Polar Vortex". The current result from running this script is:

        Name         : Hartford
        Time         : Tue Jan 07 14:13:40 EST 2014
        Location     : (41.63, -72.46)
        Weather      : Clear (Sky is Clear)
        Icon         : http://openweathermap.org/img/w/01d.png
        Current Temp : 11.138 F (high: 14.0 F, low: 10.0 F)
        Humidity     : 30%
        Sunrise      : Tue Jan 07 07:16:25 EST 2014
        Sunset       : Tue Jan 07 16:36:23 EST 2014
        Wind         : 4.608088761632 mph at 232 deg
        Cloudiness   : 0%


        Name         : Brisbane
        Time         : Tue Jan 07 14:20:21 EST 2014
        Location     : (-27.47, 153.02)
        Weather      : Clouds (overcast clouds)
        Icon         : http://openweathermap.org/img/w/04n.png
        Current Temp : 72.932 F (high: 76.0 F, low: 70.0 F)
        Humidity     : 71%
        Sunrise      : Mon Jan 06 14:00:36 EST 2014
        Sunset       : Tue Jan 07 03:47:48 EST 2014
        Wind         : 2.4606299213 mph at 146 deg
        Cloudiness   : 88%


        Name         : Paris
        Time         : Tue Jan 07 14:17:06 EST 2014
        Location     : (48.85, 2.35)
        Weather      : Clear (Sky is Clear)
        Icon         : http://openweathermap.org/img/w/01n.png
        Current Temp : 51.674 F (high: 54.0 F, low: 50.0 F)
        Humidity     : 92%
        Sunrise      : Tue Jan 07 02:42:36 EST 2014
        Sunset       : Tue Jan 07 11:11:33 EST 2014
        Wind         : 3.4448818898 mph at 248 deg
        Cloudiness   : 0%


        Name         : Batununggal
        Time         : Tue Jan 07 14:22:14 EST 2014
        Location     : (-6.96, 107.65)
        Weather      : Clear (sky is clear)
        Icon         : http://openweathermap.org/img/w/02n.png
        Current Temp : 69.9728 F (high: 70.0 F, low: 69.0 F)
        Humidity     : 98%
        Sunrise      : Mon Jan 06 17:40:32 EST 2014
        Sunset       : Tue Jan 07 06:10:58 EST 2014
        Wind         : 3.06460272011453125 mph at 205.001 deg
        Cloudiness   : 8%


        Name         :
        Time         : Tue Jan 07 14:22:17 EST 2014
        Location     : (-78.33, 20.61)
        Weather      : Clear (Sky is Clear)
        Icon         : http://openweathermap.org/img/w/01d.png
        Current Temp : -14.7622 F (high: -14.0 F, low: -15.0 F)
        Humidity     : 58%
        Sunrise      : Tue Jan 07 13:22:17 EST 2014
        Sunset       : Wed Jan 08 01:22:17 EST 2014
        Wind         : 12.7952755906 mph at 12.0009 deg
        Cloudiness   : 0%


        Name         : Winnipeg
        Time         : Tue Jan 07 14:21:02 EST 2014
        Location     : (49.9, -97.14)
        Weather      : Clouds (scattered clouds)
        Icon         : http://openweathermap.org/img/w/03d.png
        Current Temp : -13.9504 F (high: -13.0 F, low: -14.0 F)
        Humidity     : 42%
        Sunrise      : Tue Jan 07 09:24:51 EST 2014
        Sunset       : Tue Jan 07 17:45:27 EST 2014
        Wind         : 7.7174302076 mph at 309 deg
        Cloudiness   : 48%

So it’s cold here, it's nice in Brisbane (though the sunset time demonstrates "fun with time zones"), the Java weather station is in Batununggal (however that's pronounced), and it's a nice, balmy summer in Antarctica. It is almost, but not quite, colder in Winnipeg than it is in Antarctica.

The Gradle build and getting the code

Other than Grails, Gradle may be the break-out project in the Groovy ecosystem. Gradle brings more Java developers to Groovy than practically anything else, partly because it's pretty cool and partly because there’s a deep loathing for Maven in the community. As I say in my book, Making Java Groovy (did I seriously make it all the way here before I mentioned the book? Whoa), I've never seen a project that is so common and yet so universally despised, except maybe for every Microsoft project ever.

My Open Weather project is pretty simple. Its only dependencies are on Groovy, Gson, and Spock, so the Gradle build file is pretty simple. Here it is, in its entirety:

apply plugin:'groovy'
apply plugin:'eclipse'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.codehaus.groovy:groovy-all:2.2.1'
    compile 'com.google.code.gson:gson:2.2.4'
    testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
}

If you have Gradle installed, you can type gradle build at the command line to download the necessary dependencies, compile everything, run all the tests, and generate a test report you can access via the build/reports/tests/index.html file. I use the Eclipse plugin to generate the Eclipse .project file and .settings directory, which automatically adds the jar dependencies from my Gradle cache.

If you don’t have Gradle installed, I added a wrapper task to generate the minimal Gradle scripts to get going. That means if you change to the root directory and run ./gradlew build, the system will download and install Gradle and then execute the build.

If you want the code, everything is in the Github repository for Making Java Groovy. The openweather project is part of chapter 2, Groovy by Example.

(In fact, chapter 2 now uses a multi-project Gradle build, but that's a subject for another day.)

Summary

In this post I demonstrated how to use Groovy classes with a Java library (Gson) to download, parse, and process JSON data from a RESTful web service.

The Groovy features included:

  1. POGOs
  2. Closures
  3. the Groovy JDK
  4. Multi-line strings
  5. Spock tests
  6. a Gradle build

There are many public web services on the web that return JSON data. Hopefully following this example will show you how to access and use them with Groovy.

About the Author

Ken Kousen is the President of Kousen IT, Inc, through which he does consulting and training in all areas related to Java, specializing in open source technologies like Groovy, Grails, Android, Spring, and Hibernate. He is the author of the Manning book Making Java Groovy and was recently named a 2013 JavaOne Rock Star.

Related Topics >>