Вземете местоположение (географска ширина / дължина) от адрес без град в 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 резултата за мен – Тествано от Mt View, Калифорния и беше върнато в Оукли, Калифорния.

Освен ако няма друг метод, различен от Geocoder.getFromLocationName() за извършване на търсене на адрес или по-добър начин за използване на ограничаваща координата, ще приема собствения си оригинален отговор.

person nathanielwolf    schedule 25.09.2012