דוגמה לשירות API של Google Places Web

API מקומות של Google ניתן להשתמש בו כדי למצוא מקומות בקרבת מקום. במדריך זה, נפתח אפליקציה שמציגה את המקומות הקרובים אלינו לבחירתנו יחד עם המרחק האפרוקסימטי והזמן ממיקומנו הנוכחי. נשתמש ב-API של Google Places עם ממשק שירות האינטרנט של מטריצת מרחק ביישום.

ממשק ה- API של Google Places

מאפשר לנו לשאול את המקומות על פי מספר פרמטרים כמו סוג המקום, האם המקום פתוח כרגע וכו '. בקשת חיפוש בקרבת מקום היא כתובת URL של HTTP בפורמט הבא:

https://maps.googleapis.com/maps/api/place/nearbysearch/output?parameters

json הוא הפורמט המומלץ output, כשהאחר הוא xml. הפרמטרים הנדרשים הם:

  1. מפתח(מפתח API)
  2. מיקום
  3. rankby=מרחק או רדיוס : אם משמש פרמטר אחד, השני לא יכול להיות משמש.

הערה: rankby=מרחק דורש לציין אחד מהפרמטרים הבאים:

  1. שם: הערכים יכולים להיות mcdonalds, kfc וכו '.
  2. סוג : הערכים יכולים להיות מסעדה, קפה וכו '.
  3. מילת מפתח

הפרמטרים האופציונליים יכולים להיות 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

הפרמטרים האופציונליים הם:

  1. מצב: מצפה לערך בין נהיגה, רכיבה, הליכה, תחבורה ציבורית
  2. הימנע: מכניס אילוצים למסלול כמו עבר, פנים אולמות וכו'

לקבלת פרטים נוספים בקרו בדף הזה.

אפשרות הפעלת מפתחות API

עברו אל https://console.developers.google.com/ והפעילו את ה- API הבאים:

  1. API של Google Maps Distance Matrix
  2. שירות האינטרנט של Google Places API
  3. חבילת ה-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 הסופי מהקישור למטה.

הורד פרויקט לדוגמה של Google Places API

Source:
https://www.digitalocean.com/community/tutorials/google-places-api