Thursday, January 27, 2011

Liferay (s)mashup - Google Maps


In the previous blog, we saw how to fire events asynchronously on the client side. In this blog, we will see the integration of Google Maps in a portlet.
Showing Google Maps in a portlet is a breeze. For this, create a portlet called 'Visual View'. This portlet displays the Google Map for visual interaction. It receives the source and destination from the Plan Your Travel portlet and shows the road route in the map.

Using Google Maps API

Google Maps API is a collection of web services providing geographic data for creating maps applications. These web services use HTTP requests to specific URLs, passing URL parameters as arguments to the services. Generally, these services return data in the HTTP request as either JSON or XML for parsing and/or processing by an application.  Let's get straight to the code.

Code Listing

<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%>
<portlet:defineObjects />
<style type="text/css">
#map_canvas {
    height: 500px;
    width: 400px
}
</style>

<script type="text/javascript">
    var <portlet:namespace />map;
    var <portlet:namespace />geocoder;
    var <portlet:namespace />directionDisplay;
    var <portlet:namespace />directionsService;
    var <portlet:namespace />rendererOptions = {
        draggable : true //make the map points draggable
    };
    var <portlet:namespace />initialLocation;
    var <portlet:namespace />infowindow;

    function <portlet:namespace />initialize() {
        var myLatlng = new google.maps.LatLng(-34.397, 150.644);
        var myOptions = {
            zoom : 8,
            center : myLatlng,
            mapTypeId : google.maps.MapTypeId.ROADMAP
        };

        <portlet:namespace />map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
        <portlet:namespace />geocoder = new google.maps.Geocoder();
        <portlet:namespace />directionsService = new google.maps.DirectionsService();
        <portlet:namespace />directionsDisplay = new google.maps.DirectionsRenderer(
                                                    <portlet:namespace />rendererOptions);
        <portlet:namespace />directionsDisplay.setMap(<portlet:namespace />map);
        <portlet:namespace />infowindow = new google.maps.InfoWindow();

        //geo location
        // Try W3C Geolocation method (Preferred)
        if (navigator.geolocation) {
            browserSupportFlag = true;
            navigator.geolocation.getCurrentPosition(function(position) {
                <portlet:namespace />initialLocation = new google.maps.LatLng(
                        position.coords.latitude, position.coords.longitude);
                contentString = "Location found using W3C standard";
                <portlet:namespace />map
                        .setCenter(<portlet:namespace />initialLocation);
                var marker = new google.maps.Marker({
                    map : <portlet:namespace />map,
                    position : <portlet:namespace />initialLocation
                });
            }, function() {
                <portlet:namespace />handleNoGeolocation(browserSupportFlag);
            });
        } else if (google.gears) {
            // Try Google Gears Geolocation
            browserSupportFlag = true;

            var geo = google.gears.factory.create('beta.geolocation');
            geo.getCurrentPosition(function(position) {
                <portlet:namespace />initialLocation = new google.maps.LatLng(
                        position.latitude, position.longitude);
                contentString = "Location found using Google Gears";
                <portlet:namespace />map.setCenter(<portlet:namespace />initialLocation);
                <portlet:namespace />infowindow.setContent(contentString);
                <portlet:namespace />infowindow.setPosition(<portlet:namespace />initialLocation);
                <portlet:namespace />infowindow.open(<portlet:namespace />map);
            }, function() {
                <portlet:namespace />handleNoGeolocation(browserSupportFlag);
            });
        } else {
            // Browser doesn't support Geolocation
            browserSupportFlag = false;
            <portlet:namespace />handleNoGeolocation(browserSupportFlag);
        }

        //add listener for directions change
        google.maps.event.addListener(
            <portlet:namespace />directionsDisplay,
            'directions_changed',
            function() {
                var leg = <portlet:namespace />directionsDisplay.directions.routes[0].legs[0];
                Liferay.fire('directionsChanged', {
                        origin : leg.start_address,
                        destination : leg.end_address
                });
            });
    }

    function <portlet:namespace />handleNoGeolocation(errorFlag) {
        if (errorFlag == true) {
            <portlet:namespace />initialLocation = newyork;
            contentString = "Error: The Geolocation service failed.";
        } else {
            <portlet:namespace />initialLocation = siberia;
            contentString = "Error: Your browser doesn't support geolocation. Are you in Siberia?";
        }
        <portlet:namespace />map.setCenter(<portlet:namespace />initialLocation);
        <portlet:namespace />infowindow.setContent(contentString);
        <portlet:namespace />infowindow.setPosition(<portlet:namespace />initialLocation);
        <portlet:namespace />infowindow.open(<portlet:namespace />map);
    }
 
    function <portlet:namespace />loadScript() {
        var script = document.createElement("script");
        script.type = "text/javascript";
        script.src = "http://maps.google.com/maps/api/js?sensor=false&callback=<portlet:namespace />initialize";
        document.body.appendChild(script);
    }

    window.onload = <portlet:namespace />loadScript;

    function <portlet:namespace />showDirection(source, destination) {
        var request = {
            origin : source,
            destination : destination,
            travelMode : google.maps.DirectionsTravelMode.DRIVING
        };
        <portlet:namespace />directionsService.route(request, function(
                response, status) {
            if (status == google.maps.DirectionsStatus.OK) {
                <portlet:namespace />directionsDisplay.setDirections(response);
            }
        });
    }

    Liferay.on('planTravel', function(event) {
        <portlet:namespace />showDirection(event.origin, event.destination);
    });
</script>

<div id="map_canvas"></div>

The Google Maps API requires a single div element with an id in the html page to be rendered. Here, we have given the id as 'map_canvas'. The loadScript() method dynamically loads the maps script file. This speeds up the page display. The initialize() method loads the map view with pre-configured values. Also, latest versions of most browsers support geolocation. Geolocation allows tracking user's current location. This information is used to load the map with user's current location.
Note the listener for the directions changed event from the map in the initialize function. When the directions changed event occurs, we republish the event using Liferay's event manager to notify other portlets. Other portlets receives this event to update their display. We saw this in the search form in the first part of this blog series.
To use the two portlets that we have developed so far, type in the names of cities in the origin and destination and hit the Search button. The road rout between the two cities will be shown in the Google map. Now, try changing the cities by dragging the marker in the map. The cities' names are updated in the Plan Your Travel portlet. We will see interesting applications of this event model in the next few portlets.
For more details working on the Google Maps API, see this and this.