Android Core

Android Reverse Geocoding with Yahoo API – PlaceFinder

In my previous tutorial (Android Location Based Services Application – GPS location) I showed you how to retrieve the user’s current location in the form of latitude and longitude coordinates. Using those coordinates we are going to provide information regarding the user’s position. For example, we are going to find the street address nearest to a specific point. That process is called reverse Geocoding and it is the reverse process of Geocoding, which involves finding associated geographic coordinates (often expressed as latitude and longitude) from other geographic data, such as street addresses, or zip codes (postal codes).

For that purpose, we are going to use Yahoo! PlaceFinder, a super cool API for geocoding and reverse geocoding. From the official site:

“Yahoo! PlaceFinder is a geocoding Web service that helps developers make their applications location-aware by converting street addresses or place names into geographic coordinates (and vice versa).”

In order to use the API, you are going to need a key (totally free). However, for the purposes of this tutorial, a key is not required. We are going to use the sample request which can be reached here. The response is in XML format so an XML parser will be needed. Don’t worry though, since Android has included XML capabilities in its core API.

Another thing to note is that PlaceFinder is a web API, so in order to be used by an Android application, the device has to be connected to the… well… web. Moreover, an HTTP client will have to be used by our application. Again, don’t worry because the Android API includes Apache’s HTTP Client for that purpose.

So, let’s get started. We are going to continue from where we left the previous tutorial. Please check it out if you haven’t already read it. You can download the Eclipse project from here.

First, we add a new Button which will trigger the reverse geocoding procedure. Thus, the “main.xml” file for the user interface becomes as follows:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<Button
 android:id="@+id/retrieve_location_button" 
 android:text="Retrieve Location"
 android:layout_width="wrap_content" 
 android:layout_height="wrap_content" 
 />
<Button 
 android:id="@+id/reverse_geocoding_button" 
 android:text="Reverse Geocoding"
 android:layout_width="wrap_content" 
 android:layout_height="wrap_content" 
 />
</LinearLayout>

The new button gets hooked up with a OnClickListener and when it is clicked, the “performReverseGeocodingInBackground” method is invoked. In that method, the current location is retrieved (as shown in the previous tutorial). A Location instance object, named “currentLocation” is used in order to store the user’s latest known location. Then, the reverse geocoding procedure gets started.

Note that the operation will take place in the background, meaning it will execute in a thread other than the main UI thread. This is because Android is very sensitive in responsiveness issues. Fetching data from the internet can be a time consuming operation and we don’t want to pause the main thread while the HTTP client waits for the data to be downloaded. Also note that stalling the main thread UI for more than five seconds will trigger an “Application Not Responding” (ANR) dialog, where the user is given the opportunity to kill your application. Not very good.

For that reason, we will execute the data retrieval operation in a new thread, leveraging the Android API. The class that we will use extends the AsyncTask and is named “ReverseGeocodeLookupTask”. From the official documentation page, “AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers”.

There are three methods that we will override. First, onPreExecute, which occurs in the main thread and is generally used for initialization purposes. In our implementation, we use a ProgressDialog widget to let the user know that some operation is taking place. Second, the doInBackground method, in which a computation is performed on a background thread and a result object (defined by the subclass of this task) is returned. In our implementation, the XML data are retrieved and parsed (as I will show you in a minute) and we pass back a domain model class named “GeoCodeResult” which is a placeholder for the location data. Third, the onPostExecute, which also runs on the UI thread and is used for cleanup purposes. In our case, the ProgressDialog is cancelled and a Toast notification presents the retrieved data to the user. Incorporating all the above, the code for our main Activity is now the following:

package com.javacodegeeks.android.lbs;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

import com.javacodegeeks.android.lbs.model.GeoCodeResult;
import com.javacodegeeks.android.lbs.services.GeoCoder;

public class LbsGeocodingActivity extends Activity {
    
    private static final long MINIMUM_DISTANCE_CHANGE_FOR_UPDATES = 1; // in Meters
    private static final long MINIMUM_TIME_BETWEEN_UPDATES = 1000; // in Milliseconds
    
    private GeoCoder geoCoder = new GeoCoder();
    
    protected LocationManager locationManager;
    protected Location currentLocation;

    protected Button retrieveLocationButton;
    protected Button reverseGeocodingButton;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        retrieveLocationButton = (Button) findViewById(R.id.retrieve_location_button);
        reverseGeocodingButton = (Button) findViewById(R.id.reverse_geocoding_button);
        
        locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        
        locationManager.requestLocationUpdates(
                LocationManager.GPS_PROVIDER, 
                MINIMUM_TIME_BETWEEN_UPDATES, 
                MINIMUM_DISTANCE_CHANGE_FOR_UPDATES,
                new MyLocationListener()
        );
        
        retrieveLocationButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                showCurrentLocation();
            }
        });
        
        reverseGeocodingButton.setOnClickListener(new OnClickListener() {            
            @Override
            public void onClick(View v) {                
                performReverseGeocodingInBackground();
            }
        });
        
    }    

    protected void performReverseGeocodingInBackground() {
        showCurrentLocation();
        new ReverseGeocodeLookupTask().execute((Void[])null);
    }

    protected void showCurrentLocation() {

        currentLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);

        if (currentLocation != null) {
            String message = String.format(
                    "Current Location \n Longitude: %1$s \n Latitude: %2$s",
                    currentLocation.getLongitude(), currentLocation.getLatitude()
            );
            Toast.makeText(LbsGeocodingActivity.this, message,
                    Toast.LENGTH_LONG).show();
        }

    }   

    private class MyLocationListener implements LocationListener {

        public void onLocationChanged(Location location) {
            String message = String.format(
                    "New Location \n Longitude: %1$s \n Latitude: %2$s",
                    location.getLongitude(), location.getLatitude()
            );
            Toast.makeText(LbsGeocodingActivity.this, message, Toast.LENGTH_LONG).show();
        }

        public void onStatusChanged(String s, int i, Bundle b) {
            Toast.makeText(LbsGeocodingActivity.this, "Provider status changed",
                    Toast.LENGTH_LONG).show();
        }

        public void onProviderDisabled(String s) {
            Toast.makeText(LbsGeocodingActivity.this,
                    "Provider disabled by the user. GPS turned off",
                    Toast.LENGTH_LONG).show();
        }

        public void onProviderEnabled(String s) {
            Toast.makeText(LbsGeocodingActivity.this,
                    "Provider enabled by the user. GPS turned on",
                    Toast.LENGTH_LONG).show();
        }

    }
    
    public class ReverseGeocodeLookupTask extends AsyncTask <Void, Void, GeoCodeResult> {
        
        private ProgressDialog progressDialog;

        @Override
        protected void onPreExecute() {
            this.progressDialog = ProgressDialog.show(
                    LbsGeocodingActivity.this,
                    "Please wait...contacting Yahoo!", // title
                    "Requesting reverse geocode lookup", // message
                    true // indeterminate
            );
        }

        @Override
        protected GeoCodeResult doInBackground(Void... params) {
            return geoCoder.reverseGeoCode(currentLocation.getLatitude(), currentLocation.getLongitude());
        }

        @Override
        protected void onPostExecute(GeoCodeResult result) {
            this.progressDialog.cancel();
            Toast.makeText(LbsGeocodingActivity.this, result.toString(), Toast.LENGTH_LONG).show();            
        }
        
    }
    
}

In order to keep things simple and separate the application’s concerns, we are going to use multiple small classes. The main activity uses a GeoCoder service, our custom implementation which uses the Yahoo API. The code for that class is the following:

package com.javacodegeeks.android.lbs.services;

import com.javacodegeeks.android.lbs.model.GeoCodeResult;

public class GeoCoder {
    
    private static final String YAHOO_API_BASE_URL = "http://where.yahooapis.com/geocode?q=%1$s,+%2$s&gflags=R&appid=[yourappidhere]";
    
    private HttpRetriever httpRetriever = new HttpRetriever();
    private XmlParser xmlParser = new XmlParser();
    
    public GeoCodeResult reverseGeoCode(double latitude, double longitude) {
        
        String url = String.format(YAHOO_API_BASE_URL, String.valueOf(latitude), String.valueOf(longitude));        
        String response = httpRetriever.retrieve(url);
        return xmlParser.parseXmlResponse(response);
        
    }

}

The model object class named GeoCodeResult will be described later. The GeoCoder service uses two others services.

First we have the “HttpRetriever” class which uses the Apache HTTP client API to perform a GET request and retrieve the data from the Yahoo API URL. The code is the following:

package com.javacodegeeks.android.lbs.services;

import java.io.IOException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.util.Log;

public class HttpRetriever {
    
    private final String TAG = getClass().getSimpleName();

    private DefaultHttpClient client = new DefaultHttpClient();

    public String retrieve(String url) {

        HttpGet get = new HttpGet(url);

        try {

            HttpResponse getResponse = client.execute(get);
            HttpEntity getResponseEntity = getResponse.getEntity();

            if (getResponseEntity != null) {
                String response = EntityUtils.toString(getResponseEntity);
                Log.d(TAG, response);
                return response;
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;

    }

}

Easy stuff here. A DefaultHttpClient instance is used for the HTTP requests. A HttpGet is used to represent a GET request and is passed as an argument to the execute method of the client. A HttpResponse is retrieved as a result (in successful operations) and from that we get an HttpEntity object. Finally, the EntityUtils helper class is used to convert the response to a String and that response is returned to the caller. Note that no control of unsuccessful operations has been implemented, please take care of that if you want something more robust.

Next service to exam is the “XmlParser” class. This uses the existing DOM manipulation classes to perform parsing of the XML response. From the API’s response (example here), we are going to use only the “line1” – “line4” nodes of the ResultSet/Result element. The implementation is a typical DOM one, so I will not get into many details. Here is the code:

package com.javacodegeeks.android.lbs.services;

import java.io.StringReader;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import android.util.Log;

import com.javacodegeeks.android.lbs.model.GeoCodeResult;

public class XmlParser {
    
    private final String TAG = getClass().getSimpleName();
    
    public GeoCodeResult parseXmlResponse(String response) {
        
        try {
            
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document doc = db.parse(new InputSource(new StringReader(response)));
            
            NodeList resultNodes = doc.getElementsByTagName("Result");            
            Node resultNode = resultNodes.item(0);            
            NodeList attrsList = resultNode.getChildNodes();
            
            GeoCodeResult result = new GeoCodeResult();
            
            for (int i=0; i < attrsList.getLength(); i++) {
                
                Node node = attrsList.item(i);                
                Node firstChild = node.getFirstChild();
                
                if ("line1".equalsIgnoreCase(node.getNodeName()) && firstChild!=null) {
                    result.line1 = firstChild.getNodeValue();
                }
                if ("line2".equalsIgnoreCase(node.getNodeName()) && firstChild!=null) {
                    result.line2 = firstChild.getNodeValue();
                }
                if ("line3".equalsIgnoreCase(node.getNodeName()) && firstChild!=null) {
                    result.line3 = firstChild.getNodeValue();
                }
                if ("line4".equalsIgnoreCase(node.getNodeName()) && firstChild!=null) {
                    result.line4 = firstChild.getNodeValue();
                }
            }
            
            Log.d(TAG, result.toString());
            
            return result;
            
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        
        return null;
        
    }

}

In short, we begin from the root element, we travel down to the “Result” node and then we get all this node’s children. From those, we sort out the elements we care about using the getNodeName method and finally we retrieve the node value using the getNodeValue method executed against the first child of the node. The model class is populated accordingly and then returned back. The code for that class is:

package com.javacodegeeks.android.lbs.model;

public class GeoCodeResult {
    
    public String line1;
    public String line2;
    public String line3;
    public String line4;
    
    @Override
    public String toString() {
        
        StringBuilder builder = new StringBuilder();
        builder.append("Location:");
        
        if (line1!=null)
            builder.append("-"+line1);
        if (line2!=null)
            builder.append("-"+line2);
        if (line3!=null)
            builder.append("-"+line3);
        if (line4!=null)
            builder.append("-"+line4);
        
        return builder.toString();
        
    }
    
}

The final step is to add the permission required to access the internet, i.e. “android.permission.INTERNET”. Launch the emulator using the already created “Run Configuration”. Go to the DDMS view and set the coordinates to a known location. I used the ones that the PlaceFinder sample request uses in order to make sure that a valid response will be generated. Hit “Send” in order to update the user’s last known location and then click the “Reverse Geocoding” button of our application. First, the progress dialog appears, informing us about the background operation that takes place. After the data are retrieved and extracted from the API response, a Toast appears and let us know what the current address is, as shown below: That’s it! You can now build your location-aware applications and test them using the Android emulator. You can find the new version of the Eclipse project here. Happy mobile coding!!!

Related Articles :

Ilias Tsagklis

Ilias is a software developer turned online entrepreneur. He is co-founder and Executive Editor at Java Code Geeks.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

8 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Sumant Singh
11 years ago

the code doesn’t work for 2.3.3?

Anuja Rajput
11 years ago

Thanks for u r code it really helped me :)

santhosh
santhosh
11 years ago

Sir Thank you very much for the coding it works fine . I need to display current city name in my project so i modified this code to display the city name by reverse geocoding but now the problem is u had got the value from retrieve location and then while clicking on reverse geocoding it gets the location but now it in my case both works simultaneously and reverse geocoding returns force close bcoz it cannot get the lat and longitude when the app is opened . Please help me to display the city name sir.

Ashish Yadav
Ashish Yadav
11 years ago

Great post and site.

Jone
Jone
10 years ago

Hello Ilias,
I found this example highly educative and helpful. however, instead of Yahoo API, I want to use Google API. can you kindly please send the codes to my email?

Thanking you.

sampath
sampath
10 years ago

Hi,
I tried alot to get the location name from latitude and longitude values but its not working will u please help in solving this problem………..

shylendra
shylendra
9 years ago

Hi,
I tried a lot to get latitude and longitude values but its not working will u please help in solving this problem.
Thanks in advance..

kostas
8 years ago

I want to do the same thing but with a small difference I want to show the official website of someone MUSEUM , can help me ;;;

Back to top button