API מקומות של Google ניתן להשתמש בו כדי למצוא מקומות בקרבת מקום. במדריך זה, נפתח אפליקציה שמציגה את המקומות הקרובים אלינו לבחירתנו יחד עם המרחק האפרוקסימטי והזמן ממיקומנו הנוכחי. נשתמש ב-API של Google Places עם ממשק שירות האינטרנט של מטריצת מרחק ביישום.
ממשק ה- API של Google Places
מאפשר לנו לשאול את המקומות על פי מספר פרמטרים כמו סוג המקום, האם המקום פתוח כרגע וכו '. בקשת חיפוש בקרבת מקום היא כתובת URL של HTTP בפורמט הבא:
https://maps.googleapis.com/maps/api/place/nearbysearch/output?parameters
json
הוא הפורמט המומלץ output
, כשהאחר הוא xml
. הפרמטרים הנדרשים הם:
- מפתח(מפתח API)
- מיקום
- rankby=מרחק או רדיוס : אם משמש פרמטר אחד, השני לא יכול להיות משמש.
הערה: rankby=מרחק
דורש לציין אחד מהפרמטרים הבאים:
- שם: הערכים יכולים להיות mcdonalds, kfc וכו '.
- סוג : הערכים יכולים להיות מסעדה, קפה וכו '.
- מילת מפתח
הפרמטרים האופציונליים יכולים להיות opennow
, pagetoken
וכו '. לפרטים נוספים ראו דף זה.
ממשק מטריצת מרחק של Google API
ממשק ה-Distance Matrix API
משמש לחישוב מרחק וזמן בין שני או יותר נקודות. כתובת URL של ממשק מטריצת המרחק בעלת התבנית:
https://maps.googleapis.com/maps/api/distancematrix/outputFormat?parameters
הפרמטרים הדרושים הם origins
, destinations
וה-key
. origins — מכיל את נקודת ההתחלה לחישוב מרחק וזמן נסיעה. אפשר לעבור יותר מאוד קבוצות של קואורדינטות מופרדות בצינור (|). ניתן גם להעביר את הכתובות / ה- place id במקום קואורדינטות והשירות ממיר אותם אוטומטית לקואורדינטות קווי-רוחב כדי לחשב מרחק ומשך. קוד לדוגמה:
https://maps.googleapis.com/maps/api/distancematrix/json?origins=Washington,DC&destinations=New+York+City,NY&key=YOUR_API_KEY
הפרמטרים האופציונליים הם:
- מצב: מצפה לערך בין
נהיגה
,רכיבה
,הליכה
,תחבורה ציבורית
- הימנע: מכניס אילוצים למסלול כמו
עבר
,פנים אולמות
וכו'
לקבלת פרטים נוספים בקרו בדף הזה.
אפשרות הפעלת מפתחות API
עברו אל https://console.developers.google.com/
והפעילו את ה- API הבאים:
- API של Google Maps Distance Matrix
- שירות האינטרנט של Google Places API
- חבילת ה-Android של Google Places API
עבור לכרטיסייה של כתוביות וצור מפתח חדש. הגדר את ההגבלה של המפתח לאין בשביל עכשיו. בואו נדבר על החלק העסקי של המדריך הזה. נפתח אפליקציה שתאפשר לנו לחפש מקומות קרובים לפי המיקום הנוכחי שלנו ולהציג את המקומות ב- RecyclerView . נחפש מקומות על פי הסוג והשם שיוזן ב- EditText ויפרידו ברווח. לדוגמה: מסעדה דומינוס או בית קפה צמחוני
מבנה פרויקט דוגמת Google Places API
הפרויקט מורכב מפעילות יחידה. כיתת מתאם עבור RecyclerView. כיתת מודל שמחזיקה את הנתונים עבור כל שורת RecyclerView. שני POJO קלאסים להמרת תגובות JSON ל- Gson מ- Google Places API ו-Distance Matrix API. 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'
הוא ספריית LocationTracking צד ג' שמפחיתה את קוד ה- boilerplate. קוד ה- 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 בפעולה ניתן למטה: לתשומת לב: גרסת ה-Places API אינה מדוייקת ל-McDonalds ולחלק מרשתות המזון, במיוחד למיקומים בהודו. דרך לעבור על זאת היא להעביר את הערך בפרמטר
name
בתוך מרכאות כפולות כגון:
Call call = apiService.doPlaces(placeType, latLngString,"\""+ businessName +"\"", true, "distance", APIClient.GOOGLE_PLACE_API_KEY);
הפלט מופיע כך עבור המיקום שלי ניתן למטה: זה מסיים את המדריך הזה. ניתן להוריד את הפרויקט לדוגמה של גוגל פלייס API הסופי מהקישור למטה.
Source:
https://www.digitalocean.com/community/tutorials/google-places-api