Menentukan Rute Terdekat dari Lokasi menggunakan Google Map

Tutorial ini membahas mengenai bagaimana membuat rute terdekat dari lokasi menggunakan google map, dimana untuk menentukan rute terdekat dari lokasi tersebut membutuhkan API dari Google Developers anda bisa membaca dokumentasi dibagian sini.

Kotlin - Menentukan Rute Terdekat dari Lokasi menggunakan Google Map

Sebelum Anda mulai membuat proyek ini, Anda membutuhkan API keys supaya aplikasi dapat berjalan dengan Google Maps, untuk membuat Google Maps API key disini

API yang digunakan untuk membuat rute terdekat dari lokasi, yang dimana untuk output format menggunakan json.

http://maps.googleapis.com/maps/api/directions/json?parameters

Dari API diatas ada parameter yang harus diprelukan antara lain:

  • origin - Lokasi asal dengan mengisikan berupa alamat, nilai dari garis lintang (Latitude) dan bujur (Longitude) atau ID tempat dimana anda ingin menentukan rute.
    • Jika anda mengisikan berupa alamat anda perlu memasukan pada parameter origin
      origin=Mataram
    • Jika anda mengisikan dengan nilai lintang dan bujur anda harus memasukan bagian parameter origin
      origin=-8.594848, 116.105390
    • Jika anda mengisikan dengan nilai ID anda harus memasukan bagian parameter origin
      origin=place_id:DhIJSS-JXXauEmsRUcIaWtf5MzE
  • destination - Lokasi tujuan dengan mengisikan berupa alamat, nilai dari garis lintang (Latitude) dan bujur (Longitude) atau ID, untuk opsi destination hampirsama pengisian dengan origin.
  • key - Kunci API aplikasi yang harus anda buat untuk ditempatkan di parameter key

Menyiapkan Proyek

Langkah pertama masukan dependencies dibagian file build gradle

implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.google.android.gms:play-services-maps:16.1.0'
implementation 'com.google.android.gms:play-services-location:16.0.0'
implementation 'com.google.android.gms:play-services-places:16.0.0'
implementation 'com.google.android.libraries.places:places-compat:1.1.0'
implementation 'com.google.maps.android:android-maps-utils:0.5+'

Download resource disini dan tempatkan dibagain drawable

Langkah Membuat Proyek

Bagian Manifests Masukan Api Keys dibagain value dari metadata.

.....
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

<application
.....
<meta-data
    android:name="com.google.android.geo.API_KEY"
    android:value="YOU HERE KEYS" />

<service
   android:name=".service.FetchAddressIntentService"
   android:exported="false"/>

</application>

Menambahkan Permission GPS, fungsinya saat aplikasi dijalankan akan tampil dialog untuk meminta mengaktifkan GPS.

package com.kodetr.googlemap.utils;

import android.app.Activity;
import android.content.Context;
import android.content.IntentSender;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.location.LocationSettingsResult;
import com.google.android.gms.location.LocationSettingsStatusCodes;

import java.util.List;

/**
 * Created by kodetr on 09/05/19.
 */

public class PermissionGPS {

    private Activity activity;

    private GoogleApiClient googleApiClient;
    final static int REQUEST_LOCATION = 199;

    public PermissionGPS(Activity activity) {
        this.activity = activity;
        // Todo Location Already on  ... start
        final LocationManager manager = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE);
        if (manager.isProviderEnabled(LocationManager.GPS_PROVIDER) && hasGPSDevice(activity)) {
        // Toast.makeText(activity, "Gps already enabled", Toast.LENGTH_SHORT).show();
        }
        
        // Todo Location Already on  ... end
        if (!hasGPSDevice(activity)) {
            Toast.makeText(activity, "Gps tidak mendukung", Toast.LENGTH_SHORT).show();
        }

        if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER) && hasGPSDevice(activity)) {
            Log.e("TAG", "Gps already enabled");
            enableLoc();
        } else {
            Log.e("TAG", "Gps already enabled");
        }
    }

    private boolean hasGPSDevice(Context context) {
        final LocationManager mgr = (LocationManager) context
                .getSystemService(Context.LOCATION_SERVICE);
        if (mgr == null)
            return false;
        final List<String> providers = mgr.getAllProviders();
        if (providers == null)
            return false;
        return providers.contains(LocationManager.GPS_PROVIDER);
    }

    private void enableLoc() {
        if (googleApiClient == null) {
            googleApiClient = new GoogleApiClient.Builder(activity)
                    .addApi(LocationServices.API)
                    .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                        @Override
                        public void onConnected(Bundle bundle) {

                        }

                        @Override
                        public void onConnectionSuspended(int i) {
                            googleApiClient.connect();
                        }
                    })
                    .addOnConnectionFailedListener(connectionResult -> Log.d("Location error", "Location error " + connectionResult.getErrorCode())).build();
            googleApiClient.connect();
        }

        LocationRequest locationRequest = LocationRequest.create();
        locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        locationRequest.setInterval(30 * 1000);
        locationRequest.setFastestInterval(5 * 1000);
        LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder()
                .addLocationRequest(locationRequest);

        builder.setAlwaysShow(true);

        PendingResult<LocationSettingsResult> result =
                LocationServices.SettingsApi.checkLocationSettings(googleApiClient, builder.build());
        result.setResultCallback(result1 -> {
            final Status status = result1.getStatus();
            switch (status.getStatusCode()) {
                case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
                    try {
                        status.startResolutionForResult(activity, REQUEST_LOCATION);
                    } catch (IntentSender.SendIntentException e) {
                    }
                    break;
            }
        });
    }
}

Membuat validasi supaya terhubung ke jaringan internet dengan membuat fungsi check koneksi, kasusnya jika terhubung ke jaringan maka aplikasi diteruskan jika tidak maka keluar dari aplikasi dengan popup “Kesalahan jariangn periksa koneksi anda”.

package com.kodetr.googlemap.utils;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;

/**
 * Created by kodetr on 18/05/19.
 */

public class Connections {
    public static boolean checkConnection(Context context) {
        final ConnectivityManager connMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetworkInfo = connMgr.getActiveNetworkInfo();
        if (activeNetworkInfo != null) {
            if (activeNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                // connected to wifi
                return true;
            } else if (activeNetworkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
                // connected to the mobile provider's data plan
                return true;
            }
        }
        return false;
    }
}

Selanjutnya membuat penampungan konstan, jadi anda tidak menginginkan anda bisa menerapkan langsung pada aplikasi anda.

package com.kodetr.googlemap.utils;

/**
 * Created by kodetr on 19/05/19.
 */

public class Constants {
    public static final String DIRECTION_URL_API = "https://maps.googleapis.com/maps/api/directions/json?";
    private static final String PACKAGE_NAME = "com.kodetr.googlemap";
    public static final String RECEIVER = PACKAGE_NAME + ".RECEIVER";
    public static final String RESULT_DATA_KEY = PACKAGE_NAME + ".RESULT_DATA_KEY";
    public static final String LOCATION_DATA_EXTRA = PACKAGE_NAME + ".LOCATION_DATA_EXTRA";
    public static final int SUCCESS_RESULT = 0;
    public static final int FAILURE_RESULT = 1;
}

Membuat aksi dimana digunakan untuk mengambil alamat menggunakan intent dengan menerima berupa result dari lokasi yang ditentukan oleh kordinat blue point pada GPS, untuk menerapkannya dengan membuat Intent Service dan menjalankan dengan memanggil service pada manifest yang sudah diterapkan pada langkah sebelumnya.

package com.kodetr.googlemap.service;

import android.app.IntentService;
import android.content.Intent;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.text.TextUtils;
import android.util.Log;

import com.kodetr.googlemap.R;
import com.kodetr.googlemap.utils.Constants;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
 * Asynchronously handles an intent using a worker thread. Receives a ResultReceiver object and a
 * location through an intent. Tries to fetch the address for the location using a Geocoder, and
 * sends the result to the ResultReceiver.
 */
public class FetchAddressIntentService extends IntentService {
    private static final String TAG = "FetchAddressIS";

    /**
     * The receiver where results are forwarded from this service.
     */
    private ResultReceiver receiver;

    /**
     * This constructor is required, and calls the super IntentService(String)
     * constructor with the name for a worker thread.
     */
    public FetchAddressIntentService() {
        super(TAG);
    }

    /**
     * Tries to get the location address using a Geocoder. If successful, sends an address to a
     * result receiver. If unsuccessful, sends an error message instead.
     * Note: {@link ResultReceiver} in * MainActivity is defined to
     * process the content sent from this service.
     * <p>
     * This service calls this method from the default worker thread with the intent that started
     * the service. When this method returns, the service automatically stops.
     */
    @Override
    protected void onHandleIntent(Intent intent) {
        String errorMessage = "";

        receiver = intent.getParcelableExtra(Constants.RECEIVER);

        if (receiver == null) {
            Log.wtf(TAG, "No receiver received. There is nowhere to send the results.");
            return;
        }

        Location location = intent.getParcelableExtra(Constants.LOCATION_DATA_EXTRA);

        if (location == null) {
            errorMessage = getString(R.string.no_location_data_provided);
            Log.wtf(TAG, errorMessage);
            deliverResultToReceiver(Constants.FAILURE_RESULT, errorMessage);
            return;
        }

        Geocoder geocoder = new Geocoder(this, Locale.getDefault());
        List<Address> addresses = null;

        try {
            addresses = geocoder.getFromLocation(
                    location.getLatitude(),
                    location.getLongitude(),
                    1);
        } catch (IOException ioException) {
            errorMessage = getString(R.string.service_not_available);
            Log.e(TAG, errorMessage, ioException);
        } catch (IllegalArgumentException illegalArgumentException) {
            errorMessage = getString(R.string.invalid_lat_long_used);
            Log.e(TAG, errorMessage + ". " +
                    "Latitude = " + location.getLatitude() +
                    ", Longitude = " + location.getLongitude(), illegalArgumentException);
        }

        if (addresses == null || addresses.size() == 0) {
            if (errorMessage.isEmpty()) {
                Log.e(TAG, errorMessage);
            }
            deliverResultToReceiver(Constants.FAILURE_RESULT, errorMessage);
        } else {
            Address address = addresses.get(0);
            ArrayList<String> addressFragments = new ArrayList<>();

            for (int i = 0; i <= address.getMaxAddressLineIndex(); i++) {
                addressFragments.add(address.getAddressLine(i));
            }

            Log.i(TAG, getString(R.string.address_found));
            deliverResultToReceiver(Constants.SUCCESS_RESULT,
                    TextUtils.join(System.getProperty("line.separator"), addressFragments));
        }
    }

    /**
     * Sends a resultCode and message to the receiver.
     */
    private void deliverResultToReceiver(int resultCode, String message) {
        Bundle bundle = new Bundle();
        bundle.putString(Constants.RESULT_DATA_KEY, message);
        receiver.send(resultCode, bundle);
    }
}

Selanjutnya adalah membuat Parsing data dari url API dengan menggunakan lib json, Membuat model berupa interfece finder listener, class model Distance, class model Duration dan class model Route.

public class Distance {
    public String text;
    public int value;

    public Distance(String text, int value) {
        this.text = text;
        this.value = value;
    }
}

public class Duration {
    public String text;
    public int value;

    public Duration(String text, int value) {
        this.text = text;
        this.value = value;
    }
}

public class Route {
    public Distance distance;
    public Duration duration;
    public String endAddress;
    public LatLng endLocation;
    public String startAddress;
    public LatLng startLocation;

    public List<LatLng> points;
}

public interface DirectionFinderListener {
    void onDirectionFinderStart();

    void onDirectionFinderSuccess(List<Route> route);
}

Menerapkan Parsing data dari url API dengan menggunakan lib json.

package com.kodetr.googlemap.models.direction;

import android.annotation.SuppressLint;
import android.os.AsyncTask;

import com.google.android.gms.maps.model.LatLng;
import com.kodetr.googlemap.utils.Constants;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by kodetr on 09/05/19.
 */

public class DirectionFinder {

    private DirectionFinderListener listener;
    private String origin;
    private String destination;

    public DirectionFinder(DirectionFinderListener listener, String origin, String destination) {
        this.listener = listener;
        this.origin = origin;
        this.destination = destination;
    }

    public void execute(String google_api_key) throws UnsupportedEncodingException {
        listener.onDirectionFinderStart();
        new DownloadRawData().execute(createUrl(google_api_key));
    }

    private String createUrl(String google_api_key) throws UnsupportedEncodingException {
        String urlOrigin = URLEncoder.encode(origin, "utf-8");
        String urlDestination = URLEncoder.encode(destination, "utf-8");

        return Constants.DIRECTION_URL_API + "origin=" + urlOrigin + "&destination=" + urlDestination + "&key=" + google_api_key;
    }

    @SuppressLint("StaticFieldLeak")
    private class DownloadRawData extends AsyncTask<String, Void, String> {

        @Override
        protected String doInBackground(String... params) {
            String link = params[0];
            try {
                URL url = new URL(link);
                InputStream is = url.openConnection().getInputStream();
                StringBuilder buffer = new StringBuilder();
                BufferedReader reader = new BufferedReader(new InputStreamReader(is));

                String line;
                while ((line = reader.readLine()) != null) {
                    buffer.append(line).append("\n");
                }

                return buffer.toString();

            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(String res) {
            try {
                parseJSon(res);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }

    private void parseJSon(String data) throws JSONException {
        if (data == null)
            return;

        List<Route> routes = new ArrayList<Route>();
        JSONObject jsonData = new JSONObject(data);
        JSONArray jsonRoutes = jsonData.getJSONArray("routes");
        for (int i = 0; i < jsonRoutes.length(); i++) {
            JSONObject jsonRoute = jsonRoutes.getJSONObject(i);
            Route route = new Route();

            JSONObject overview_polylineJson = jsonRoute.getJSONObject("overview_polyline");
            JSONArray jsonLegs = jsonRoute.getJSONArray("legs");
            JSONObject jsonLeg = jsonLegs.getJSONObject(0);
            JSONObject jsonDistance = jsonLeg.getJSONObject("distance");
            JSONObject jsonDuration = jsonLeg.getJSONObject("duration");
            JSONObject jsonEndLocation = jsonLeg.getJSONObject("end_location");
            JSONObject jsonStartLocation = jsonLeg.getJSONObject("start_location");

            route.distance = new Distance(jsonDistance.getString("text"), jsonDistance.getInt("value"));
            route.duration = new Duration(jsonDuration.getString("text"), jsonDuration.getInt("value"));
            route.endAddress = jsonLeg.getString("end_address");
            route.startAddress = jsonLeg.getString("start_address");
            route.startLocation = new LatLng(jsonStartLocation.getDouble("lat"), jsonStartLocation.getDouble("lng"));
            route.endLocation = new LatLng(jsonEndLocation.getDouble("lat"), jsonEndLocation.getDouble("lng"));
            route.points = decodePolyLine(overview_polylineJson.getString("points"));
            routes.add(route);
        }
        listener.onDirectionFinderSuccess(routes);
    }
}

Menambahkan fungsi decode untuk setiap poin yang ditampung dalam list berupa langitude dan longitude untuk mencari rute paling dominan dengan menampilkan berupa garis pada map.

private List<LatLng> decodePolyLine(final String poly) {
    int len = poly.length();
    int index = 0;
    List<LatLng> decoded = new ArrayList<>();
    int lat = 0;
    int lng = 0;

    while (index < len) {
        int b;
        int shift = 0;
        int result = 0;
        do {
            b = poly.charAt(index++) - 63;
            result |= (b & 0x1f) << shift;
            shift += 5;
        } while (b >= 0x20);
        int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
        lat += dlat;
        shift = 0;
        result = 0;
        do {
            b = poly.charAt(index++) - 63;
            result |= (b & 0x1f) << shift;
            shift += 5;
        } while (b >= 0x20);
        int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
        lng += dlng;
        decoded.add(new LatLng(lat / 100000d, lng / 100000d));
    }
   return decoded;
}

Jika anda sudah mengikuti langkah dengan benar maka struktur akan seperti gambar berikut.

Bagian activity utama yaitu MapsActivity yang harus anda terapkan terlebih dahulu, berupa Permission dan cek Connection.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    checkLocationPermission();
}

if (!Connections.checkConnection(this)) {
   Toast.makeText(this, "Kesalahan jaringan periksa koneksi anda", Toast.LENGTH_SHORT).show();
   finish();
}

Buat request permission dari fungsi checkLocationPermission beserta dengan fungsi popup.

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull    String[] permissions, @NonNull int[] grantResults) {
    Log.i(TAG, "onRequestPermissionResult");
    if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
        if (grantResults.length <= 0)
            Log.i(TAG, "Membatalkan interaksi pengguna.");
        else
            if (grantResults[0] ==    PackageManager.PERMISSION_GRANTED) getDeviceLocation(false);
            else
                showSnackbar(R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE, android.R.string.ok,view -> requestPermission());
    }
}

private void showSnackbar(int textStringId, int length, int actionStringId, View.OnClickListener listener) {
    Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), textStringId, length);
    if (listener != null)
        snackbar.setAction(actionStringId, listener);
        snackbar.show();
}

private void requestPermission() {
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE);
}

private boolean checkPermission() {
    return ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
}

public static final int MY_PERMISSIONS_REQUEST_LOCATION = 99;
    
public boolean checkLocationPermission() {
    if (ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.ACCESS_FINE_LOCATION},MY_PERMISSIONS_REQUEST_LOCATION);
        } else {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION},MY_PERMISSIONS_REQUEST_LOCATION);
        }
        return false;
    } else {
        return true;
    }
}

Membuat fungsi untuk pencarian menggunakan Auto Complete dengan penerapan pada parameter url API sebagai destination.

private void setupAutoCompleteFragment() {
    PlaceAutocompleteFragment autocompleteFragment = (PlaceAutocompleteFragment) getFragmentManager().findFragmentById(R.id.place_autocomplete_fragment);
    autocompleteFragment.setOnPlaceSelectedListener(new PlaceSelectionListener() {
        @Override
        public void onPlaceSelected(Place place) {
            searchLocation = place.getLatLng();
        }

        @Override
        public void onError(Status status) {
            Log.e("Error", status.getStatusMessage());
        }
    });
}

Mencari rute terdekat dengan melakukan aksi pada fungsi DirectionFinder dengan memasukan parameter origin, destination dan api key kedalam fungsi tersebut.

new DirectionFinder(MapsActivity.this, origin, destination , api_key);

Menerapkan proses Direction Finder untuk menampilkan distance dan duration dengan origin dari blue poin ke destination yang ditandai dengan marker merah dengan menampilkan berupa garis dengan warna merah.

@Override
public void onDirectionFinderSuccess(List<Route> routes) {
    progressDialog.dismiss();
    polyLinePaths = new ArrayList<>();
    originMarkers = new ArrayList<>();
    destinationMarker = new ArrayList<>();

    for (Route route : routes) {
        map.moveCamera(CameraUpdateFactory.newLatLngZoom(route.startLocation, 15.5f));
        ((TextView) findViewById(R.id.tvDistance)).setText(route.distance.text);
        ((TextView) findViewById(R.id.tvTime)).setText(route.duration.text);

        destinationMarker.add(map.addMarker(new MarkerOptions()
            .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED))
            .title(route.endAddress)
            .position(route.endLocation)));

        PolylineOptions polylineOptions = new PolylineOptions()
            .geodesic(true)
            .color(getResources().getColor(R.color.colorPrimary))
            .width(10);

        for (int i = 0; i < route.points.size(); i++) {
            polylineOptions.add(route.points.get(i));
        }

        polyLinePaths.add(map.addPolyline(polylineOptions));
    }
}

Lebih jelasnya ada bisa melihat vidio diatas dan jika langkah-langkah diatas sudah diikuti dengan benar untuk source code lengkapnya seperti dibawah.

package com.kodetr.googlemap;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Geocoder;
import android.location.Location;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.ResultReceiver;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.common.api.Status;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;
import com.google.android.gms.tasks.Task;
import com.google.android.libraries.places.compat.Place;
import com.google.android.libraries.places.compat.ui.PlaceAutocompleteFragment;
import com.google.android.libraries.places.compat.ui.PlaceSelectionListener;
import com.kodetr.googlemap.models.direction.DirectionFinder;
import com.kodetr.googlemap.models.direction.DirectionFinderListener;
import com.kodetr.googlemap.models.direction.Route;
import com.kodetr.googlemap.service.FetchAddressIntentService;
import com.kodetr.googlemap.utils.Connections;
import com.kodetr.googlemap.utils.Constants;
import com.kodetr.googlemap.utils.PermissionGPS;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by kodetr on 09/05/19.
 */

public class MapsActivity extends AppCompatActivity implements GoogleMap.OnMarkerClickListener, GoogleMap.OnMapClickListener,
        OnMapReadyCallback, DirectionFinderListener {

    private static final String TAG = MapsActivity.class.getSimpleName();
    private LatLng LocationA = new LatLng(-8.594848, 116.105390);

    private static final float DEFAULT_ZOOM = 9.5f;

    private static final long UPDATE_INTERVAL = 500;
    private static final long FASTEST_UPDATE_INTERVAL = UPDATE_INTERVAL / 5;
    private static final int LOCATION_PERMISSION_REQUEST_CODE = 1;
    private static boolean gpsFirstOn = true;

    private GoogleMap map;
    private FusedLocationProviderClient fusedLocationProvider;
    private LocationRequest locationRequest;
    private LocationCallback locationCallback;
    private Location locationgps;
    private ResultReceiver resultReceiver;
    private Marker selectedMarker;
    private LatLng searchLocation;

    private List<Marker> originMarkers = new ArrayList<>();
    private List<Marker> destinationMarker = new ArrayList<>();
    private List<Polyline> polyLinePaths = new ArrayList<>();

    private ProgressDialog progressDialog;

    @SuppressLint("SetTextI18n")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            checkLocationPermission();
        }

        if (!Connections.checkConnection(this)) {
            Toast.makeText(this, "Kesalahan jaringan periksa koneksi anda", Toast.LENGTH_SHORT).show();
            finish();
        }

        init();

        fusedLocationProvider = LocationServices.getFusedLocationProviderClient(this);
        resultReceiver = new ResultReceiver(new Handler()) {
            @Override
            protected void onReceiveResult(int resultCode, Bundle resultData) {
                String addressOutput = resultData.getString(Constants.RESULT_DATA_KEY);
                Toast.makeText(getApplicationContext(), addressOutput, Toast.LENGTH_SHORT).show();
            }
        };

        locationgps = new Location("Point A");
    }

    @SuppressLint("SetTextI18n")
    private void init() {

        setupAutoCompleteFragment();

        FloatingActionButton fa = findViewById(R.id.fblocation);
        fa.setOnClickListener(view -> {
            try {
                String origin = locationgps.getLatitude() + "," + locationgps.getLongitude();
                new DirectionFinder(MapsActivity.this, origin, searchLocation.latitude + "," + searchLocation.longitude).execute(getString(R.string.google_maps_key));
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        final FloatingActionButton ft = findViewById(R.id.fbsatelit);
        ft.setOnClickListener(view -> {
            if (map != null) {
                int MapType = map.getMapType();
                if (MapType == 1) {
                    ft.setImageResource(R.drawable.ic_satellite_off);
                    map.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
                } else {
                    ft.setImageResource(R.drawable.ic_satellite_on);
                    map.setMapType(GoogleMap.MAP_TYPE_NORMAL);
                }
            }
        });

        FloatingActionButton fm = findViewById(R.id.fbgps);
        fm.setOnClickListener(view -> {
            getDeviceLocation(true);
            if (!Geocoder.isPresent()) {
                showSnackbar(R.string.no_geocoder_available, Snackbar.LENGTH_LONG, 0, null);
            } else {
                showAddress();
            }
        });

        locationCallback = new LocationCallback() {
            @Override
            public void onLocationResult(LocationResult locationResult) {
                if (locationResult == null)
                    return;

                for (Location locationUpdate : locationResult.getLocations()) {
                    locationgps = locationUpdate;
                    if (gpsFirstOn) {
                        gpsFirstOn = false;
                        getDeviceLocation(true);
                    }
                }
            }
        };

        locationRequest = new LocationRequest();
        locationRequest.setInterval(UPDATE_INTERVAL);
        locationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL);
        locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

        MapFragment mapFragment = (MapFragment) getFragmentManager().findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);
    }

    private void setupAutoCompleteFragment() {
        PlaceAutocompleteFragment autocompleteFragment = (PlaceAutocompleteFragment)
                getFragmentManager().findFragmentById(R.id.place_autocomplete_fragment);
        autocompleteFragment.setOnPlaceSelectedListener(new PlaceSelectionListener() {
            @Override
            public void onPlaceSelected(Place place) {
                searchLocation = place.getLatLng();
            }

            @Override
            public void onError(Status status) {
                Log.e("Error", status.getStatusMessage());
            }
        });
    }

    @Override
    public void onMapReady(GoogleMap gMap) {
        map = gMap;

        map.setOnMapClickListener(this);
        map.setOnMarkerClickListener(this);

        map.moveCamera(CameraUpdateFactory.newLatLngZoom(LocationA, DEFAULT_ZOOM));

        map.getUiSettings().setMapToolbarEnabled(false);
        map.getUiSettings().setMyLocationButtonEnabled(false);
//        map.getUiSettings().setCompassEnabled(false);

        // TODO : location
        map.getProjection().getVisibleRegion();

        if (!checkPermission())
            requestPermission();

        getDeviceLocation(false);
    }

    @Override
    public void onDirectionFinderStart() {
        progressDialog = ProgressDialog.show(this, "Tunggu sebentar", "Mencari lokasi terdekat..", true);
        if (originMarkers != null) {
            for (Marker marker : originMarkers) {
                marker.remove();
            }
        }
        if (destinationMarker != null) {
            for (Marker marker : destinationMarker) {
                marker.remove();
            }
        }
        if (polyLinePaths != null) {
            for (Polyline polylinePath : polyLinePaths) {
                polylinePath.remove();
            }
        }
    }

    @Override
    public void onDirectionFinderSuccess(List<Route> routes) {
        progressDialog.dismiss();
        polyLinePaths = new ArrayList<>();
        originMarkers = new ArrayList<>();
        destinationMarker = new ArrayList<>();

        for (Route route : routes) {
            map.moveCamera(CameraUpdateFactory.newLatLngZoom(route.startLocation, 15.5f));
            ((TextView) findViewById(R.id.tvDistance)).setText(route.distance.text);
            ((TextView) findViewById(R.id.tvTime)).setText(route.duration.text);

            destinationMarker.add(map.addMarker(new MarkerOptions()
                    .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED))
                    .title(route.endAddress)
                    .position(route.endLocation)));

            PolylineOptions polylineOptions = new PolylineOptions()
                    .geodesic(true)
                    .color(getResources().getColor(R.color.colorPrimary))
                    .width(10);

            for (int i = 0; i < route.points.size(); i++) {
                polylineOptions.add(route.points.get(i));
            }

            polyLinePaths.add(map.addPolyline(polylineOptions));
        }
    }

    private void getDeviceLocation(final boolean MyLocation) {
        if (!MyLocation)

            if (checkPermission()) {
                if (map != null)
                    map.setMyLocationEnabled(true);

                final Task<Location> locationResult = fusedLocationProvider.getLastLocation();
                locationResult.addOnCompleteListener(this, task -> {
                    if (task.isSuccessful() && task.getResult() != null) {
                        // lastKnownLocation = task.getResult();
                    } else {
                        Log.w(TAG, "getLastLocation:exception", task.getException());

                        showSnackbar(R.string.no_location_detected, Snackbar.LENGTH_LONG, 0, null);
                    }
                });
            } else // !checkPermission()
                Log.d(TAG, "Current location is null. Permission Denied.");
    }

    @Override
    public void onMapClick(final LatLng point) {
        selectedMarker = null;
    }

    @Override
    public boolean onMarkerClick(Marker marker) {
        if (marker.equals(selectedMarker)) {
            selectedMarker = null;
            return true;
        }

        Toast.makeText(this, marker.getTitle(), Toast.LENGTH_SHORT).show();
        selectedMarker = marker;
        return false;
    }

    private void showAddress() {
        Intent intent = new Intent(this, FetchAddressIntentService.class);
        intent.putExtra(Constants.RECEIVER, resultReceiver);
        intent.putExtra(Constants.LOCATION_DATA_EXTRA, locationgps);
        startService(intent);
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (Connections.checkConnection(this)) {
            new PermissionGPS(this);
        }
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        if (Connections.checkConnection(this)) {
            new PermissionGPS(this);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (Connections.checkConnection(this)) {
            if (checkPermission())
                fusedLocationProvider.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        Log.i(TAG, "onRequestPermissionResult");
        if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
            if (grantResults.length <= 0)
                Log.i(TAG, "User interaction was cancelled.");
            else // grantResults.length > 0
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
                    getDeviceLocation(false);
                else
                    showSnackbar(R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE, android.R.string.ok,
                            view -> requestPermission());
        }
    }

    private void showSnackbar(int textStringId, int length, int actionStringId, View.OnClickListener listener) {
        Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), textStringId, length);
        if (listener != null)
            snackbar.setAction(actionStringId, listener);
        snackbar.show();
    }

    private void requestPermission() {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE);

    }

    private boolean checkPermission() {
        return ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
    }

    public static final int MY_PERMISSIONS_REQUEST_LOCATION = 99;

    public boolean checkLocationPermission() {
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_FINE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {

            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.ACCESS_FINE_LOCATION)) {

                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                        MY_PERMISSIONS_REQUEST_LOCATION);


            } else {
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                        MY_PERMISSIONS_REQUEST_LOCATION);
            }
            return false;
        } else {
            return true;
        }
    }
}

Hasil Output

Hasil akhirnya anda bisa lihat gambar dibawah.

Demikian yang dapat saya sampaikan dari artikel ini semoga bermanfaat, jika anda mengalami kesulitan anda bisa melihat vidio yang sudah disediakan atau melalui komentar dibawah, selamat mencoba.

Share Comments
comments powered by Disqus