Маршруты на картах Google в Android-приложении — некоторые уточнения

в 9:41, , рубрики: android, Google Maps, java, tutorial, Геоинформационные сервисы, маршруты, Разработка под android

Поскольку я являюсь начинающим разработчиком в данной области, то на первом этапе пользуюсь готовыми идеями, чтобы понять суть решения типовых задач. В данный момент мне стало необходимо проложить схематический маршрут между двумя точками на картах Google. Самым интересным аналогом для решения поставленной задачи, обнаруженным в сети Интернет, оказался следующий: "Маршруты на картах Google в Android-приложении". Однако, при его дальнейшем рассмотрении и реализации появились некоторые подводные камни, о которых я и хочу рассказать.

Во первых, возникла проблема постановки двух маркеров для указания отправной и конечной точек. Может это сделано и криво, но я выбрал следующее решение — контроль за их наличием с передачей данных при постановке. Получилось примерно так:

// Установка слушателя кликов по карте
map.setOnMapClickListener(new GoogleMap.OnMapClickListener() {

     // обработка кликов
     @Override
     public void onMapClick(LatLng latlng) {
           if ((fromMarker == false) && (toMarker == false)) {
           MarkerOptions markerOptions = new MarkerOptions();
           markerOptions.position(latlng);
           markerOptions.title("" + latlng.latitude + " " + latlng.longitude);
           BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.
           fromResource(R.drawable.a_marker);
           markerOptions.icon(bitmapDescriptor);
           //параметры "from - из" и "to - в" - задаются, как строки, и будут передаваться как строки
           from=""+latlng.latitude+","+latlng.longitude;
           map.addMarker(markerOptions);
           fromMarker = true;
           } 
           else {
                   if ((fromMarker == true) && (toMarker == false)) {
                   MarkerOptions markerOptions = new MarkerOptions();
                   markerOptions.position(latlng);
                   markerOptions.title("" + latlng.latitude + " " + latlng.longitude);
                   BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.
                   fromResource(R.drawable.b_marker);
                   markerOptions.icon(bitmapDescriptor);
                   to=""+latlng.latitude+","+latlng.longitude;
                   map.addMarker(markerOptions);
                   toMarker = true;
                   } else {
                             if ((fromMarker == true) && (toMarker == true)) {
                             map.clear();
                             fromMarker = false;
                             toMarker = false;
                             }
                             } 
                 }
         }
});

После проверки установки маркеров и запоминания исходных и конечных координат, стал вопрос о реализации отображения маршрута. Предложенный в указанной выше статье «Маршруты на картах Google в Android-приложении» вариант реализации оказался не совсем работоспособным.

Во-первых, возникала ошибка при обращении с запросом к сайту maps.googleapis.com. Ошибка выражалась в невозможности выполнения запроса GET с передачей в главный поток. Проверка выполнялась на устройстве с Android 5,0. По описанию, автор примера делает синхронный GET запрос, который имеет вид:

public interface RouteApi {
    @GET("/maps/api/directions/json")
    RouteResponse getRoute(
            @Query(value = "origin", encodeValue = false) String position,
            @Query(value = "destination", encodeValue = false) String destination,
            @Query("sensor") boolean sensor,
            @Query("language") String language);
}

Поскольку ошибку давал именно Retrofit, на базе которого и построены все запросы и парсинг JSON-ответа, то решено было разобраться в нем. Для получения информации воспользовался статьей — «Retrofit – библиотека для работы с REST API» по ссылке java-help.ru/retrofit-library.

Помня, что ранее в статьях писалось о нежелательности работы в синхронном режиме при обращении к сети, было решено переделать запрос в асинхронную версию. В результате получилось так:

   //Интерфейс для запроса маршрута
    public interface RouteApi {
        @GET("/maps/api/directions/json")
        void getRoute(
                @Query(value = "origin", encodeValue = false) String position,
                @Query(value = "destination", encodeValue = false) String destination,
                @Query("sensor") boolean sensor,
                @Query("language") String language,
                Callback<RouteResponse> cb
        );
    }

Соответственно произошли изменения в дальнейшем коде. Вместо:

RestAdapter restAdapter = new RestAdapter.Builder()
    .setEndpoint("https://maps.googleapis.com")
    .build();
RouteApi routeService = restAdapter.create(RouteApi.class);
RouteResponse routeResponse = routeService.getRoute(position, destination, true, "ru");

Получилось:

        //Переход от интерфейса к API
        RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint("https://maps.googleapis.com")
                .setLogLevel(RestAdapter.LogLevel.FULL)
                .build();
        RouteApi routeService = restAdapter.create(RouteApi.class);

        //Вызов запроса на маршрут (асинхрон)
        routeService.getRoute(from, to, true, "ru", new Callback<RouteResponse>() {

        public void success(RouteResponse arg0, retrofit.client.Response arg1) {
        }

        public void failure(RetrofitError arg0) {
        }
       
        });

После чего, по запросу начал возвращаться JSON-ответ без ошибок.

В дальнейшем осталось только выделить из всего кода маршрут и отразить его на карте. Автор цитируемой статьи упоминает, что для получения точек маршрута необходимо воспользоваться классом PolyUtil. Цитирую: "...PolyUtil содержит метод decode(), принимающий строку Points и возвращающий набор объектов LatLng, узлов нашего маршрута. Этого нам достаточно для того, чтобы нарисовать наш маршрут на карте." Однако, образца применения метода в статье нет. В моей реализации это выглядит вот так:

        //Вызов запроса на маршрут (асинхрон)
        routeService.getRoute(from, to, true, "ru", new Callback<RouteResponse>() {
            public void success(RouteResponse arg0, retrofit.client.Response arg1) {
                //Если прошло успешно, то декодируем маршрут в точки LatLng
                List<LatLng> mPoints = PolyUtil.decode(arg0.getPoints());

Далее уже можно строить полилинию методом, описанным у автора.

Итоговый код приложения будет иметь следующий вид:

package com.example.gpstest;

import android.location.LocationManager;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.PolylineOptions;
import com.google.maps.android.PolyUtil;
import java.util.List;
import retrofit.Callback;
import retrofit.RestAdapter;
import retrofit.RetrofitError;
import retrofit.http.GET;
import retrofit.http.Query;

public class MainActivity extends FragmentActivity {

    SupportMapFragment mapFragment;
    GoogleMap map;

    private LocationManager locationManager;
    private LocationListener locationListener;
    Marker label;
    boolean fromMarker, toMarker;
    String from, to,result;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        fromMarker = false;
        toMarker = false;
        //запрашиваем карту на вывод
        mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
        map = mapFragment.getMap();
        map.getUiSettings().setZoomControlsEnabled(true);
        map.setMyLocationEnabled(true);

        if (map == null) {
            return;
        }
        //Обработка клика на карту
        //Если нет маркеров, то ставим А, если есть А - ставим B, если есть оба - сбрасываем и вводим заново
        map.setOnMapClickListener(new GoogleMap.OnMapClickListener() {

            @Override
            public void onMapClick(LatLng latlng) {
                if ((fromMarker == false) && (toMarker == false)) {
                    MarkerOptions markerOptions = new MarkerOptions();
                    markerOptions.position(latlng);
                    markerOptions.title("" + latlng.latitude + " " + latlng.longitude);
                    BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromResource(R.drawable.a_marker);
                    markerOptions.icon(bitmapDescriptor);
                    from=""+latlng.latitude+","+latlng.longitude;
                    map.addMarker(markerOptions);
                    fromMarker = true;
                } else {
                    if ((fromMarker == true) && (toMarker == false)) {
                        MarkerOptions markerOptions = new MarkerOptions();
                        markerOptions.position(latlng);
                        markerOptions.title("" + latlng.latitude + " " + latlng.longitude);
                        BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromResource(R.drawable.b_marker);
                        markerOptions.icon(bitmapDescriptor);
                        to=""+latlng.latitude+","+latlng.longitude;
                        map.addMarker(markerOptions);
                        toMarker = true;
                    } else {
                        if ((fromMarker == true) && (toMarker == true)) {
                            map.clear();
                            fromMarker = false;
                            toMarker = false;
                        }
                    }
                }
            }
        });
    }


    //Класс точки маршрута движения
    public class RouteResponse {

        public List<Route> routes;

        public String getPoints() {
            return this.routes.get(0).overview_polyline.points;
        }

        class Route {
            OverviewPolyline overview_polyline;
        }

        class OverviewPolyline {
            String points;
        }
    }
    //Интерфейс для запросак маршрута
    public interface RouteApi {
        @GET("/maps/api/directions/json")
        void getRoute(
                @Query(value = "origin", encodeValue = false) String position,
                @Query(value = "destination", encodeValue = false) String destination,
                @Query("sensor") boolean sensor,
                @Query("language") String language,
                Callback<RouteResponse> cb
        );
    }

    // метод показа маршрута
    public void showRoute(View view ) {
        //Переход от интерфейса к API
        RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint("https://maps.googleapis.com")
                .setLogLevel(RestAdapter.LogLevel.FULL)
                .build();
        RouteApi routeService = restAdapter.create(RouteApi.class);

        //Вызов запроса на маршрут (асинхрон)
        routeService.getRoute(from, to, true, "ru", new Callback<RouteResponse>() {
            public void success(RouteResponse arg0, retrofit.client.Response arg1) {
                //Если прошло успешно, то декодируем маршрут в точки LatLng
                List<LatLng> mPoints = PolyUtil.decode(arg0.getPoints());
                //Строим полилинию
                PolylineOptions line = new PolylineOptions();
                line.width(4f).color(R.color.colorPrimary);
                LatLngBounds.Builder latLngBuilder = new LatLngBounds.Builder();
                for (int i = 0; i < mPoints.size(); i++) {

                    line.add((LatLng) mPoints.get(i));
                    latLngBuilder.include((LatLng) mPoints.get(i));
                }
                map.addPolyline(line);
                int size = getResources().getDisplayMetrics().widthPixels;
                LatLngBounds latLngBounds = latLngBuilder.build();
                CameraUpdate track = CameraUpdateFactory.newLatLngBounds(latLngBounds, size, size, 25);
                map.moveCamera(track);
                }
            //Если запрос прошел неудачно
            public void failure(RetrofitError arg0) {
            }
        });
    }
}

Результат работы ниже на скрине:

Маршруты на картах Google в Android-приложении — некоторые уточнения - 1

Ссылки на материалы, которыми воспользовался при решении задачи:

Retrofit – библиотека для работы с REST API
A type-safe HTTP client for Android and Java
Маршруты на картах Google в Android-приложении

Автор: Stalker_27

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js