Статьи

Обратное геокодирование Android с помощью Yahoo API — PlaceFinder

В моем предыдущем уроке ( Приложение для служб определения местоположения Android — GPS-местоположение ) я показал вам, как получить текущее местоположение пользователя в виде координат широты и долготы. Используя эти координаты, мы собираемся предоставить информацию о положении пользователя. Например, мы собираемся найти адрес улицы, ближайший к определенной точке. Этот процесс называется обратным геокодированием, и это обратный процесс геокодирования , который включает в себя поиск связанных географических координат (часто выражаемых в виде широты и долготы) из других географических данных, таких как адреса улиц или почтовые индексы (почтовые индексы).

Для этого мы будем использовать Yahoo! PlaceFinder, очень крутой API для геокодирования и обратного геокодирования. С официального сайта :

«Yahoo! PlaceFinder — это веб-служба геокодирования, которая помогает разработчикам ориентировать свои приложения на местоположение путем преобразования адресов улиц или названий мест в географические координаты (и наоборот) ».

Чтобы использовать API, вам понадобится ключ (совершенно бесплатно). Однако для целей данного руководства ключ не требуется. Мы собираемся использовать запрос образца, с которым можно связаться здесь . Ответ в формате XML, поэтому потребуется анализатор XML. Не беспокойтесь, так как Android включил возможности XML в свой основной API.

Следует также отметить, что PlaceFinder — это веб-API, поэтому для использования приложением Android устройство должно быть подключено к… ну… сети. Более того, HTTP-клиент должен будет использоваться нашим приложением. Опять же, не беспокойтесь, потому что для этой цели Android API включает HTTP-клиент Apache .

Итак, начнем. Мы продолжим с того места, где мы оставили предыдущий урок. Пожалуйста, проверьте это, если вы еще не прочитали это. Вы можете скачать проект Eclipse отсюда .

Сначала мы добавляем новую кнопку, которая запускает процедуру обратного геокодирования. Таким образом, файл «main.xml» для пользовательского интерфейса становится следующим:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
<?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>

Новая кнопка подключается к OnClickListener, и при ее нажатии вызывается метод executeReverseGeocodingInBackground. В этом методе текущее местоположение извлекается (как показано в предыдущем уроке). Объект экземпляра Location с именем «currentLocation» используется для хранения последнего известного местоположения пользователя. Затем начинается процедура обратного геокодирования.

Обратите внимание, что операция будет выполняться в фоновом режиме, то есть будет выполняться в потоке, отличном от основного потока пользовательского интерфейса. Это потому, что Android очень чувствителен к проблемам отзывчивости . Извлечение данных из Интернета может быть трудоемкой операцией, и мы не хотим приостанавливать основной поток, пока HTTP-клиент ожидает загрузки данных. Также обратите внимание, что остановка пользовательского интерфейса основного потока более чем на пять секунд вызовет диалоговое окно «Приложение не отвечает» (ANR), где пользователю предоставляется возможность убить ваше приложение. Не очень хорошо.

По этой причине мы будем выполнять операцию извлечения данных в новом потоке, используя API-интерфейс Android. Класс, который мы будем использовать, расширяет AsyncTask и называется «ReverseGeocodeLookupTask». На официальной странице документации «AsyncTask обеспечивает правильное и простое использование потока пользовательского интерфейса. Этот класс позволяет выполнять фоновые операции и публиковать результаты в потоке пользовательского интерфейса без необходимости манипулировать потоками и / или обработчиками ».

Есть три метода, которые мы переопределим. Во-первых, onPreExecute , который происходит в основном потоке и обычно используется для целей инициализации. В нашей реализации мы используем виджет ProgressDialog, чтобы пользователь знал, что происходит какая-то операция. Во-вторых, возвращается метод doInBackground , в котором выполняется вычисление в фоновом потоке и объект результата (определенный подклассом этой задачи). В нашей реализации данные XML извлекаются и анализируются (как я покажу вам через минуту), и мы возвращаем класс модели домена с именем «GeoCodeResult», который является заполнителем для данных местоположения. В-третьих, onPostExecute , который также выполняется в потоке пользовательского интерфейса и используется для целей очистки. В нашем случае ProgressDialog отменяется, и уведомление Toast представляет полученные данные пользователю. Включая все вышеперечисленное, код нашей основной деятельности теперь выглядит следующим образом:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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();           
        }
         
    }
     
}

Для простоты и разделения задач приложения мы будем использовать несколько небольших классов. Основной вид деятельности использует сервис GeoCoder, наша пользовательская реализация, которая использует Yahoo API. Код для этого класса следующий:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
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);
         
    }
 
}

Класс объекта модели с именем GeoCodeResult будет описан позже. Сервис GeoCoder использует два других сервиса.

Сначала у нас есть класс «HttpRetriever», который использует клиентский API-интерфейс Apache HTTP для выполнения запроса GET и получения данных из URL-адреса Yahoo API. Код следующий:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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;
 
    }
 
}

Легкие вещи здесь. Экземпляр DefaultHttpClient используется для HTTP-запросов. HttpGet используется для представления запроса GET и передается в качестве аргумента методу выполнения клиента. HttpResponse извлекается в результате (в успешных операциях), и из этого мы получаем объект HttpEntity . Наконец, вспомогательный класс EntityUtils используется для преобразования ответа в строку, и этот ответ возвращается вызывающей стороне. Обратите внимание, что контроль неуспешных операций не реализован, пожалуйста, позаботьтесь об этом, если вы хотите что-то более надежное.

Следующим сервисом к экзамену является класс «XmlParser». При этом используются существующие классы манипулирования DOM для анализа XML-ответа. Из ответа API (пример здесь ) мы будем использовать только узлы «line1» — «line4» элемента ResultSet / Result. Реализация является типичной для DOM, поэтому я не буду вдаваться в подробности. Вот код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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;
         
    }
 
}

Короче говоря, мы начинаем с корневого элемента, спускаемся до узла «Результат», а затем получаем всех потомков этого узла. Из них мы сортируем элементы, которые нам нужны, используя метод getNodeName и, наконец, извлекаем значение узла, используя метод getNodeValue, выполняемый для первого дочернего элемента узла. Класс модели заполняется соответствующим образом и затем возвращается обратно. Код для этого класса:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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();
         
    }
     
}

Последний шаг — добавить разрешение, необходимое для доступа в Интернет, то есть « android.permission.INTERNET ». Запустите эмулятор, используя уже созданную «Run Configuration». Перейдите в представление DDMS и установите координаты в известном месте. Я использовал те, которые использует образец запроса PlaceFinder, чтобы убедиться, что будет сгенерирован правильный ответ. Нажмите «Отправить», чтобы обновить последнее известное местоположение пользователя, а затем нажмите кнопку «Обратное геокодирование» нашего приложения. Сначала появляется диалоговое окно прогресса, информирующее нас о выполняемой фоновой операции. После того, как данные извлечены и извлечены из ответа API, появляется Toast и сообщает нам, каков текущий адрес, как показано ниже: Это оно! Теперь вы можете создавать приложения с поддержкой определения местоположения и тестировать их с помощью эмулятора Android. Вы можете найти новую версию проекта Eclipse здесь . Счастливого мобильного кодирования !!!

Статьи по Теме :