Showing posts with label google maps. Show all posts
Showing posts with label google maps. Show all posts

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.

Wednesday, December 29, 2010

Liferay (s)mashup - The Beginning


Part Two - Liferay (s)mashup - Google Maps

There are many popular web services available online. From the rich set of APIs from Google and Yahoo to social sites likeTwitter and Facebook, almost all service providers make available their services in the form of an open API. These rich APIs has given rise to new kind of applications called mashups. They are basically an integration of various web APIs from different providers put together to form new kind of applications.

In this and the next series of blogs, I will show how to integrate with some of these APIs.

To make meaningful use of these APIs, let me propose a sample application where these services can be used. Let us develop a travel planning site that helps user to plan their travel dates and location. Travelling is just not about location and date. There are other factors like weather conditions, transport availabiity, political turmoil, other events clashing with date. Questions such as 'Are any of my friends travelling on that date or to that location?', 'Do I know anyone from that location I am going to?' frequently comes up. Our site will try to resolve these questions so that a probable date and location can be zeroed on.

Features of the Application
 - Display input form for receiving the source, destination, journey date and duration.
 - Show the road route of the origin and destination in Google map
 - Show weather information of origin and destination
 - Show the event happening in origin and destination on and around the journey date
 - Show news related to origin and destination
 - Moving the marker on the Google map must also update all other information in real time like weather, events, news and  the input form.
 - More services as we go along

For the first requirement, create a new portlet. In the view.jsp file, design a basic form  for the user to fill in: Origin, Destination, Journey Date and Duration.

Code Listing

<form action="<liferay-portlet:actionURL portletConfiguration="true" />" method="post" name="<portlet:namespace />fm">
    <aui:input name="origin" label="Origin" />
    <aui:input name="destination" label="Destination" />
    <aui:input name="journeyDate" label="Journey Date" />
    <aui:input name="duration" label="Duration" suffix="(in days)" />
    <input type="button" value="Search" onclick="<portlet:namespace />postPlanDetails()" />
</form>

The journeyDate is shown as a calendar when user clicks on the input field. Define it in javascript as:

AUI().ready('aui-calendar', function(A) {
    var calendar1 = new A.Calendar({
        trigger : '#<portlet:namespace />journeyDate',
        dateFormat : '%d/%m/%Y',
        setValue : true,
        selectMultipleDates : true,
        on : {
            select : function(event) {
                var normal = event.date.normal;
                var detailed = event.date.detailed;
                var formatted = event.date.formatted;
            }
        }
}).render();

A.on('mousedown', function() {
    A.CalendarManager.hideAll();
}, document);

});

The postPlanDetails() javascript function is defined as follows:

function <portlet:namespace />postPlanDetails() {
    var origin = document.<portlet:namespace />fm.<portlet:namespace />origin.value;
    var dest = document.<portlet:namespace />fm.<portlet:namespace />destination.value;
    var journeyDate = document.<portlet:namespace />fm.<portlet:namespace />journeyDate.value;
    var duration = document.<portlet:namespace />fm.<portlet:namespace />duration.value;

    Liferay.fire('planTravel', {
        origin : origin,
        destination : dest,
        journeyDate : journeyDate,
        duration : duration
    });

}

The code in bold above functions publishes the 4 parameters as an event using Liferay client side Inter Portlet Communication. For more details on IPC, see Portlet to Portlet Communication.

This portlet also receives the 'directionsChanged' event from the Google Maps portlet. We will discuss this in the next blog.

Liferay.on('directionsChanged',
    function(event) {
        document.<portlet:namespace />fm.<portlet:namespace />origin.value = event.origin;
        document.<portlet:namespace />fm.<portlet:namespace />destination.value = event.destination;
});