Search code examples
androidgoogle-geocoder

Get location (latitude / longitude) from address without city in Android


I'm trying to find the location (lat/long) of a street address w/o city - such as "123 Main St." - closest to the current location. This functionality is built into the Google Maps app as well as the iOS maps api, so it's surprising to find it missing for Android - e.g. calling Geocoder.getFromLocation() and have the platform insert a reference point. I have tried several solutions, the following is the best, but still feels inferior.

I make calls to Geocoder.getFromLocationName() with lower-left and upper-right coord. Calls are made beginning with a 10kmx10km area around the current location, and are repeated (30x30, 100x100, and then without the bounding box parameters)until some Addresses are returned. When multiple addresses are returned, the closest is calculated and used:

UPDATE: This approach seemed like it would be inefficient for easily found addresses outside the bounds. E.g. "New york, NY" or "Boston" searched from the west coast - requiring 3 bounded and 1 unbounded call to Geocoder.getFromLocation(). However, unexpectidly, the correct lat/lng is returned for NYC and Boston, on the first call, with tightest bounds here in CA. Google is being smart and ignoring the bounds for us. This may cause problems for some, but it is great for this approach.

package com.puurbuy.android;

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

import android.content.Context;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.os.AsyncTask;
import android.util.Log;

public class GeocoderRunner extends AsyncTask<String, Void, Address> {
    final static double LON_DEG_PER_KM = 0.012682308180089;
    final static double LAT_DEG_PER_KM =0.009009009009009;
    final static double[] SEARCH_RANGES = {10, 50,800,-1}; //city, region, state, everywhere

private Context mContext;
private GeocoderListener mListener;
private Location mLocation;

public GeocoderRunner(Context context, Location location,
        GeocoderListener addressLookupListener) {
    mContext = context;
    mLocation = location;
    mListener = addressLookupListener;
}
@Override
protected Address doInBackground(String... params) {
    Geocoder geocoder = new Geocoder(mContext);
    List<Address> addresses = null;

    //reference location TODO handle null 
    double lat = mLocation.getLatitude();
    double lon = mLocation.getLongitude();
    int i = 0;
    try {
        //loop through SEARCH_RANGES until addresses are returned
        do{
            //if range is -1, call  getFromLocationName() without bounding box
            if(SEARCH_RANGES[i] != -1){

                //calculate bounding box
                double lowerLeftLatitude =  translateLat(lat,-SEARCH_RANGES[i]);
                double lowerLeftLongitude = translateLon(lon,SEARCH_RANGES[i]);
                double upperRightLatitude = translateLat(lat,SEARCH_RANGES[i]);
                double upperRightLongitude = translateLon(lon,-SEARCH_RANGES[i]);

                addresses = geocoder.getFromLocationName(params[0], 5, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude);

            } else {
                //last resort, try unbounded call with 20 result
                addresses = geocoder.getFromLocationName(params[0], 20);    
            }
            i++;

        }while((addresses == null || addresses.size() == 0) && i < SEARCH_RANGES.length );

    } catch (IOException e) {
        Log.i(this.getClass().getSimpleName(),"Gecoder lookup failed! " +e.getMessage());
    }



    if(addresses == null ||addresses.size() == 0)
        return null;

    //If multiple addresses were returned, find the closest
    if(addresses.size() > 1){
        Address closest = null;
        for(Address address: addresses){
            if(closest == null)
                closest = address;
            else
                closest = getClosest(mLocation, closest,address);//returns the address that is closest to mLocation
        }
        return closest;
    }else
        return addresses.get(0);

}

@Override
protected void onPostExecute(Address address) {
    if(address == null)
        mListener.lookupFailed();
    else
        mListener.addressReceived(address);

}

//Listener callback
public interface GeocoderListener{
    public void addressReceived(Address address);
    public void lookupFailed();
}

//HELPER Methods

private static double translateLat(double lat, double dx){
    if(lat > 0 )
        return (lat + dx*LAT_DEG_PER_KM);
    else
        return (lat - dx*LAT_DEG_PER_KM);
}

private static double translateLon(double lon, double dy){
    if(lon > 0 )
        return (lon + dy*LON_DEG_PER_KM);
    else
        return (lon - dy*LON_DEG_PER_KM);

}

private static Address getClosest(Location ref, Address address1, Address address2){
    double xO = ref.getLatitude();
    double yO = ref.getLongitude();
    double x1 = address1.getLatitude();
    double y1 = address1.getLongitude();
    double x2 = address2.getLatitude();
    double y2 = address2.getLongitude();
    double d1 = distance(xO,yO,x1,y1);
    double d2 = distance(xO,yO,x2,y2);
    if(d1 < d2)
        return address1;
    else
        return address2;

}

private static double distance(double x1, double y1, double x2, double y2){
    return Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) );
}

}

Perhaps this is the best solution, but I was wondering if there was a way to do this in a single call.


Solution

  • I tried Jin35's suggestion and increased the max_results of Geocoder.getFromLocationName(), but the results were not desirable. Firstly the large max_result, unbounded call took much longer (2.5x - 7x = 1 - 6 seconds) than the 5 result, geocoord bounded call on my emulator. Perhaps realworld would be faster and this factor becomes less significant.

    The killer was no matter if the max_results were 50 or 100, only 20 results came back everytime. Seems Google is limiting the results on the server-side. The closest "123 Main St" was not amoung those 20 results for me - Tested from Mt View, CA and was returned Oakley, CA.

    Unless there is another method other than Geocoder.getFromLocationName() for doing address lookup, or a better way to use bounding coord, I will accept my own original answer.