Получить местоположение (широта/долгота) по адресу без города в Android

Я пытаюсь найти местоположение (широта/долгота) уличного адреса без города, например «123 Main St». - ближайший к текущему местоположению. Эта функция встроена в приложение Google Maps, а также в API карт iOS, поэтому удивительно, что она отсутствует для Android. вызывая Geocoder.getFromLocation(), и платформа вставляет опорную точку. Я пробовал несколько решений, следующее является лучшим, но все еще чувствует себя хуже.

Я вызываю Geocoder.getFromLocationName() с нижней левой и верхней правой координатами. Вызовы выполняются, начиная с области 10 км x 10 км вокруг текущего местоположения, и повторяются (30 x 30, 100 x 100, а затем без параметров ограничивающей рамки) до тех пор, пока не будут возвращены некоторые адреса. Когда возвращается несколько адресов, вычисляется и используется ближайший:

ОБНОВЛЕНИЕ: Этот подход показался неэффективным для легко находимых адресов за пределами границ. Например. "Нью-Йорк, Нью-Йорк" или "Бостон" искали с западного побережья - требуется 3 ограниченных и 1 неограниченный вызов Geocoder.getFromLocation(). Однако неожиданно правильные значения широты и долготы возвращаются для Нью-Йорка и Бостона при первом вызове с самыми узкими границами здесь, в Калифорнии. Google действует умно и игнорирует границы для нас. Это может вызвать проблемы для некоторых, но это отлично подходит для этого подхода.

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) );
}

}

Возможно, это лучшее решение, но мне было интересно, есть ли способ сделать это за один вызов.


person nathanielwolf    schedule 25.09.2012    source источник


Ответы (2)


Ваш код выглядит слишком сложным, вот более простой способ:

    String searchPattern = "123 Main St." 
    LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    //I use last known location, but here we can get real location
    Location lastKnownLocation = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);

    List<Address> addresses = null;
    try {
        //trying to get all possible addresses by search pattern
        addresses = (new Geocoder(this)).getFromLocationName(searchPattern, Integer.MAX_VALUE);
    } catch (IOException e) {
    }
    if (addresses == null || lastKnownLocation == null) {
        // location service unavailable or incorrect address
        // so returns null
        return null;
    }

    Address closest = null;
    float closestDistance = Float.MAX_VALUE;
    // look for address, closest to our location
    for (Address adr : addresses) {
        if (closest == null) {
            closest = adr;
        } else {
            float[] result = new float[1];
            Location.distanceBetween(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude(), adr.getLatitude(), adr.getLongitude(), result);
            float distance = result[0];
            if (distance < closestDistance) {
                closest = adr;
                closestDistance = distance;
            }
        }
    }
    return closest; //here can be null if we did not find any addresses by search pattern.
person Jin35    schedule 25.09.2012
comment
Javadoc для Geocoder.getFromLocationName() предлагает запросить 1-5 результатов. Есть более 5 мест, которые соответствуют 123 Main St. Я сделал это только что и получил Van Buren ME, Madawaska ME, Buffalo, NY, Worcester NY и Schenevus NY. Я в Калифорнии, возможно, это не то, что я ищу. - person nathanielwolf; 25.09.2012
comment
Да, у меня плохие результаты при использовании рекомендуемых 5 результатов. Потому что, если есть 100 главных улиц, маловероятно, что вы получите желаемую среди первых 5. Итак, просто получите все результаты - это займет много времени - и сделайте это в AsyncTask. - person Jin35; 25.09.2012
comment
Geocoder.getFromLocationName() возвращает не более 20 результатов, независимо от параметра maxResults. Смотри ниже. - person nathanielwolf; 26.09.2012

Я попробовал предложение Jin35 и увеличил max_results Geocoder.getFromLocationName(), но результаты были нежелательными. Во-первых, большой max_result, неограниченный вызов занял намного больше времени (2,5x - 7x = 1 - 6 секунд), чем 5 результат, ограниченный вызов geocoord на моем эмуляторе. Возможно, реальный мир будет быстрее, и этот фактор станет менее значимым.

Убийцей было то, что независимо от того, было ли max_results 50 или 100, каждый раз возвращалось только 20 результатов. Кажется, Google ограничивает результаты на стороне сервера. Ближайший «123 Main St» не был среди этих 20 результатов для меня - протестировано в Маунт-Вью, Калифорния, и было возвращено в Окли, Калифорния.

Если не существует другого метода, кроме Geocoder.getFromLocationName(), для поиска адреса или лучшего способа использования ограничивающей координаты, я приму свой собственный первоначальный ответ.

person nathanielwolf    schedule 25.09.2012