Google Places API可以用来查找附近的地点。在本教程中,我们将开发一个应用程序,该应用程序显示我们选择的附近地点,以及离我们当前位置的大致距离和时间。我们将在应用程序中使用带有距离矩阵API的Google Places API Web服务。
Google Places API
Google Places API Web Service允许我们根据几个参数来查询位置,例如位置类型、某个位置现在是否开放等。附近的搜索请求是以下形式的HTTP URL:
1https://maps.googleapis.com/maps/api/place/nearbysearch/output?parameters
json
为推荐的output
,另一个为xml
,必选参数为:
1.Key (API密钥) 2.位置 3.rankby=距离 或** 半径** :一个用了,另一个就不能用了。
注意 :rankby=distance
需要指定以下参数之一:
1.名称 :取值范围包括麦当劳、肯德基等。 2.类型 :取值范围为餐厅、咖啡馆等。 3.关键词
可选参数可以是OpenNow
、Pagetoken
等,详见this页面。
Google距离矩阵接口
距离矩阵接口
用于计算两个或多个点之间的距离和时间。距离矩阵API URL的格式为:
1https://maps.googleapis.com/maps/api/distancematrix/outputFormat?parameters
必选参数为Origins
、estinations
和key
。原点-它包含计算旅行距离和时间的起点。我们可以传递由管道(|)分隔的多组坐标。我们还可以传递地址/地点ID而不是坐标,服务会自动将它们转换为经纬度坐标,以计算距离和持续时间。示例代码:
1https://maps.googleapis.com/maps/api/distancematrix/json?origins=Washington,DC&destinations=New+York+City,NY&key=YOUR_API_KEY
可选参数包括:
1.模式 :取值范围为driving
、bicling
、walking
、transit
2.避免 :引入通行费
、室内
等路由限制
有关更多详细信息,请访问this页面。
开启API密钥
进入https://console.developers.google.com/
,开通以下接口:
1.Google地图距离矩阵接口 2.Google Places API Web服务 3.Android版Google Places API
转到).中显示这些地点我们将根据将在EditText中输入并由空格分隔的类型和名称关键字来搜索地点。例如:餐厅多米诺骨牌 或** 咖啡馆素食者**
Google Places API示例项目结构
该项目由单个活动组成。用于RecclerView的适配器类。一个Model类,它保存每一个RecclerView行的数据。两个POJO类,用于将JSON响应从Google Places API和Distance Matrix API转换为Gson。用于使用Retrofit和终端的APIClient和API接口。
Google Places API示例代码
在Build.gradle
文件中添加以下依赖项
1compile 'com.google.android.gms:play-services-location:10.2.1'
2 compile 'com.google.android.gms:play-services-places:10.2.1'
3 compile 'com.google.code.gson:gson:2.7'
4 compile 'com.squareup.retrofit2:retrofit:2.1.0'
5 compile 'com.squareup.retrofit2:converter-gson:2.1.0'
6 compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
7 compile 'com.squareup.okhttp3:okhttps:3.4.1'
8 compile 'io.nlopez.smartlocation:library:3.3.1'
9 compile 'com.android.support:cardview-v7:25.3.0'
10 compile 'com.android.support:recyclerview-v7:25.3.0'
[library](https://github.com/mrmans0n/smart-location-lib)‘io.nlopez.SmartLocation:库:3.3.1’
是一个位置跟踪第三方库,它减少了样板代码。APIClient.java 代码如下:
1package com.journaldev.nearbyplaces;
2
3import java.util.concurrent.TimeUnit;
4import okhttp3.OkHttpClient;
5import okhttp3.logging.HttpLoggingInterceptor;
6import retrofit2.Retrofit;
7import retrofit2.converter.gson.GsonConverterFactory;
8
9public class APIClient {
10
11 private static Retrofit retrofit = null;
12
13 public static final String GOOGLE_PLACE_API_KEY = "ADD_YOUR_API_KEY_HERE";
14
15 public static String base_url = "https://maps.googleapis.com/maps/api/";
16
17 public static Retrofit getClient() {
18
19 HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
20 interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
21 OkHttpClient client = new OkHttpClient.Builder().readTimeout(30, TimeUnit.SECONDS).writeTimeout(30, TimeUnit.SECONDS).addInterceptor(interceptor).build();
22
23 retrofit = null;
24
25 retrofit = new Retrofit.Builder()
26 .baseUrl(base_url)
27 .addConverterFactory(GsonConverterFactory.create())
28 .client(client)
29 .build();
30
31 return retrofit;
32 }
33
34}
ApiInterface.java 代码如下所示
1package com.journaldev.nearbyplaces;
2
3import retrofit2.Call;
4import retrofit2.http.GET;
5import retrofit2.http.Query;
6
7public interface ApiInterface {
8
9 @GET("place/nearbysearch/json?")
10 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);
11
12 @GET("distancematrix/json") // origins/destinations: LatLng as string
13 Call<ResultDistanceMatrix> getDistance(@Query("key") String key, @Query("origins") String origins, @Query("destinations") String destinations);
14}
PlacesPOJO.java 是保存Places API响应的文件。其代码如下所示
1package com.journaldev.nearbyplaces;
2
3import com.google.gson.annotations.SerializedName;
4import java.io.Serializable;
5import java.util.ArrayList;
6import java.util.List;
7
8public class PlacesPOJO {
9
10 public class Root implements Serializable {
11
12 @SerializedName("results")
13 public List<CustomA> customA = new ArrayList<>();
14 @SerializedName("status")
15 public String status;
16 }
17
18 public class CustomA implements Serializable {
19
20 @SerializedName("geometry")
21 public Geometry geometry;
22 @SerializedName("vicinity")
23 public String vicinity;
24 @SerializedName("name")
25 public String name;
26
27 }
28
29 public class Geometry implements Serializable{
30
31 @SerializedName("location")
32 public LocationA locationA;
33
34 }
35
36 public class LocationA implements Serializable {
37
38 @SerializedName("lat")
39 public String lat;
40 @SerializedName("lng")
41 public String lng;
42
43 }
44
45}
ResultDistanceMatrix.java 类保存距离矩阵接口的响应。其代码如下:
1package com.journaldev.nearbyplaces;
2
3import com.google.gson.annotations.SerializedName;
4
5import java.util.List;
6
7public class ResultDistanceMatrix {
8 @SerializedName("status")
9 public String status;
10
11 @SerializedName("rows")
12 public List<InfoDistanceMatrix> rows;
13
14 public class InfoDistanceMatrix {
15 @SerializedName("elements")
16 public List elements;
17
18 public class DistanceElement {
19 @SerializedName("status")
20 public String status;
21 @SerializedName("duration")
22 public ValueItem duration;
23 @SerializedName("distance")
24 public ValueItem distance;
25
26 }
27
28 public class ValueItem {
29 @SerializedName("value")
30 public long value;
31 @SerializedName("text")
32 public String text;
33
34 }
35 }
36}
active_main.xml 文件如下所示
1<?xml version="1.0" encoding="utf-8"?>
2<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
3 xmlns:tools="https://schemas.android.com/tools"
4 android:layout_width="match_parent"
5 android:layout_height="match_parent"
6 android:background="#212121"
7 tools:context="com.journaldev.nearbyplaces.MainActivity">
8
9 <EditText
10 android:id="@+id/editText"
11 android:layout_width="match_parent"
12 android:textColor="@android:color/white"
13 android:textColorHint="@android:color/white"
14 android:text="restaurant mcdonalds"
15 android:hint="type name"
16 android:layout_height="wrap_content"
17 android:layout_alignParentTop="true"
18 android:layout_toLeftOf="@+id/button"
19 android:layout_toStartOf="@+id/button" />
20
21 <Button
22 android:id="@+id/button"
23 android:layout_width="wrap_content"
24 android:layout_height="wrap_content"
25 android:layout_alignParentEnd="true"
26 android:layout_alignParentRight="true"
27 android:text="Search" />
28
29 <android.support.v7.widget.RecyclerView
30 android:id="@+id/recyclerView"
31 android:layout_width="match_parent"
32 android:layout_height="match_parent"
33 android:layout_below="@+id/editText"
34 android:scrollbars="vertical" />
35
36</RelativeLayout>
下面给出了MainActivity.java
类代码。
1package com.journaldev.nearbyplaces;
2
3import android.annotation.TargetApi;
4import android.content.DialogInterface;
5import android.content.pm.PackageManager;
6import android.location.Location;
7import android.os.Build;
8import android.support.v7.app.AlertDialog;
9import android.support.v7.app.AppCompatActivity;
10import android.os.Bundle;
11import android.support.v7.widget.LinearLayoutManager;
12import android.support.v7.widget.RecyclerView;
13import android.view.View;
14import android.widget.Button;
15import android.widget.EditText;
16import android.widget.Toast;
17import com.google.android.gms.maps.model.LatLng;
18import java.util.ArrayList;
19import java.util.List;
20
21import io.nlopez.smartlocation.OnLocationUpdatedListener;
22import io.nlopez.smartlocation.SmartLocation;
23import retrofit2.Call;
24import retrofit2.Callback;
25import retrofit2.Response;
26
27import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
28import static android.Manifest.permission.ACCESS_FINE_LOCATION;
29
30public class MainActivity extends AppCompatActivity {
31
32 private ArrayList<String> permissionsToRequest;
33 private ArrayList<String> permissionsRejected = new ArrayList<>();
34 private ArrayList<String> permissions = new ArrayList<>();
35 private final static int ALL_PERMISSIONS_RESULT = 101;
36 List<StoreModel> storeModels;
37 ApiInterface apiService;
38
39 String latLngString;
40 LatLng latLng;
41
42 RecyclerView recyclerView;
43 EditText editText;
44 Button button;
45 List<PlacesPOJO.CustomA> results;
46
47 @Override
48 protected void onCreate(Bundle savedInstanceState) {
49 super.onCreate(savedInstanceState);
50 setContentView(R.layout.activity_main);
51
52 permissions.add(ACCESS_FINE_LOCATION);
53 permissions.add(ACCESS_COARSE_LOCATION);
54
55 permissionsToRequest = findUnAskedPermissions(permissions);
56
57 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
58
59 if (permissionsToRequest.size() > 0)
60 requestPermissions(permissionsToRequest.toArray(new String[permissionsToRequest.size()]), ALL_PERMISSIONS_RESULT);
61 else {
62 fetchLocation();
63 }
64 } else {
65 fetchLocation();
66 }
67
68 apiService = APIClient.getClient().create(ApiInterface.class);
69
70 recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
71
72 recyclerView.setNestedScrollingEnabled(false);
73 recyclerView.setHasFixedSize(true);
74
75 LinearLayoutManager layoutManager = new LinearLayoutManager(this);
76 recyclerView.setLayoutManager(layoutManager);
77
78 editText = (EditText) findViewById(R.id.editText);
79 button = (Button) findViewById(R.id.button);
80
81 button.setOnClickListener(new View.OnClickListener() {
82 @Override
83 public void onClick(View v) {
84 String s = editText.getText().toString().trim();
85 String[] split = s.split("\\s+");
86
87 if (split.length != 2) {
88 Toast.makeText(getApplicationContext(), "Please enter text in the required format", Toast.LENGTH_SHORT).show();
89 } else
90 fetchStores(split[0], split[1]);
91 }
92 });
93
94 }
95
96 private void fetchStores(String placeType, String businessName) {
97
98 /**
99 * For Locations In India McDonalds stores aren't returned accurately
100 */
101
102 //Call<PlacesPOJO.Root> call = apiService.doPlaces(placeType, latLngString,"\""+ businessName +"\"", true, "distance", APIClient.GOOGLE_PLACE_API_KEY);
103
104 Call<PlacesPOJO.Root> call = apiService.doPlaces(placeType, latLngString, businessName, true, "distance", APIClient.GOOGLE_PLACE_API_KEY);
105 call.enqueue(new Callback<PlacesPOJO.Root>() {
106 @Override
107 public void onResponse(Call<PlacesPOJO.Root> call, Response<PlacesPOJO.Root> response) {
108 PlacesPOJO.Root root = response.body();
109
110 if (response.isSuccessful()) {
111
112 if (root.status.equals("OK")) {
113
114 results = root.customA;
115 storeModels = new ArrayList<>();
116 for (int i = 0; i < results.size(); i++) {
117
118 if (i == 10)
119 break;
120 PlacesPOJO.CustomA info = results.get(i);
121
122 fetchDistance(info);
123
124 }
125
126 } else {
127 Toast.makeText(getApplicationContext(), "No matches found near you", Toast.LENGTH_SHORT).show();
128 }
129
130 } else if (response.code() != 200) {
131 Toast.makeText(getApplicationContext(), "Error " + response.code() + " found.", Toast.LENGTH_SHORT).show();
132 }
133
134 }
135
136 @Override
137 public void onFailure(Call<PlacesPOJO.Root> call, Throwable t) {
138 // Log error here since request failed
139 call.cancel();
140 }
141 });
142
143 }
144
145 private ArrayList<String> findUnAskedPermissions(ArrayList<String> wanted) {
146 ArrayList<String> result = new ArrayList<>();
147
148 for (String perm : wanted) {
149 if (!hasPermission(perm)) {
150 result.add(perm);
151 }
152 }
153
154 return result;
155 }
156
157 private boolean hasPermission(String permission) {
158 if (canMakeSmores()) {
159 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
160 return (checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED);
161 }
162 }
163 return true;
164 }
165
166 private boolean canMakeSmores() {
167 return (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1);
168 }
169
170 @TargetApi(Build.VERSION_CODES.M)
171 @Override
172 public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
173
174 switch (requestCode) {
175
176 case ALL_PERMISSIONS_RESULT:
177 for (String perms : permissionsToRequest) {
178 if (!hasPermission(perms)) {
179 permissionsRejected.add(perms);
180 }
181 }
182
183 if (permissionsRejected.size() > 0) {
184
185 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
186 if (shouldShowRequestPermissionRationale(permissionsRejected.get(0))) {
187 showMessageOKCancel("These permissions are mandatory for the application. Please allow access.",
188 new DialogInterface.OnClickListener() {
189 @Override
190 public void onClick(DialogInterface dialog, int which) {
191 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
192 requestPermissions(permissionsRejected.toArray(new String[permissionsRejected.size()]), ALL_PERMISSIONS_RESULT);
193 }
194 }
195 });
196 return;
197 }
198 }
199
200 } else {
201 fetchLocation();
202 }
203
204 break;
205 }
206
207 }
208
209 private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
210 new AlertDialog.Builder(MainActivity.this)
211 .setMessage(message)
212 .setPositiveButton("OK", okListener)
213 .setNegativeButton("Cancel", null)
214 .create()
215 .show();
216 }
217
218 private void fetchLocation() {
219
220 SmartLocation.with(this).location()
221 .oneFix()
222 .start(new OnLocationUpdatedListener() {
223 @Override
224 public void onLocationUpdated(Location location) {
225 latLngString = location.getLatitude() + "," + location.getLongitude();
226 latLng = new LatLng(location.getLatitude(), location.getLongitude());
227 }
228 });
229 }
230
231 private void fetchDistance(final PlacesPOJO.CustomA info) {
232
233 Call<ResultDistanceMatrix> call = apiService.getDistance(APIClient.GOOGLE_PLACE_API_KEY, latLngString, info.geometry.locationA.lat + "," + info.geometry.locationA.lng);
234 call.enqueue(new Callback<ResultDistanceMatrix>() {
235 @Override
236 public void onResponse(Call<ResultDistanceMatrix> call, Response<ResultDistanceMatrix> response) {
237
238 ResultDistanceMatrix resultDistance = response.body();
239 if ("OK".equalsIgnoreCase(resultDistance.status)) {
240
241 ResultDistanceMatrix.InfoDistanceMatrix infoDistanceMatrix = resultDistance.rows.get(0);
242 ResultDistanceMatrix.InfoDistanceMatrix.DistanceElement distanceElement = infoDistanceMatrix.elements.get(0);
243 if ("OK".equalsIgnoreCase(distanceElement.status)) {
244 ResultDistanceMatrix.InfoDistanceMatrix.ValueItem itemDuration = distanceElement.duration;
245 ResultDistanceMatrix.InfoDistanceMatrix.ValueItem itemDistance = distanceElement.distance;
246 String totalDistance = String.valueOf(itemDistance.text);
247 String totalDuration = String.valueOf(itemDuration.text);
248
249 storeModels.add(new StoreModel(info.name, info.vicinity, totalDistance, totalDuration));
250
251 if (storeModels.size() == 10 || storeModels.size() == results.size()) {
252 RecyclerViewAdapter adapterStores = new RecyclerViewAdapter(results, storeModels);
253 recyclerView.setAdapter(adapterStores);
254 }
255
256 }
257
258 }
259
260 }
261
262 @Override
263 public void onFailure(Call<ResultDistanceMatrix> call, Throwable t) {
264 call.cancel();
265 }
266 });
267
268 }
269}
在上面的代码中,我们首先请求运行时权限,然后使用SmartLocation库获取当前位置。准备好之后,我们将EditText中的第一个单词传递到类型中,并将第二个单词传递到fetchStores()
方法的name参数中,该方法最终调用Google Places API Web服务。我们将搜索结果限制为10个。对于每个结果,我们在fetchDistance()
方法中计算到商店的距离和时间。一旦完成了对所有商店的操作,我们将使用StoreModel.java
数据类填充Recical erViewAdapter.java
类中的数据。StoreModel.java 代码如下:
1package com.journaldev.nearbyplaces;
2
3public class StoreModel {
4
5 public String name, address, distance, duration;
6
7 public StoreModel(String name, String address, String distance, String duration) {
8
9 this.name = name;
10 this.address = address;
11 this.distance = distance;
12 this.duration = duration;
13 }
14
15}
下面的XML给出了RecillerView的每一行的布局:store_list_row.xml
1<?xml version="1.0" encoding="utf-8"?>
2<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent"
4 android:layout_height="wrap_content"
5 android:layout_marginBottom="@dimen/activity_horizontal_margin"
6 android:orientation="vertical">
7
8 <android.support.v7.widget.CardView xmlns:card_view="https://schemas.android.com/apk/res-auto"
9 android:id="@+id/card_view"
10 android:layout_width="match_parent"
11 android:layout_height="wrap_content"
12 card_view:cardCornerRadius="0dp"
13 card_view:cardElevation="5dp">
14
15 <LinearLayout
16 android:layout_width="match_parent"
17 android:layout_height="wrap_content"
18 android:orientation="vertical"
19 android:padding="5dp">
20
21 <TextView
22 android:id="@+id/txtStoreName"
23 android:layout_width="wrap_content"
24 android:layout_height="wrap_content"
25 android:paddingBottom="5dp"
26 android:textColor="#212121" />
27
28 <TextView
29 android:id="@+id/txtStoreAddr"
30 android:layout_width="wrap_content"
31 android:layout_height="wrap_content"
32 android:paddingBottom="5dp"
33 android:textColor="#212121" />
34
35 <TextView
36 android:id="@+id/txtStoreDist"
37 android:layout_width="wrap_content"
38 android:layout_height="wrap_content"
39 android:paddingBottom="5dp" />
40
41 </LinearLayout>
42
43 </android.support.v7.widget.CardView>
44
45</LinearLayout>
下面给出了 ClerViewAdapter.java 代码。
1public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyViewHolder> {
2
3 private List<PlacesPOJO.CustomA> stLstStores;
4 private List<StoreModel> models;
5
6 public RecyclerViewAdapter(List<PlacesPOJO.CustomA> stores, List<StoreModel> storeModels) {
7
8 stLstStores = stores;
9 models = storeModels;
10 }
11
12 @Override
13 public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
14 final View view = LayoutInflater.from(parent.getContext())
15 .inflate(R.layout.store_list_row, parent, false);
16
17 return new MyViewHolder(view);
18 }
19
20 @Override
21 public void onBindViewHolder(MyViewHolder holder, int position) {
22
23 holder.setData(stLstStores.get(holder.getAdapterPosition()), holder, models.get(holder.getAdapterPosition()));
24 }
25
26 @Override
27 public int getItemCount() {
28 return Math.min(5, stLstStores.size());
29 }
30
31 public class MyViewHolder extends RecyclerView.ViewHolder {
32
33 TextView txtStoreName;
34 TextView txtStoreAddr;
35 TextView txtStoreDist;
36 StoreModel model;
37
38 public MyViewHolder(View itemView) {
39 super(itemView);
40
41 this.txtStoreDist = (TextView) itemView.findViewById(R.id.txtStoreDist);
42 this.txtStoreName = (TextView) itemView.findViewById(R.id.txtStoreName);
43 this.txtStoreAddr = (TextView) itemView.findViewById(R.id.txtStoreAddr);
44
45 }
46
47 public void setData(PlacesPOJO.CustomA info, MyViewHolder holder, StoreModel storeModel) {
48
49 this.model = storeModel;
50
51 holder.txtStoreDist.setText(model.distance + "\n" + model.duration);
52 holder.txtStoreName.setText(info.name);
53 holder.txtStoreAddr.setText(info.vicinity);
54
55 }
56
57 }
58}
Google Places API示例应用程序的运行输出如下:注:Places API对于麦当劳和一些食品连锁店是不准确的,特别是对于印度的位置。一种解决方法是在参数
name
中传递双引号中的值,例如:
1Call call = apiService.doPlaces(placeType, latLngString,"\""+ businessName +"\"", true, "distance", APIClient.GOOGLE_PLACE_API_KEY);