Android Location with Google Maps – Part 2

In Part 1, we analyzed our requirements and the tools at our disposal to get an initial application design.

.
This part will start the implementation with the lower-level class, LocatorOverlay, and answer the following question: What feature(s) do we want to provide on top of the phone’s location map? Simply displaying a map location on a phone screen is cute…for about ten seconds. So we need to think about added functionality. Here, we’ll implement a “send location to contact” feature: the user can just tap on his/her phone’s location point on the map, and share it with any number of contacts via SMS.

For overlaying items,  Google Maps provides us with a number of choices as base classes that can be extended to inherit the functionality:

  1. The Overlay abstract class
  2. The ItemizedOverlay subclass of Overlay, which we can use as a list of overlay items.
  3. The MyLocationOverlay subclass of Overlay, that draws the user location on a map.

The second option is a good choice if we intend to display several items on top of a map. Let’s start coding a first draft of our LocatorOverlay class:

//...package statement and other imports go here

//...
import com.google.android.maps.ItemizedOverlay;
import com.google.android.maps.OverlayItem;

/**
 * Custom overlay class
 */
public class LocatorOverlay extends ItemizedOverlay<OverlayItem> {

   private List<OverlayItem> mapOverlays = new ArrayList<OverlayItem>();
   private Context context;

   public LocatorOverlay(Drawable defaultMarker) {
        super(boundCenterBottom(defaultMarker));
   }

   public LocatorOverlay(Drawable defaultMarker, Context context) {
        this(defaultMarker);
        this.context = context;
   }
   public void addOverlay(OverlayItem overlay) {
      mapOverlays.add(overlay);
      this.populate();
   }

   @Override
   protected OverlayItem createItem(int i) {
      return mapOverlays.get(i);
   }

   @Override
   public int size() {
      return mapOverlays.size();
   }

   @Override
   protected boolean onTap(int index) {
      // TODO
      return true;
   }

}

Nothing extraordinary so far. We have a list of overlay items, and a reference to the application context. The last method onTap(int index)  is where things get interesting. Let’s implement a send-location-via-SMS dialog that will popup when the user taps the phone location point:

   /**
     * Show some info and send location via SMS
     *
     * @param index
     * @return
     */
   @Override
   protected boolean onTap(int index) {
       // Items to display in the dialog
      OverlayItem item = mapOverlays.get(index); //? see Part 3

      AlertDialog.Builder dialog = new AlertDialog.Builder(context);
      dialog.setTitle(item.getTitle());
      dialog.setMessage(item.getSnippet());

       // get location in the format "latitude,longitude"
      final String address =  item.routableAddress();

      // dialog to send location via SMS
      dialog.setCancelable(false);
      dialog.setPositiveButton("Send Location",
       new DialogInterface.OnClickListener() {
          public void onClick(DialogInterface dialog, int id) {
              // call SMS program
              Intent sendIntent = new Intent(Intent.ACTION_VIEW);
              sendIntent.putExtra("sms_body", "My location: " + address);
              sendIntent.setType("vnd.android-dir/mms-sms");
              context.startActivity(sendIntent);
          }
      }) ;
      dialog.setNegativeButton("Cancel", 
       new DialogInterface.OnClickListener() {
          public void onClick(DialogInterface dialog, int id) {
              dialog.dismiss();
          }
      });
      dialog.show();
      return true;
   }

What we did, was:

  1. Get the list of items to overlay (here, display as part of a dialog’s title and message)
  2. Create the dialog to enable the sending of the location via SMS
  3. Include the location in the body of the SMS message

But where does the list of overlay items mapOverlays get its OverlayItem elements in the first place? We’ll see that in Part 3. For now, here’s a peek of how it will look like in the end, when the user will tap on the location point in the first screen shot:

.

Are we done with the location display by SMS? Not quite. See, the “address” we are getting from OverlayItem.routableAddress() only contains a latitude & longitude pair, and is of limited practical use. However, there is nothing preventing us from constructing a Google Map URL with those coordinates so that our SMS recipient(s) can click on the link and visualise our location on their browser. For instance, we could do this:

String latLong =  item.routableAddress();
final String locationURL = "https://maps.google.com/maps?q=" +
                           URLEncoder.encode(latLong);
// code ....
// send locationURL instead of initial address
sendIntent.putExtra("sms_body", locationURL);
// code ...

We could get even more sophisticated and use Google’s URL Shortener API for our location URL.

If on the other hand we prefer to send a street address corresponding (more or less) to the latitude-longitude pair, we would need to use Reverse Geocoding :

// new imports:
import android.location.Address; 
import android.location.Geocoder;

//...

// inside onTap() method
//get street address estimate                     
 GeoPoint point = item.getPoint();
 String streetAddr = "";                            
 Geocoder geoCoder = new Geocoder(context, Locale.getDefault()); 
 try {   
   // point's coordinates are stored in micro-degrees... 
   List<Address> addrList = 
        geoCoder.getFromLocation( point.getLatitudeE6()  / 1E6,
                                  point.getLongitudeE6() / 1E6,
                                  1);  // max result = 1     
   if ( addrList != null && addrList .size() > 0)  {
     // first addr in the list is closest estimate 
      Address addr1 =  addrList.get(0);
      streetAddr = addr1.getAddressLine(0) 
                      + ", " + addr1.getLocality(); 
   }                                                   
 }                                     
 catch (IOException e) {
   e.printStackTrace();
   streetAddr = "Not Available (Network connection)";  
 }

final String address = streetAddr;

//code ...
sendIntent.putExtra("sms_body", "My location: \n" + address);
//code ...

Of course, nothing prevents us from sending both a location URL and a street address to the SMS recipient. Be aware though that in general the reverse-geocoding lookup will slow down the application response time in a noticeable way, and in this case, the time needed for the dialog to pop-up in response to the tap event.

We’re done with LocatorOverlay. In Part 3,  we’ll implement the Locator class.

This article is also available at JavaLobby.

Advertisements

, , , , ,

  1. #1 by mejimaru on June 26, 2012 - 6:50 am

    great blog….:) good job bro…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: