Introduction

For many developers Java is often a synonym for totally non-sexy enterprise applications development. It is associated with numerous XML configuration files, boilerplate code, etc. So they instead use dynamic languages like Ruby, Python, PHP to develop their projects, especially when these are own simple utilities, mash-ups, etc.

However the Java field has changed much in the recent few years. There are multiple frameworks which relieve developer from “enterprise” burden. Grails is probably one of the best. It is based on Groovy, which is a dynamic language running on Java platform designed specially for Java programmers. It uses well known robust and efficient Java libraries to do all the heavy lifting (Spring, Hibernate, etc.). There is also a plugin system and plugins exist for almost every widely used Java library.

In this article we’ll show how to make a mash-up of Twitter and Google Maps in around 20 minutes. The end result will look similar to this:

Prepare environment

At first you need to make sure that you have proper version of JDK installed. The best idea would be to grab the latest version of JDK here.
Install it and then make sure JAVA_HOME environment variable points to the installation directory.

Then download latest Grails release from here.
Unpack the archive anywhere you want but then set GRAILS_HOME environment variable to point to that directory. Also make sure that "bin/" subdirectory of Grails installation directory is referenced in PATH environment variable.

After doing the aforementioned steps you should be able to run Grails commands in console. Try to type "grails help" to check it. It should output the list of available commands.

Basic concepts

Grails framework is based on the MVC design pattern. Model is represented by the domain classes, controllers by the controller classes, views by GSP pages. In this project only controller class and view GSP need to be defined, as model is provided by Twitter API.

A good inroduction reading would be quick start guide. Further information can be obtained in user guide. In particular for this article information about controllers and groovy server pages would be helpful.

Generate application skeleton

One of the features provided by Grails to relieve developer from the burden of enterprise Java development is the ability to automatically generate the skeleton of basic application.
To generate it simply type:

grails create-app geo_twitter

This will create application with name "geo_twitter" in the current working directory. You’ll need to change into that directory for all further steps.

Getting started with Google Maps

Integrating Google Maps is simple and well-supported by Google. However you need at first obtain API key to set full access to all services. Don’t fear — it is easy, quick and free.

Let’s at first clean up main layout of Grails application — Grails logo, etc can also be removed. Open grails-app/views/layout/main.gsp and edit it so that it looks like this:

<html>
    <head>
        <title><g:layoutTitle default="Grails" /></title>
        <link rel="stylesheet" href="${resource(dir:'css',file:'main.css')}" />
        <link rel="shortcut icon" href="${resource(dir:'images',file:'favicon.ico')}" type="image/x-icon" />
        <g:layoutHead />
    </head>
    <body>
        <g:layoutBody />
    </body>
</html>


Then let’s edit grails-app/views/index.gsp so that it includes simple Google Maps integration as follows:

<html>
    <head>
        <title>Welcome to GeoTwitter!</title>
        <meta name="layout" content="main" />

        <script src="http://www.google.com/jsapi?key=YOUR_GOOGLE_MAPS_API_KEY" type="text/javascript"></script>

        <script type="text/javascript">
            google.load("maps", "2.x");
            google.load("jquery", "1.3.1");
        
            google.setOnLoadCallback(function() {
                $(document).ready(function() {
                    var map = new GMap2(document.getElementById('map'));
                    var vinnitsa = new GLatLng(49.2325477, 28.4744695); // Replace this by coordinates of your own city ;)
                    map.setCenter(vinnitsa, 8);
                    map.addControl(new GLargeMapControl());
                });
            });
        </script>
    </head>
    <body>
        <div id="map" style="width:800px; height:600px">
        </div>
    </body>
</html>


The end result will look similar to this:

Add a form for Twitter and do layout

Let’s add a simple user name form for Twitter to the index.gsp view.

<div class="form">
    <form action="" id="twitter">
        <p>
            <label>twitter id:</label>
            <input type="text" id="name" name="name" value=""/>
        </p>
        <p class="submit">
            <input type="submit" value="Map my friends!">
        </p>
    </form>
</div>


Then replace the main stylesheet in web-app/css/main.css with something like this:

body {
    font-family: Verdana, Helvetica, sans-serif;
    margin: 1em;
}

#map {
    position: absolute;
    width: 800px;
    height: 600px;
    left: 19em;
    top: 1em;
}

.form {
    border: 1px dashed gray;
    width: 15em;
    padding: 0.5em;
}

.form label {
    width: 7em;
    display: block;
    float: left;
}

.form input {
    width: 10em;
}

.form .submit {
    padding-left: 7em;
}


You’ll get the page looking similar to:

Add server-side logic

To make something actually working, we’ll need to add some server-side logic. Let’s at first install Grails Twitter plugin to work with Twitter.

grails install-plugin twitter

Now we need to create controller which will give the list of friends from Twitter with information about their location, etc.

grails create-controller Twitter

The command above will generate grails-app/controllers/TwitterController.groovy file with dummy controller source. It should be replaced by controller implementation which will give out info about friends in JSON format. It will also have to call geocoding service to get coordinates on map by given location name.

import grails.converters.*

class TwitterController {
    // Google Maps API key
    static def API_KEY = "Insert your Google Maps API key here"

    // TwitterService instance will be injected into this variable by Spring
    def twitterService

    def friendsJson = {
        // Get friends of given user
        def friends = getFriends(params.name)
        // Render friends list as JSON
        render(friends as JSON)
    }

    private def getFriends(String userName) {
        def friends = twitterService.getFriends(params.name)
        
        // Return only the needed fields for each user and retrieve coordinates for location
        friends.collect { it ->
            [
                screenName: it.screenName,
                name: it.name,
                pictureUrl: it.profileImageUrl as String,
                bio: it.description,
                status: it.status?.text,
                coords: getCoordsFromLocation(it.location)
            ]    
        }
    }
    
    /**
     * This method gets coordinates on map for given location string.
     */
    private def getCoordsFromLocation(String location) {
        if (location) {
            if (location.contains("iPhone:")) {
                // There can be coords specified in location
                // like iPhone: 39.035248,-77.138687
                location = location.replace("iPhone: ", "")
                def parts = location.split(",")
                return [latitude: parts[0], longitude: parts[1]]
            } else {
                // Encode location as URL
                def encodedLocation = URLEncoder.encode(location)
                // Call web service by retrieving URL content
                def response = 
                    "http://maps.google.com/maps/geo?q=${encodedLocation}&output=xml&key=${API_KEY}".toURL().getText()
                // Parse response XML
                def root = new XmlSlurper().parseText(response)
                if (root.Response.Placemark.size() == 1) {
                    def coords = root.Response.Placemark.Point.coordinates.text()
                    def parts = coords.split(",")
                    if (parts.size() > 1) {
                        return [latitude: parts[1] as Double, longitude: parts[0] as Double]
                    }
                }
            }
        }

        // No coordinates are determined
        return null
    }
}


Use AJAX to retrieve data from server

After we have written controller logic, it is needed to write JS code that will actually retrieve data from server and display it on the map. This code can be done as form submit handler, however at first let’s specify correct action for form:
action="${createLink(controller: 'twitter', action: 'friendsJson'}"

Then let’s add form submit handler to index.gsp, so that we have such script:

google.load("maps", "2.x");
google.load("jquery", "1.3.1");

google.setOnLoadCallback(function() {
    $(document).ready(function() {
        // Create and configure Google Map control
        var map = new GMap2(document.getElementById("map"));
        var vinnitsa = new GLatLng(49.2325477, 28.4744695);
        map.setCenter(vinnitsa, 4);
        map.addControl(new GLargeMapControl());
        // Add form submit handler
        var form = $("#twitter");
        form.submit(function() {
            $.getJSON(form.attr("action") + "?" + form.serialize(), function (data) {
                // Clear all markers
                map.clearOverlays();
                // Loop through friends list and add markers to map
                $.each(data, function (i, item) {
                    if (item.coords) {
                        var marker = new GMarker(new GLatLng(item.coords.latitude, item.coords.longitude));
                        map.addOverlay(marker);
                        var popup = '<img style="width: 48px; height:48px;" src="' + item.pictureUrl + '">' + 
                            item.name + ' (' + item.screenName + ') <br>' + 
                            item.bio + '<br>' + item.status;
                        GEvent.addListener(marker, "click", function() {
                            marker.openInfoWindowHtml(popup);
                        });
                    }
                });
            });
            // Indicate that form should not actually be submitted
            return false;
        });
    });
});

Now when you enter the name and click “Map you friends!” you’ll have such a nice picture:

Live demo and source code

Further reading

Shameless plug

We develop mobile and web apps, you can hire us to work on your next project.