Perfect Geocoding Zoom: Part 1

Nov 4, 2011   //   by Theo   //   Blog  //  No Comments

The Problem

Ever used the built-in Geocoder class in Android to display a location on a map? If so, you may have found that the geocoder does not provide you with the viewport data needed to zoom the map appropriately. Given the reality of different size locations (countries, states, cities, buildings) how can we zoom the map appropriately to show the complete location on the screen, at a zoom level that’s not too high and not too low?

The Solution

In this post we will build a geocoding library called GeocoderPlus that uses the Google Maps Web Services APIs V3 to retrieve both location coordinates and viewport info. In the second part of this post, we will use the GeocoderPlus library to display locations on a map at the right zoom level. If you are simply interested in using the library without understanding the nuts and bolts, you can jump ahead to the second part.

The source code for this post is on Github at:
http://www.github.com/bricolsoftconsulting/GeocoderPlus/

GeocoderPlus

The GeocoderPlus library uses the Google Maps Web Services APIs V3 to retrieve both location coordinates and viewport info. The entire geocoding process consists of these steps:

  1. Create an appropriate request URL.
  2. Retrieve the data from the URL in JSON format.
  3. Parse the data from JSON into appropriate structures.

Step 1: Create the request URL

A Google Maps Web Services API V3 geocoding URL takes the form:

http://maps.googleapis.com/maps/api/geocode/output?parameters

The parameters used are:

Parameter Description
address The address you want to geocode.
sensor Indicates whether the device has a sensor (true / false).
language The language to use for the results.
region Specify a region to customize the results via region biasing. Region biasing gives priority to results in your region over results in other regions.

For example, to query the location for the city of Rome and customize the results for the English language with region biasing for Great Britain, the query is:

http://maps.googleapis.com/maps/api/geocode/json?address=Rome&sensor=true&language=en_gb®ion=gb

Armed with this knowledge, we can write a function that creates the URL and appends the required parameters to it.

// Constants
public static final String URL_MAPS_GEOCODE = "http://maps.googleapis.com/maps/api/geocode/json";
public static final String PARAM_SENSOR = "sensor";
public static final String PARAM_ADDRESS = "address";
public static final String PARAM_LANGUAGE = "language";
public static final String PARAM_REGION = "region";

// Members
Locale mLocale;
boolean mUseRegionBias = false;

private String getGeocodingUrl(String locationName)
{
	// Declare
	String url;
	Vector params;

	// Extract language from locale
	String language = mLocale.getLanguage();

	// Create params
	params = new Vector();
	params.add(new BasicNameValuePair(PARAM_SENSOR, "true"));
	params.add(new BasicNameValuePair(PARAM_ADDRESS, locationName));
	if (language != null && language.length() > 0)
	{
		params.add(new BasicNameValuePair(PARAM_LANGUAGE, language));
	}
	if (mUseRegionBias)
	{
		String region = mLocale.getCountry();
		params.add(new BasicNameValuePair(PARAM_REGION, region));
	}

	// Create URL
	String encodedParams = URLEncodedUtils.format(params, "UTF-8");
	url = URL_MAPS_GEOCODE + "?" + encodedParams;

	// Return
	return url;
}

Step 2: Retrieve JSON data from the URL

To retrieve the JSON data from the URL, we create a class called HttpRetriever. This class relies on two built-in classes: HttpClient and BasicResponseHandler. HttpClient issues GET requests and BasicResponseHandler processes the returned data and converts it to a string.

The section below shows the implementation of the HttpClient class. The main function we will use to retrieve data is retrieve().

package com.bricolsoftconsulting.geocoderplus.util.http;

import java.io.IOException;

import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;

public class HttpRetriever
{
	// Members
	DefaultHttpClient httpclient = null;

	// Constructor
	public HttpRetriever()
	{
		httpclient = new DefaultHttpClient();
	}

	// Document retrieval function
	public String retrieve(String url) throws ClientProtocolException, IOException
	{
		// Declare
		String response = null;

		// Connect to server and get JSON response
		HttpGet request = new HttpGet(url);
		ResponseHandler responseHandler = new BasicResponseHandler();
		response = httpclient.execute(request, responseHandler);

		// Return
		return response;
	}
}

Step 3: Parse the JSON data

At this point, the next hurdle is parsing the JSON content and storing it in Java objects. To accomplish this, we need to understand the format of the JSON data, define custom Java classes to hold the extracted data and create functions to parse the data. Let’s take a look at each point.

The JSON Format

The best way to understand the format of the JSON data is to look at the JSON string returned in response to our Rome query (see Step 1 above for URL).

{
   "results" : [
      {
         "address_components" : [
            {
               "long_name" : "Rome",
               "short_name" : "Rome",
               "types" : [ "locality", "political" ]
            },
            {
               "long_name" : "Rome",
               "short_name" : "RM",
               "types" : [ "administrative_area_level_2", "political" ]
            },
            {
               "long_name" : "Lazio",
               "short_name" : "Lazio",
               "types" : [ "administrative_area_level_1", "political" ]
            },
            {
               "long_name" : "Italy",
               "short_name" : "IT",
               "types" : [ "country", "political" ]
            }
         ],
         "formatted_address" : "Rome, Italy",
         "geometry" : {
            "bounds" : {
               "northeast" : {
                  "lat" : 42.17071890,
                  "lng" : 12.92975280
               },
               "southwest" : {
                  "lat" : 41.65587379999999,
                  "lng" : 12.23442660
               }
            },
            "location" : {
               "lat" : 41.89051980,
               "lng" : 12.49424860
            },
            "location_type" : "APPROXIMATE",
            "viewport" : {
               "northeast" : {
                  "lat" : 42.17071890,
                  "lng" : 12.92975280
               },
               "southwest" : {
                  "lat" : 41.65587379999999,
                  "lng" : 12.23442660
               }
            }
         },
         "types" : [ "locality", "political" ]
      }
   ],
   "status" : "OK"
}

As you can see above, the entire result is a JSON object. Inside the JSON object there is a status field and a results array. Each item in the array is an address. Each address is broken down into 2 sections: address components and geometry.  Geometry is further broken down into viewport and bounds (viewing areas) and location (address latitude and longitude).

New Java Classes

Now that we understand the JSON format, let’s define appropriate data structures for this data.

Address Class

Android’s built in Address class is based on the second version of the Google Maps Geocoding APIs. The V2 APIs have since been deprecated and replaced with V3 APIs that use different fields. Accordingly, we need to create our own Address class based on the V3 fields. We will use the following fields:

Field Description
Locale The locale specifying the language and region for the result.
Street Number The street number component of the address.
Premise The named location, usually a building or a collection of buildings with the same name.
SubPremise An entity below the premise level, can be a building or a collection of buildings with the same name.
Floor The floor component of the address, if any.
Room The room component of the address, if any.
Neighborhood The neighborhood for the location.
Locality The city or town.
SubLocality Political entity below city or town.
AdminArea First entity below country level.
SubAdminArea Second entity below country level.
SubAdminArea2 Third entity below country level.
CountryName The name of the country (e.g France, Belgium)
CountryCode The two letter country code (e.g. FR, BE)
PostalCode The postal code component of the address.
FormattedAddress The complete address formatted for display as a string.
ViewPort The viewport for the address, which indicates the area that we should use to view the location.
Bounds The viewport for the address, which indicates the area that we should use to view the location. For countries, the bounds include any territories and possessions, while viewport just contains the main area of the country.
Latitude The location’s latitude.
Longitude The location’s longitude.

The implementation of this class is straight-forward, and consists of creating members fields plus getters and setters for these fields. Please see the Address.java file on Github for details.

Area and Point Classes

In the JSON data, the viewport and bounds objects are identical data structures representing a geographical area. In Java, we will represent these structures using the Area and Point classes. The Area class designates a geographical area using the northeast and southwest corners. Each corner point in turn is represented using the Point class, which consists of latitude and longitude coordinates.

Once again, the implementation of these classes is straight-forward, and consists of member fields plus getters and setters. See the Github  source code for details.

JSON Parsing

GeocoderPlus uses a series of functions to parse the JSON tree. Processing starts at the top of the tree and moves down, progressively extracting data from the JSON tree and transferring it into Java objects.

  1. The top level function, getAddressesFromJSON(), operates on the entire JSON tree and looks for individual addresses.
  2. The second level function, getAddressFromJSON(), operates on each JSON address object and relies on lower levels to fill in an Address object.
  3. The third level functions, populateAddressComponentsFromJSON() and populateAddressGeometryFromJSON(), process the address components and geometry sections of each address. Notice how the structure of the parsing code mirrors the structure of the JSON.
  4. At the fourth level, the populateAddressComponentsFromJSON() function performs the field-by-field transfer of address components into the Address object. The populateAddressGeometryFromJSON() function transfers the viewport, bounds and location (latitude/ longitude) data using the getAreaFromJSON() function.

The entire parsing process is built in such a way that any missing field will not cause the entire process to halt. Many of the fields are in fact optional and depend on the type of address.

Conclusion

This post explained the inner workings of the GeocoderPlus library and how it uses the Google Maps Geocoding APIs V3 to provide geocoding results that contain viewport information. The second part of this post will explain how to use the library to display objects on a map at the just the right zoom level.

The source code for this post is on Github at:
http://www.github.com/bricolsoftconsulting/GeocoderPlus/