Google Places API может использоваться для поиска близлежащих мест. В этом учебнике мы будем разрабатывать приложение, которое отображает близлежащие места нашего выбора вместе с приблизительным расстоянием и временем от нашего текущего местоположения. В приложении мы будем использовать веб-службу Google Places API с API матрицы расстояний.
Google Places API
Веб-служба Google Places API позволяет нам запрашивать места на основе нескольких параметров, таких как тип места, открыто ли место в данный момент и т. д. Запрос на поиск близлежащих мест представляет собой HTTP-URL следующего вида:
https://maps.googleapis.com/maps/api/place/nearbysearch/output?parameters
json
является рекомендуемым output
, другим является xml
. Обязательные параметры:
- ключ(ключ API)
- местоположение
- rankby=distance или радиус: Если один используется, другой не может быть использован.
Примечание: rankby=distance
требует указания одного из следующих параметров:
- название: значения могут быть mcdonalds, kfc и т. д.
- тип: значения могут быть ресторан, кафе и т. д.
- ключевое слово
Дополнительные параметры могут быть opennow
, pagetoken
и т. д. Для получения более подробной информации обратитесь к этой странице.
API Google Distance Matrix
API матрицы расстояний
используется для вычисления расстояния и времени между двумя или более точками. URL API матрицы расстояний имеет следующий формат:
https://maps.googleapis.com/maps/api/distancematrix/outputFormat?parameters
Обязательными параметрами являются origins
, destinations
и key
. origins — содержит точку начала расчета расстояния и времени в пути. Можно передать более одного набора координат, разделенных вертикальными чертами(|). Также можно передавать адреса/идентификаторы места вместо координат, и служба автоматически преобразует их в координаты широты и долготы для расчета расстояния и продолжительности. Пример кода:
https://maps.googleapis.com/maps/api/distancematrix/json?origins=Washington,DC&destinations=New+York+City,NY&key=YOUR_API_KEY
Дополнительными параметрами являются:
- mode : ожидает значение среди
driving
,bicycling
,walking
,transit
- avoid : Вводит ограничения на маршрут, такие как
tolls
,indoor
и т. д.
Для получения дополнительной информации посетите this страницу.
Включение ключей API
Перейдите по адресу https://console.developers.google.com/
и включите следующие API:
- Google Maps Distance Matrix API
- Google Places API Web Service
- Google Places API для Android
Перейдите к учетным данным и создайте новый ключ. В настоящее время установите ограничение ключа на Нет. Давайте перейдем к деловой части этого учебника. Мы будем разрабатывать приложение, которое позволит нам искать близлежащие места на основе нашего текущего местоположения и отображать эти места в RecyclerView. Мы будем искать места на основе типа и ключевых слов, которые будут введены в EditText и разделены пробелом. Пример: ресторан домино или кафе вегетарианец
Пример структуры проекта Google Places API
Проект состоит из одной активности. Класс адаптера для RecyclerView. Класс модели, который содержит данные для каждой строки RecyclerView. Два класса POJO для преобразования ответов JSON в Gson из API Google Places и API Distance Matrix. APIClient и ApiInterface для использования Retrofit и конечных точек.
Пример кода Google Places API
Добавьте следующие зависимости в файл build.gradle
compile 'com.google.android.gms:play-services-location:10.2.1'
compile 'com.google.android.gms:play-services-places:10.2.1'
compile 'com.google.code.gson:gson:2.7'
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
compile 'com.squareup.okhttp3:okhttps:3.4.1'
compile 'io.nlopez.smartlocation:library:3.3.1'
compile 'com.android.support:cardview-v7:25.3.0'
compile 'com.android.support:recyclerview-v7:25.3.0'
compile 'io.nlopez.smartlocation:library:3.3.1'
– это сторонняя библиотека отслеживания местоположения, которая уменьшает код “бойлерплейт”. Код библиотеки APIClient.java приведен ниже:
package com.journaldev.nearbyplaces;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class APIClient {
private static Retrofit retrofit = null;
public static final String GOOGLE_PLACE_API_KEY = "ADD_YOUR_API_KEY_HERE";
public static String base_url = "https://maps.googleapis.com/maps/api/";
public static Retrofit getClient() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().readTimeout(30, TimeUnit.SECONDS).writeTimeout(30, TimeUnit.SECONDS).addInterceptor(interceptor).build();
retrofit = null;
retrofit = new Retrofit.Builder()
.baseUrl(base_url)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
return retrofit;
}
}
Код ApiInterface.java приведен ниже
package com.journaldev.nearbyplaces;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
public interface ApiInterface {
@GET("place/nearbysearch/json?")
Call<PlacesPOJO.Root> doPlaces(@Query(value = "type", encoded = true) String type, @Query(value = "location", encoded = true) String location, @Query(value = "name", encoded = true) String name, @Query(value = "opennow", encoded = true) boolean opennow, @Query(value = "rankby", encoded = true) String rankby, @Query(value = "key", encoded = true) String key);
@GET("distancematrix/json") // origins/destinations: LatLng as string
Call<ResultDistanceMatrix> getDistance(@Query("key") String key, @Query("origins") String origins, @Query("destinations") String destinations);
}
PlacesPOJO.java – это файл, который содержит ответ от Places API. Его код приведен ниже
package com.journaldev.nearbyplaces;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class PlacesPOJO {
public class Root implements Serializable {
@SerializedName("results")
public List<CustomA> customA = new ArrayList<>();
@SerializedName("status")
public String status;
}
public class CustomA implements Serializable {
@SerializedName("geometry")
public Geometry geometry;
@SerializedName("vicinity")
public String vicinity;
@SerializedName("name")
public String name;
}
public class Geometry implements Serializable{
@SerializedName("location")
public LocationA locationA;
}
public class LocationA implements Serializable {
@SerializedName("lat")
public String lat;
@SerializedName("lng")
public String lng;
}
}
Класс ResultDistanceMatrix.java содержит ответ от Distance Matrix API. Его код приведен ниже:
package com.journaldev.nearbyplaces;
import com.google.gson.annotations.SerializedName;
import java.util.List;
public class ResultDistanceMatrix {
@SerializedName("status")
public String status;
@SerializedName("rows")
public List<InfoDistanceMatrix> rows;
public class InfoDistanceMatrix {
@SerializedName("elements")
public List elements;
public class DistanceElement {
@SerializedName("status")
public String status;
@SerializedName("duration")
public ValueItem duration;
@SerializedName("distance")
public ValueItem distance;
}
public class ValueItem {
@SerializedName("value")
public long value;
@SerializedName("text")
public String text;
}
}
}
Файл activity_main.xml приведен ниже
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#212121"
tools:context="com.journaldev.nearbyplaces.MainActivity">
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:textColor="@android:color/white"
android:textColorHint="@android:color/white"
android:text="restaurant mcdonalds"
android:hint="type name"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toLeftOf="@+id/button"
android:layout_toStartOf="@+id/button" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:text="Search" />
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/editText"
android:scrollbars="vertical" />
</RelativeLayout>
Код класса MainActivity.java
приведен ниже.
package com.journaldev.nearbyplaces;
import android.annotation.TargetApi;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Build;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.google.android.gms.maps.model.LatLng;
import java.util.ArrayList;
import java.util.List;
import io.nlopez.smartlocation.OnLocationUpdatedListener;
import io.nlopez.smartlocation.SmartLocation;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
public class MainActivity extends AppCompatActivity {
private ArrayList permissionsToRequest;
private ArrayList permissionsRejected = new ArrayList<>();
private ArrayList permissions = new ArrayList<>();
private final static int ALL_PERMISSIONS_RESULT = 101;
List storeModels;
ApiInterface apiService;
String latLngString;
LatLng latLng;
RecyclerView recyclerView;
EditText editText;
Button button;
List results;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
permissions.add(ACCESS_FINE_LOCATION);
permissions.add(ACCESS_COARSE_LOCATION);
permissionsToRequest = findUnAskedPermissions(permissions);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (permissionsToRequest.size() > 0)
requestPermissions(permissionsToRequest.toArray(new String[permissionsToRequest.size()]), ALL_PERMISSIONS_RESULT);
else {
fetchLocation();
}
} else {
fetchLocation();
}
apiService = APIClient.getClient().create(ApiInterface.class);
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setNestedScrollingEnabled(false);
recyclerView.setHasFixedSize(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
editText = (EditText) findViewById(R.id.editText);
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String s = editText.getText().toString().trim();
String[] split = s.split("\\s+");
if (split.length != 2) {
Toast.makeText(getApplicationContext(), "Please enter text in the required format", Toast.LENGTH_SHORT).show();
} else
fetchStores(split[0], split[1]);
}
});
}
private void fetchStores(String placeType, String businessName) {
/**
* For Locations In India McDonalds stores aren't returned accurately
*/
// Вызов call = apiService.doPlaces(placeType, latLngString,"\""+ businessName +"\"", true, "distance", APIClient.GOOGLE_PLACE_API_KEY);
Call call = apiService.doPlaces(placeType, latLngString, businessName, true, "distance", APIClient.GOOGLE_PLACE_API_KEY);
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
PlacesPOJO.Root root = response.body();
if (response.isSuccessful()) {
if (root.status.equals("OK")) {
results = root.customA;
storeModels = new ArrayList<>();
for (int i = 0; i < results.size(); i++) {
if (i == 10)
break;
PlacesPOJO.CustomA info = results.get(i);
fetchDistance(info);
}
} else {
Toast.makeText(getApplicationContext(), "No matches found near you", Toast.LENGTH_SHORT).show();
}
} else if (response.code() != 200) {
Toast.makeText(getApplicationContext(), "Error " + response.code() + " found.", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call call, Throwable t) {
// Зарегистрируйте ошибку здесь, так как запрос не удался
call.cancel();
}
});
}
private ArrayList findUnAskedPermissions(ArrayList wanted) {
ArrayList result = new ArrayList<>();
for (String perm : wanted) {
if (!hasPermission(perm)) {
result.add(perm);
}
}
return result;
}
private boolean hasPermission(String permission) {
if (canMakeSmores()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return (checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED);
}
}
return true;
}
private boolean canMakeSmores() {
return (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1);
}
@TargetApi(Build.VERSION_CODES.M)
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case ALL_PERMISSIONS_RESULT:
for (String perms : permissionsToRequest) {
if (!hasPermission(perms)) {
permissionsRejected.add(perms);
}
}
if (permissionsRejected.size() > 0) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (shouldShowRequestPermissionRationale(permissionsRejected.get(0))) {
showMessageOKCancel("These permissions are mandatory for the application. Please allow access.",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(permissionsRejected.toArray(new String[permissionsRejected.size()]), ALL_PERMISSIONS_RESULT);
}
}
});
return;
}
}
} else {
fetchLocation();
}
break;
}
}
private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(MainActivity.this)
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", null)
.create()
.show();
}
private void fetchLocation() {
SmartLocation.with(this).location()
.oneFix()
.start(new OnLocationUpdatedListener() {
@Override
public void onLocationUpdated(Location location) {
latLngString = location.getLatitude() + "," + location.getLongitude();
latLng = new LatLng(location.getLatitude(), location.getLongitude());
}
});
}
private void fetchDistance(final PlacesPOJO.CustomA info) {
Call call = apiService.getDistance(APIClient.GOOGLE_PLACE_API_KEY, latLngString, info.geometry.locationA.lat + "," + info.geometry.locationA.lng);
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
ResultDistanceMatrix resultDistance = response.body();
if ("OK".equalsIgnoreCase(resultDistance.status)) {
ResultDistanceMatrix.InfoDistanceMatrix infoDistanceMatrix = resultDistance.rows.get(0);
ResultDistanceMatrix.InfoDistanceMatrix.DistanceElement distanceElement = infoDistanceMatrix.elements.get(0);
if ("OK".equalsIgnoreCase(distanceElement.status)) {
ResultDistanceMatrix.InfoDistanceMatrix.ValueItem itemDuration = distanceElement.duration;
ResultDistanceMatrix.InfoDistanceMatrix.ValueItem itemDistance = distanceElement.distance;
String totalDistance = String.valueOf(itemDistance.text);
String totalDuration = String.valueOf(itemDuration.text);
storeModels.add(new StoreModel(info.name, info.vicinity, totalDistance, totalDuration));
if (storeModels.size() == 10 || storeModels.size() == results.size()) {
RecyclerViewAdapter adapterStores = new RecyclerViewAdapter(results, storeModels);
recyclerView.setAdapter(adapterStores);
}
}
}
}
@Override
public void onFailure(Call call, Throwable t) {
call.cancel();
}
});
}
}
В приведенном выше коде мы начинаем с запроса разрешений на выполнение времени, за которым следует получение текущего местоположения с использованием библиотеки SmartLocation. Как только это сделано, мы передаем первое слово из EditText в типе и второе слово в параметре имени метода fetchStores()
, который в конечном итоге вызывает веб-сервис API Google Places. Мы ограничиваем результаты поиска 10. Для каждого результата мы вычисляем расстояние и время от магазина внутри метода fetchDistance()
. Как только это сделано для всех магазинов, мы заполняем данные внутри класса RecyclerViewAdapter.java
с использованием класса данных StoreModel.java
. Код StoreModel.java приведен ниже:
package com.journaldev.nearbyplaces;
public class StoreModel {
public String name, address, distance, duration;
public StoreModel(String name, String address, String distance, String duration) {
this.name = name;
this.address = address;
this.distance = distance;
this.duration = duration;
}
}
Макет для каждой строки RecyclerView приведен в xml ниже: store_list_row.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_horizontal_margin"
android:orientation="vertical">
<android.support.v7.widget.CardView xmlns:card_view="https://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardCornerRadius="0dp"
card_view:cardElevation="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="5dp">
<TextView
android:id="@+id/txtStoreName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="5dp"
android:textColor="#212121" />
<TextView
android:id="@+id/txtStoreAddr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="5dp"
android:textColor="#212121" />
<TextView
android:id="@+id/txtStoreDist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="5dp" />
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
Код RecyclerViewAdapter.java приведен ниже.
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyViewHolder> {
private List<PlacesPOJO.CustomA> stLstStores;
private List<StoreModel> models;
public RecyclerViewAdapter(List<PlacesPOJO.CustomA> stores, List<StoreModel> storeModels) {
stLstStores = stores;
models = storeModels;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.store_list_row, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.setData(stLstStores.get(holder.getAdapterPosition()), holder, models.get(holder.getAdapterPosition()));
}
@Override
public int getItemCount() {
return Math.min(5, stLstStores.size());
}
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView txtStoreName;
TextView txtStoreAddr;
TextView txtStoreDist;
StoreModel model;
public MyViewHolder(View itemView) {
super(itemView);
this.txtStoreDist = (TextView) itemView.findViewById(R.id.txtStoreDist);
this.txtStoreName = (TextView) itemView.findViewById(R.id.txtStoreName);
this.txtStoreAddr = (TextView) itemView.findViewById(R.id.txtStoreAddr);
}
public void setData(PlacesPOJO.CustomA info, MyViewHolder holder, StoreModel storeModel) {
this.model = storeModel;
holder.txtStoreDist.setText(model.distance + "\n" + model.duration);
holder.txtStoreName.setText(info.name);
holder.txtStoreAddr.setText(info.vicinity);
}
}
}
Результат работы примерного приложения Google Places API приведен ниже: Примечание: API Places не точен для McDonalds и некоторых сетей питания, особенно для местоположений в Индии. Один из обходных путей – передать значение в параметре
name
в кавычках, например:
Call call = apiService.doPlaces(placeType, latLngString,"\""+ businessName +"\"", true, "distance", APIClient.GOOGLE_PLACE_API_KEY);
Результат для моего местоположения показан ниже: Это завершает этот учебный материал. Вы можете загрузить окончательный пример проекта Google Places API по ссылке ниже.
Source:
https://www.digitalocean.com/community/tutorials/google-places-api