يمكن استخدام واجهة برمجة تطبيقات Google Places API للعثور على الأماكن القريبة. في هذا البرنامج التعليمي، سنقوم بتطوير تطبيق يعرض الأماكن القريبة من اختيارنا مع البعد والوقت التقريبي من موقعنا الحالي. سنستخدم واجهة برمجة تطبيقات Google Places API للويب مع واجهة برمجة Distance Matrix API في التطبيق.
واجهة برمجة تطبيقات Google Places API
تسمح لنا واجهة برمجة تطبيقات Google Places API للويب بالاستعلام عن الأماكن استنادًا إلى بعض المعلمات مثل نوع المكان وما إذا كان المكان مفتوحًا الآن وما إلى ذلك. طلب البحث عن الأماكن القريبة هو عنوان URL لبروتوكول نقل النص الفائق (HTTP) بالصيغة التالية:
https://maps.googleapis.com/maps/api/place/nearbysearch/output?parameters
json
هو المخرج
الموصى به، بالإضافة إلى xml
. المعلمات المطلوبة هي:
- key(مفتاح واجهة برمجة التطبيق)
- location
- rankby=distance أو radius: إذا تم استخدام أحدهما، لا يمكن استخدام الآخر.
ملاحظة: يتطلب rankby=distance
تحديد أحد المعلمات التالية:
- name: القيم يمكن أن تكون mcdonalds، kfc وما إلى ذلك.
- type: القيم يمكن أن تكون restaurant، cafe وما إلى ذلك.
- keyword
يمكن أن تكون المعلمات الاختيارية opennow
، pagetoken
وما إلى ذلك. لمزيد من التفاصيل، راجع هذه الصفحة.
واجهة برمجة تطبيقات Google Distance Matrix
تُستخدم واجهة برمجة التطبيقات Distance Matrix لحساب المسافة والوقت بين نقطتين أو أكثر. عنوان URL لواجهة برمجة التطبيقات Distance Matrix على النحو التالي:
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
المعلمات الاختيارية هي:
- الوضع: يتوقع أن يكون له قيمة من بين
القيادة
،ركوب الدراجات
،المشي
،المواصلات
- تجنب: يضع قيودًا على المسار مثل
الرسوم
،المرافق الداخلية
الخ
لمزيد من التفاصيل قم بزيارة هذه الصفحة.
تمكين مفاتيح API
انتقل إلى https://console.developers.google.com/
وقم بتمكين الواجهات البرمجية التالية:
- خدمة واجهة برمجة تطبيقات Google Maps Distance Matrix
- خدمة واجهة برمجة تطبيقات Google Places
- خدمة واجهة برمجة تطبيقات Google Places لنظام Android
انتقل إلى الاعتمادات وأنشئ مفتاحًا جديدًا. قم بتعيين قيد المفتاح إلى “بلا قيود” في الوقت الحالي. دعنا ننتقل إلى الجزء المهم من هذا البرنامج التعليمي. سنقوم بتطوير تطبيق يتيح لنا البحث عن أماكن قريبة بناءً على موقعنا الحالي وعرض هذه الأماكن في عنصر “RecyclerView”. سنقوم بالبحث عن الأماكن بناءً على النوع والكلمات الرئيسية التي سيتم إدخالها في EditText وفصلها بواسطة مسافة. مثال: مطعم دومينوز أو مقهى نباتي
هيكل مشروع مثال لخدمة واجهة برمجة تطبيقات Google Places
تتكون المشروع من نشاط واحد. فئة محول لـ 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'
هو مكتبة تتبع الموقع من طرف ثالث تقلل من رمز النموذج. يتم توفير رمز 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()
التي في النهاية تقوم بإستدعاء خدمة ويب Google Places API. نحدد نتائج البحث إلى 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);
}
}
}
يتم تقديم نتائج تطبيق مثال واجهة برمجة تطبيقات جوجل بليس في الأسفل: ملاحظة: واجهة برمجة تطبيقات الأماكن غير دقيقة بالنسبة لمطاعم ماكدونالدز وبعض سلاسل المطاعم الأخرى، خاصة للمواقع في الهند. يمكن استخدام طريقة للتغلب على هذه المشكلة وذلك من خلال تمرير القيمة في البارامتر
name
بين علامتي اقتباس مزدوجة مثل هذا:
Call call = apiService.doPlaces(placeType, latLngString,"\""+ businessName +"\"", true, "distance", APIClient.GOOGLE_PLACE_API_KEY);
تظهر النتيجة على النحو التالي لموقعي: هذا ينهي هذا البرنامج التعليمي. يمكنك تنزيل مشروع واجهة برمجة تطبيقات جوجل بليس النهائي من الرابط أدناه.
Source:
https://www.digitalocean.com/community/tutorials/google-places-api