Android-разработчик
0,0
рейтинг
26 ноября 2014 в 13:19

Разработка → Маршруты на картах Google в Android-приложении из песочницы

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

Я опущу процесс интеграции Google карт в приложение, интересующиеся могут найти всю информацию в подробном руководстве от Google. Весь процесс отображения маршрутов состоит из нескольких этапов:

  • Получение информации о маршруте;
  • Обработка полученного ответа;
  • Рисование маршрута на карте.

Рассмотрим эти этапы.

Получение маршрута


Для получения информации о маршруте мы должны выполнить запрос к службе маршрутов Google. Полное описание запросов и возвращаемых запросов доступны на сайте Google. Я лишь отмечу, что для получения маршрута мы должны выполнить запрос вида:

https://maps.googleapis.com/maps/api/directions/output?parameters

В качестве output мы можем выбрать XML, либо (в нашем случае) JSON. К числу обязательных параметров относятся origin и destination — их можно указать в виде текстового представления адреса, либо в виде значений широты и долготы, разделенных запятой. Третий обязательный параметр sensor указывает на то, исходит ли запрос от устройства с датчиком положения, либо нет — в нашем случае всегда будет равен true.

После того, как мы разобрались с форматом запроса, необходимо выбрать способ, которым мы будем выполнять наш запрос к службе маршрутов Google и получать ответ. Я использую библиотеку Retrofit, которая позволят выполнять формировать запросы к REST-сервисам буквально в пару строк.

Для использования Retrofit необходимо подключить библиотеку к вашему проекту. С использованием gradle это делается добавлением одной строки зависимости в ваш gradle-файл:

dependencies {
    compile 'com.squareup.retrofit:retrofit:1.7.1'
}


Далее нам необходимо описать API службы маршрутов Google. Для этого мы создаем новый Java-интерфейс, где мы создаем некоторое количество методов и при помощи аннотаций Retrofit сопоставляем их с различными методами на сервере. Так как мы будем получать только информацию, нам нужно описать только один метод для 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);
}


Аннотация GET в качестве аргумента принимает директорию на сервере, к которой должен выполняться запрос, а уже в самом методе аннотируем каждый его параметр аннотацией Query. Как аргумент для каждой аннотации имя параметра, который мы включаем в запрос. В данном случае для параметров origin и destination я устанавливаю свои значения для флага encodeValue, при помощи которого я сообщаю Retrofit, чтобы он не кодировал запятую, которая разделяет значения широты и долготы в моем запросе. Также я добавляю еще один параметр language для того, что бы ответ от сервера приходил на русском языке. Наш REST-метод должен возвращать некий объект, назовем его RouteResponse. Его мы опишем позже, а пока просто создадим еще один класс с именем RouteResponse.

После того, как мы описали API нашей службы, мы можем выполнить запрос. Для этого нам надо создать RestAdapter, создать сервис, представляющий удаленную службу и вызвать у него метод нашего API:

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


Вот и все, что нужно, чтобы получить маршрут от службы маршрутов Google. Добавив в конструктор RestAdapter строку
.setLogLevel(RestAdapter.LogLevel.FULL)
, вы можете выполнить запрос, и увидеть у себя в логе ответ от сервера. Но мы на этом не останавливаемся.

Обработка полученного ответа


В результате выполнения запроса мы получим объект RouteResponse. На самом деле, поскольку от сервера мы запросили JSON, то и ответ от сервера придет в JSON-формате. Retrofit, получив ответ от сервера, самостоятельно запускает парсинг JSON с использованием парсера от Google GSON, а уж тот парсит JSON в объект RouteResponse. При желании можно выбрать другой парсер — Jackson, либо JSON-парсер от Instagram, но я предпочитаю пользоваться GSON. GSON идет в комплекте с Retrofit, поэтому никаких дополнительных зависимостей для его использования нам в проект включать не надо.

Для того, чтобы доставать какие-то данные из JSON-ответа, нам нужно создать класс, описывающий эти данные. Мы уже создали класс RouteResponse, осталось наполнить его каким-то содержимым. Общая структура ответа от сервера службы маршрутов Google такова:

{
  "routes" : [
    {
      "bounds" : {
        "northeast" : {
          "lat" : 55.79283659999999,
          "lng" : 49.2216592
        },
        "southwest" : {
          "lat" : 55.73007759999999,
          "lng" : 49.1309371
        }
      },
      "copyrights" : "Картографические данные © 2014 Google",
      "legs" : [ ],
      "overview_polyline" : {
        "points" : "qffsIk{zjHEwKpKcAvGo@bFk@bGg@vFg@hEIxFQHcTL{a@FkCF_AFm@L_@Zs@Pa@f@cB|@gDb@aBbAuDrByIrAqIhB{LTaDFoA?uAK_B]gEe@oEKk@]]}@u@AGCIEEkEsCgAy@o@o@mBwBmCyCyAaBSQiAg@iBq@aAWmGaA_AKUFm@MiACU@i@Jj@sAVW^YbAs@T_@Nq@?_@Eu@g@iCuBcHq@yCIy@Aq@Fq@He@nCmGhC{FnGcNbA}BNa@TeAPqAZmDzBiWJ}@Da@cA_CiFmLc@aAkBkEqBiEcP__@oHmPaE}IgD}HaCiFcGyM}H{PcFeLyKqV_BuDyA}CaCqF{HgQsCuGyAiDsAoCk@cAe@u@iAmAq@k@m@]aA_@oA]m@IuCK_C@yMGwUO_M@{B?yUSuEAqG?aD@cM@qFDoFEs@?iPGiDEgA?yAEoFAoDCo@?mGEmGE_JEsGAq@BaCHsAJKqAHcBn@HEsDBADEJ]FIPEZ?LJTB"
      },
      "summary" : "пр.  Победы",
      "warnings" : [],
      "waypoint_order" : []
    }
  ],
  "status" : "OK"
}


Как видим, в ответе нам приходит массив маршрутов Routes, который содержит массив отрезков Legs, состоящий из шагов Steps, составляющих отрезок маршрута, и информации об отрезке. В ранних примерах маршруты строились на основе информации о каждом шаге отрезка, однако уже в объекте Route содержится объект Overview_polyline — это объект с массивом закодированных элементов points, которые представляют приблизительный (сглаженный) путь результирующего маршрута. В большинстве случаев этого сглаженного маршрута будет достаточно. Поэтому для рисования я буду использовать именно его.

На основе этой информации мы пишем наш класс модели для GSON:

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


Выполнив запрос и получив объект RouteResponse мы можем получить из него строку points. В своем исходном состоянии она нам мало что дает. Для того, чтобы добыть из нее какую-то информацию, нам нужно расшифровать ее. Здесь нам придет на помощь класс PolyUtil из библиотеки Google Maps Android API utility library. Чтобы им воспользоваться, нужно включить следующую зависимость в ваш проект:

dependencies {
    compile 'com.google.maps.android:android-maps-utils:0.3+'
}


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

Рисование маршрута на карте


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


        PolylineOptions line = new PolylineOptions();
        line.width(4f).color(R.color.indigo_900);
        LatLngBounds.Builder latLngBuilder = new LatLngBounds.Builder();
        for (int i = 0; i < mPoints.size(); i++) {
            if (i == 0) {
                MarkerOptions startMarkerOptions = new MarkerOptions()
                        .position(mPoints.get(i))
                        .icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_marker_a));
                mGoogleMap.addMarker(startMarkerOptions);
            } else if (i == mPoints.size() - 1) {
                MarkerOptions endMarkerOptions = new MarkerOptions()
                        .position(mPoints.get(i))
                        .icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_marker_b));
                mGoogleMap.addMarker(endMarkerOptions);
            }
            line.add(mPoints.get(i));
            latLngBuilder.include(mPoints.get(i));
        }
        mGoogleMap.addPolyline(line);
        int size = getResources().getDisplayMetrics().widthPixels;
        LatLngBounds latLngBounds = latLngBuilder.build();
        CameraUpdate track = CameraUpdateFactory.newLatLngBounds(latLngBounds, size, size, 25);
        mGoogleMap.moveCamera(track);
    


Для начала мы создаем экземпляр класса PolylineOptions и устанавливаем толщину и цвет линии. Затем получаем экземпляр LatLngBuilder для построения ограничивающего прямоугольника, который будет использоваться для того, чтобы масштабировать карту. Дальше мы проходим по списку объектов LatLng, полученному в результате расшифровки ответа от API маршрутов Google и добавляем каждую точку на линию в и LatLngBuilder. Для первого и последнего объекта в списке, которые представляют собой координаты начальной и конечной точки соответственно, мы создаем маркеры и добавляем их на карту. После завершения перебора элементов списка мы добавляем построенную линию на карту вызовом метода addPolyline().

Затем нам нужно масштабировать карту таким образом, чтобы отобразить весь маршрут. Перемещение по карте выполняется при помощи метода moveCamera() класса Camera, который принимает на вход настройки камеры в объекте UpdateCamera. Объект CameraUpdate мы создаем вызовом метода newLatLngBoudns у класса UpdateCameraFactory. Мы пеередаем ему созданный нами объект LatLngBounds, который содержит все точки нашего маршрута и передаем ему ширину нашего экрана и добавляем отступ от краев. После этого мы вызываем метод для передвижения камеры. И всё, маршрут нарисован.

image

В завершение еще раз приведу все ссылки на материалы, использованные мной:
Олег Осипенко @basnopisets
карма
9,5
рейтинг 0,0
Android-разработчик
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (5)

  • 0
    Retrofit позволяет использовать RxJava вместе и делать ответы от сервисов Observable. Думаю если написать в таком стиле, то код станет еще немного удобней =)
    • 0
      да, это отличное решение, особенно вкупе с лямбдами от 8-й версии. В наших интернетах информации, правда, мало, но зато есть видео докладов Jake Wharton.
  • 0
    Можно выложить итоговый код?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.