Android Geocoder Reverse Geocoding

Filed Under: Android

Android Geocoder class is used for reverse geocoding i.e. retrieving the address from the location on the Google Map.
If you aren’t aware of how to use Google Maps in your Android Application, refer android google maps tutorial before moving ahead.

Android Geocoder

Android Geocoder class is used for Geocoding as well as Reverse Geocoding. Geocoding refers to transforming street address or any address into latitude and longitude. Reverse Geocoding refers to transforming latitude and longitude into its corresponding street address.

Address class helps in fetching the street address, locality, sub-locality, city, country, landmark etc. features of the location.

Using the above two classes we’ll be fetching the current marker address on the Google Maps in our application. Let’s start with the android reverse geocoding example project.

Android Geocoder Reverse Geocoding Project Structure

android geocoder reverse geocoding example project structure

We’ll need the google maps and google places api. So let’s add them in the build.gradle file as shown below.


apply plugin: 'com.android.application'


allprojects {
    repositories {
        jcenter()
        maven {
            url "https://maven.google.com"
        }
    }
}



android {
    compileSdkVersion 26
    buildToolsVersion "26.0.1"
    defaultConfig {
        applicationId "com.journaldev.reversegeocoding"
        minSdkVersion 16
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:26.0.1'
    compile 'com.android.support:cardview-v7:26.0.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.google.android.gms:play-services-maps:11.0.4'
    compile 'com.google.android.gms:play-services-places:11.0.4'
    testCompile 'junit:junit:4.12'
}

Get the relevant API key from the Google Developer Console and add it inside the meta-data in the AndroidManifest.xml file. If you aren’t aware of integrating Google APIs in your application, refer this tutorial before moving ahead.

Android Reverse Geocoding Code

The code for the activity_main.xml layout is given below.


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.CardView
        android:id="@+id/cardView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="12dp"
        card_view:cardCornerRadius="0dp"
        card_view:cardElevation="0dp">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center">

            <TextView
                android:id="@+id/txtLocationAddress"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentTop="true"
                android:layout_centerHorizontal="true"
                android:ellipsize="marquee"
                android:focusable="true"
                android:focusableInTouchMode="true"
                android:gravity="center"
                android:marqueeRepeatLimit="marquee_forever"
                android:maxLines="1"
                android:padding="16dp"
                android:scrollHorizontally="true"
                android:text="Current Marker Address" />

        </RelativeLayout>

    </android.support.v7.widget.CardView>


    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <fragment
            android:id="@+id/mapFragment"
            android:name="com.google.android.gms.maps.SupportMapFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:tag="tag_map_fragment" />

        <FrameLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center">

            <ImageView
                android:id="@+id/centerMarker"
                android:layout_width="48dp"
                android:layout_height="48dp"
                android:layout_gravity="center"
                android:src="@drawable/black_marker" />

        </FrameLayout>

    </FrameLayout>

</RelativeLayout>

Our layout contains a CardView, which holds a TextView that’ll eventually display the current address.
The marker is placed in an ImageView at the center of the screen.

The code for the MainActivity.java class is given below.


package com.journaldev.reversegeocoding;

import android.content.Intent;
import android.location.Address;
import android.location.Geocoder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.CardView;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.common.GooglePlayServicesRepairableException;
import com.google.android.gms.location.places.Place;
import com.google.android.gms.location.places.ui.PlaceAutocomplete;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;

import java.io.IOException;
import java.util.List;
import java.util.Locale;


public class MainActivity extends AppCompatActivity {


    TextView txtLocationAddress;
    SupportMapFragment mapFragment;
    GoogleMap map;
    LatLng center;
    CardView cardView;
    private static final int PLACE_AUTOCOMPLETE_REQUEST_CODE = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        txtLocationAddress = findViewById(R.id.txtLocationAddress);
        txtLocationAddress.setEllipsize(TextUtils.TruncateAt.MARQUEE);
        txtLocationAddress.setSingleLine(true);
        txtLocationAddress.setMarqueeRepeatLimit(-1);
        txtLocationAddress.setSelected(true);

        cardView = findViewById(R.id.cardView);

        cardView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    Intent intent =
                            new PlaceAutocomplete.IntentBuilder(PlaceAutocomplete.MODE_FULLSCREEN)
                                    .build(MainActivity.this);
                    startActivityForResult(intent, PLACE_AUTOCOMPLETE_REQUEST_CODE);
                } catch (GooglePlayServicesRepairableException e) {
                    printToast("Google Play Service Repair");
                } catch (GooglePlayServicesNotAvailableException e) {
                    printToast("Google Play Service Not Available");
                }
            }
        });

        mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.mapFragment);
        mapFragment.getMapAsync(new OnMapReadyCallback() {
            @Override
            public void onMapReady(GoogleMap googleMap) {
                map = googleMap;
                map.getUiSettings().setZoomControlsEnabled(true);
                LatLng latLng = new LatLng(20.5937, 78.9629);
                map.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 16));
                initCameraIdle();
            }
        });
    }

    private void initCameraIdle() {
        map.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() {
            @Override
            public void onCameraIdle() {
                center = map.getCameraPosition().target;
                getAddressFromLocation(center.latitude, center.longitude);
            }
        });
    }

    private void getAddressFromLocation(double latitude, double longitude) {

        Geocoder geocoder = new Geocoder(this, Locale.ENGLISH);


        try {
            List<Address> addresses = geocoder.getFromLocation(latitude, longitude, 1);

            if (addresses.size() > 0) {
                Address fetchedAddress = addresses.get(0);
                StringBuilder strAddress = new StringBuilder();
                for (int i = 0; i < fetchedAddress.getMaxAddressLineIndex(); i++) {
                    strAddress.append(fetchedAddress.getAddressLine(i)).append(" ");
                }

                txtLocationAddress.setText(strAddress.toString());

            } else {
                txtLocationAddress.setText("Searching Current Address");
            }

        } catch (IOException e) {
            e.printStackTrace();
            printToast("Could not get address..!");
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == PLACE_AUTOCOMPLETE_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                Place place = PlaceAutocomplete.getPlace(this, data);
                if (!place.getAddress().toString().contains(place.getName())) {
                    txtLocationAddress.setText(place.getName() + ", " + place.getAddress());
                } else {
                    txtLocationAddress.setText(place.getAddress());
                }

                CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(place.getLatLng(), 16);
                map.animateCamera(cameraUpdate);


            } else if (resultCode == PlaceAutocomplete.RESULT_ERROR) {
                printToast("Error in retrieving place info");

            }
        }
    }

    private void printToast(String message) {
        Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
    }
}

Let’s break down the above code and see what it does.

  1. We’ve set the attributes setEllipize() and set setMarqueeRepeatLimit() on the TextView programmatically since setting the same attributes in xml doesn’t scroll the text on all devices anymore. The above methods make the content of TextView scroll horizontally IF the content length is greater than the TextView width.
  2. Passing -1 in setMarqueeRepeatLimit() makes it scroll forever.
  3. The Google Map is displayed using the SupportMapFragment. getMapAsync() callback is assigned to the fragment. This callback gets triggered when the Google Play Services exists.
  4. The onMapReady() method is the one that displays the Google Map and returns a non-null instance of the GoogleMap class. We assign it to our instance variable map.
    map.getUiSettings().setZoomControlsEnabled(true) is used to set the zoom controls on the screen.
  5. map.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 16)) moves the map to the location specified in the latLng(Some location in India!). The camera zooms to the location in the center of the screen where the marker is placed.
  6. setOnCameraIdleListener would listen for the movements/dragging on the map. When the movement ends the method onCameraIdle() gets triggered. This is where we retrieve the latitude and longitude of the center of map(because that is where the marker resides) and pass it into the method getAddressFromLocation() which will eventually do the reverse geocoding.
  7. The method getFromLocation(double latitude, double longitude, int maxResults) returns a List of Addresses for the current location.
  8. Within the Address object, the method getAddressLine(int index) returns a line of the address numbered by the given index or null if no address exists.
  9. We append that address to a StringBuilder which is eventually displayed in the TextView.
  10. getMaxAddressLineIndex() returns the largest index currently in use to specify an address line.
  11. There are several other important details of an address that can retrieved too. Some of them are listed below:
    • getThoroughfare() : This is the street, which contains the delivery point. If it doesn’t exist null would be returned.
    • getSubThoroughfare() : When a thoroughfare name exists more than once within a town, subThoroughfare name is used for additional information.
    • getLocality() and getSublocality() : Returns the locality of the address, for example “Mountain View” and sub locality respectively if available or null.
    • getFeatureName() : Returns the nearest landmark name if available or null.
    • getCountryName() getCountryCode() : Returns the country name and country code respectively.
  12. Clicking the CardView would launch the PlaceAutoComplete service from the Google Places API.
  13. We use the PlaceAutocomplete.IntentBuilder to create Intent and pass the Mode as full screen.
  14. The selected place is returned in the onActivityResult() which is eventually displayed in the TextView. The latitude and longitude of the Place are returned as place.getLatLng() which is eventually zoomed into the center of the screen.

The output of the android reverse geocoding application in action is given below.

android reverse geocoding

This brings an end to android Geocoder example tutorial. You can download the final Android Reverse Geocoding Project from the link below and make sure to add your own API in the meta-data tag inside AndroidManifest.xml file.

References : Geocoder, Address, Place Autocomplete.

Comments

  1. Programmer says:

    Error in retrieving place info

  2. Trupti says:

    I’m also getting the same error of “Error in retrieving place info” i have added the API key and Enable the place API too. still mi’m getting this error.

  3. grim says:

    there is no api file in your program.
    you wasted my time of 3 hrs

    1. Anupam says:

      Please download the source code and follow the steps to generate your API key from the links mentioned in the tutorial.

      1. kim says:

        Hello,
        I receive the “Error in retreiving place info” – could not run search
        api key is ready

  4. Pavel says:

    Hello,
    I receive the “Error in retreiving place info” – could not run search

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages