Google Places API Anleitung

Die Google Places API kann verwendet werden, um nahegelegene Orte zu finden. In diesem Tutorial entwickeln wir eine Anwendung, die die nahegelegenen Orte unserer Wahl zusammen mit der ungefähren Entfernung und Zeit von unserem aktuellen Standort anzeigt. Wir verwenden den Google Places API Web Service mit der Distance Matrix API in der Anwendung.

Google Places API

Der Google Places API Web Service ermöglicht es uns, Orte basierend auf einigen Parametern wie dem Ortstyp oder ob ein Ort gerade geöffnet ist, abzufragen. Eine Nearby-Search-Anfrage ist eine HTTP-URL in folgender Form:

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

json wird als Ausgabe empfohlen, die andere Möglichkeit ist xml. Die erforderlichen Parameter sind:

  • key (API-Schlüssel)
  • location
  • rankby=distance oder radius: Wenn eines verwendet wird, kann das andere nicht verwendet werden.

Hinweis: rankby=distance erfordert die Angabe eines der folgenden Parameter:

  • name: Werte können z. B. McDonald’s, KFC usw. sein.
  • type: Werte können z. B. Restaurant, Café usw. sein.
  • keyword

Die optionalen Parameter können opennow, pagetoken usw. sein. Weitere Details finden Sie auf dieser Seite.

Google Distance Matrix API

Die Distance Matrix API wird verwendet, um die Entfernung und die Zeit zwischen zwei oder mehr Punkten zu berechnen. Eine Distance Matrix API URL hat folgende Form:

https://maps.googleapis.com/maps/api/distancematrix/outputFormat?parameters

Die erforderlichen Parameter sind origins, destinations und der API-Schlüssel.

  • origins: Dies enthält den Startpunkt für die Berechnung der Reisestrecke und -zeit. Wir können mehr als ein Koordinatenset mit Pipeline-Zeichen (|) getrennt übergeben. Alternativ können wir auch Adressen/Orts-IDs statt Koordinaten übergeben, und der Service wandelt diese automatisch in Breitengrad-/Längengrad-Koordinaten um, um Entfernung und Dauer zu berechnen.

Beispielcode:

https://maps.googleapis.com/maps/api/distancematrix/json?origins=Washington,DC&destinations=New+York+City,NY&key=YOUR_API_KEY

Optionale Parameter sind:

  • mode: erwartet Werte wie driving, bicycling, walking, transit
  • avoid: Fügt Einschränkungen zur Route hinzu, wie z. B. Mautstellen, Indoor usw.

Weitere Details finden Sie auf dieser Seite.

API-Schlüssel aktivieren

Gehen Sie zu https://console.developers.google.com/ und aktivieren Sie die folgenden APIs:

  • Google Maps Distance Matrix API
  • Google Places API Web Service
  • Google Places API für Android

Gehen Sie zu „Credentials“ und erstellen Sie einen neuen Schlüssel. Setzen Sie die Schlüsselbeschränkung vorerst auf „Keine“. Nun zum praktischen Teil dieses Tutorials: Wir entwickeln eine Anwendung, die es uns ermöglicht, nahegelegene Orte basierend auf unserem aktuellen Standort zu suchen und die Orte in einer RecyclerView anzuzeigen. Wir suchen Orte basierend auf den Typ- und Namen-Schlüsselwörtern, die im EditText eingegeben und durch ein Leerzeichen getrennt werden. Beispiel: restaurant dominos oder café vegetarisch.

Beispielprojektstruktur für Google Places API


Das Projekt besteht aus:

  • Eine einzelne Aktivität
  • Eine Adapterklasse für die RecyclerView
  • Eine Modellklasse, die die Daten für jede RecyclerView-Zeile enthält
  • Zwei POJO-Klassen zur Konvertierung der JSON-Antworten in Gson von der Google Places API und der Distance Matrix API
  • APIClient und ApiInterface zur Nutzung von Retrofit und den Endpunkten

Google Places API Beispielcode

Fügen Sie die folgenden Abhängigkeiten in die Datei build.gradle ein:

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'

APIClient.java Code

compile ‚io.nlopez.smartlocation:library:3.3.1‘ ist eine Drittanbieterbibliothek für Standortverfolgung, die Boilerplate-Code reduziert. Der Code für APIClient.java lautet:

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 = new Retrofit.Builder()
                .baseUrl(base_url)
                .addConverterFactory(GsonConverterFactory.create())
                .client(client)
                .build();

        return retrofit;
    }
}

ApiInterface.java Code

Der Code für ApiInterface.java lautet:

package com.journaldev.nearbyplaces;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

public interface ApiInterface {

    @GET("place/nearbysearch/json?")
    Call 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")
    Call getDistance(@Query("key") String key,
                                           @Query("origins") String origins,
                                           @Query("destinations") String destinations);
}

PlacesPOJO.java Code

Die Datei PlacesPOJO.java enthält die Antwort der Places API. Der Code lautet:

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 = 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 Code

Die ResultDistanceMatrix.java-Klasse enthält die Antwort der Distance Matrix API. Der Code ist unten angegeben:

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 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 Layout: activity_main.xml

Die Datei activity_main.xml ist unten angegeben:

<?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 Code

Der Code für die MainActivity.java-Klasse ist unten angegeben:

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<String> permissionsToRequest;
    private ArrayList<String> permissionsRejected = new ArrayList<>();
    private ArrayList<String> permissions = new ArrayList<>();
    private final static int ALL_PERMISSIONS_RESULT = 101;
    List<StoreModel> storeModels;
    ApiInterface apiService;

    String latLngString;
    LatLng latLng;

    RecyclerView recyclerView;
    EditText editText;
    Button button;
    List<PlacesPOJO.CustomA> 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<PlacesPOJO.Root> call = apiService.doPlaces(placeType, latLngString,"\""+ businessName +"\"", true, "distance", APIClient.GOOGLE_PLACE_API_KEY);

        Call<PlacesPOJO.Root> call = apiService.doPlaces(placeType, latLngString, businessName, true, "distance", APIClient.GOOGLE_PLACE_API_KEY);
        call.enqueue(new Callback<PlacesPOJO.Root>() {
            @Override
            public void onResponse(Call<PlacesPOJO.Root> call, Response<PlacesPOJO.Root> 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<PlacesPOJO.Root> call, Throwable t) {
                // Log error here since request failed
                call.cancel();
            }
        });


    }

    private ArrayList<String> findUnAskedPermissions(ArrayList<String> wanted) {
        ArrayList<String> 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<ResultDistanceMatrix> call = apiService.getDistance(APIClient.GOOGLE_PLACE_API_KEY, latLngString, info.geometry.locationA.lat + "," + info.geometry.locationA.lng);
        call.enqueue(new Callback<ResultDistanceMatrix>() {
            @Override
            public void onResponse(Call<ResultDistanceMatrix> call, Response<ResultDistanceMatrix> 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<ResultDistanceMatrix> call, Throwable t) {
                call.cancel();
            }
        });

    }
}

Im obigen Code beginnen wir mit der Anforderung von Laufzeitberechtigungen, gefolgt vom Abrufen des aktuellen Standorts mithilfe der SmartLocation-Bibliothek. Sobald dies eingerichtet ist, übergeben wir das erste Wort aus dem EditText im type-Parameter und das zweite Wort im name-Parameter der fetchStores()-Methode, die schließlich den Google Places API-Webdienst aufruft. Wir begrenzen die Suchergebnisse auf 10. Für jedes Ergebnis berechnen wir die Entfernung und die Zeit zum Geschäft innerhalb der Methode fetchDistance(). Sobald dies für alle Geschäfte abgeschlossen ist, füllen wir die Daten in der RecyclerViewAdapter.java-Klasse mithilfe einer StoreModel.java-Datenklasse aus.

StoreModel.java Code

Der StoreModel.java Code ist unten angegeben:

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 Zeilenlayout: store_list_row.xml

Das Layout für jede Zeile der RecyclerView ist im untenstehenden XML angegeben:

<?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 Code

Der RecyclerViewAdapter.java Code ist unten angegeben:

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);


        }

    }
}

Fazit

Dieser Artikel bietet eine klare Anleitung zur Nutzung der Google Places API und der Distance Matrix API, um nahegelegene Standorte, Entfernungen und Reisezeiten in einer Android-App anzuzeigen. Durch die Kombination von Tools wie Retrofit, Gson und RecyclerView haben wir eine effiziente und skalierbare Lösung geschaffen.

Die Anwendung dient als solide Grundlage, die mit Funktionen wie erweiterten Filtern, verbessertem UI oder Integrationen mit zusätzlichen Google APIs erweitert werden kann. Dieses Projekt ist ein hervorragendes Beispiel für die Verwendung von APIs zusammen mit modernen Android-Entwicklungspraktiken.

Kostenlosen Account erstellen

Registrieren Sie sich jetzt und erhalten Sie Zugang zu unseren Cloud Produkten.

Das könnte Sie auch interessieren: