Про использование Google MAPs API написано много статей, но основная часть из них устарела на столько, что глазам становится просто больно. Здесь хочу рассказать про замечательную библиотеку для работы со всеми MAPs API в частности Directions API, а так же о том как все это встроить в приложение.

Принцип работы Google MAPs API


Вся документация для работы с картами приведена на (логично) официальном сайте google maps api. Сегодня я рассматриваю только Directions API (документация). Для того что бы получить какую-либо информацию от большого числа, вам необходимо сделать запрос. Ответ прийдет в формате JSON.

Общий вид запроса:

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

Пример: https://maps.googleapis.com/maps/api/directions/json?origin=55.754724,%2037.621380&destination=55.728466,%2037.604155&key="Your MAPs API key"

Ответ
{
"geocoded_waypoints": [
{
"geocoder_status": "OK",
"place_id": "EjvQmtGA0LDRgdC90LDRjyDQv9C7LiwgMiwg0JzQvtGB0LrQstCwLCDQoNC-0YHRgdC40Y8sIDEwOTAxMg",
"types": [
"street_address"
]
},
{
"geocoder_status": "OK",
"place_id": "ChIJ5aZfug5LtUYRGSJyHfEblrk",
"types": [
"street_address"
]
}
],
"routes": [
{
"bounds": {
"northeast": {
"lat": 55.7536656,
"lng": 37.6246528
},
"southwest": {
"lat": 55.7279873,
"lng": 37.6049469
}
},
"copyrights": "Картографические данные  2017 Google",
"legs": [
{
"distance": {
"text": "4,4 км",
"value": 4434
},
"duration": {
"text": "13 мин.",
"value": 777
},
"end_address": "Ленинский пр., 6с11, Москва, Россия, 119049",
"end_location": {
"lat": 55.7279873,
"lng": 37.6049469
},
"start_address": "Красная пл., 2, Москва, Россия, 109012",
"start_location": {
"lat": 55.7536656,
"lng": 37.6225908
},
"steps": [
{
"distance": {
"text": "0,3 км",
"value": 261
},
"duration": {
"text": "1 мин.",
"value": 56
},
"end_location": {
"lat": 55.7515735,
"lng": 37.6240491
},
"html_instructions": "Направляйтесь на <b>юг</b> по <b>пл. Красная</b> в сторону <b>пл. Васильевский Cпуск</b>",
"polyline": {
"points": "mkhsIedsdFHDH@FCHIPUhAcBV_@T_@R[HKFGJGLIPIVIXGVCl@AZ?L?"
},
"start_location": {
"lat": 55.7536656,
"lng": 37.6225908
},
"travel_mode": "DRIVING"
},
{
"distance": {
"text": "0,6 км",
"value": 585
},
"duration": {
"text": "1 мин.",
"value": 78
},
"end_location": {
"lat": 55.7463555,
"lng": 37.6246528
},
"html_instructions": "Продолжайте движение по <b>мост Большой Москворецкий</b><div style=\"font-size:0.9em\">Участки этой дороги могут быть перекрыты в определенные дни или часы</div>",
"polyline": {
"points": "i~gsIimsdFj@@L?DC@ABEBCDM`A?~BSvBMhAGdAEfAElAED@vBIXAv@Ev@El@Ej@C"
},
"start_location": {
"lat": 55.7515735,
"lng": 37.6240491
},
"travel_mode": "DRIVING"
},
{
"distance": {
"text": "0,3 км",
"value": 259
},
"duration": {
"text": "1 мин.",
"value": 74
},
"end_location": {
"lat": 55.7458782,
"lng": 37.6206072
},
"html_instructions": "Поверните <b>направо</b> на <b>ул. Болотная</b>",
"maneuver": "turn-right",
"polyline": {
"points": "w}fsIaqsdFJ`CRbDPnCJvB\\~D@T@P?F?H"
},
"start_location": {
"lat": 55.7463555,
"lng": 37.6246528
},
"travel_mode": "DRIVING"
},
{
"distance": {
"text": "69 м",
"value": 69
},
"duration": {
"text": "1 мин.",
"value": 10
},
"end_location": {
"lat": 55.7464196,
"lng": 37.6201379
},
"html_instructions": "<b>ул. Болотная</b> поворачивает <b>направо</b> и переходит в <b>пер. Фалеевский</b>",
"polyline": {
"points": "wzfsIywrdFAF?BABIN{@f@SJMH"
},
"start_location": {
"lat": 55.7458782,
"lng": 37.6206072
},
"travel_mode": "DRIVING"
},
{
"distance": {
"text": "0,4 км",
"value": 420
},
"duration": {
"text": "1 мин.",
"value": 85
},
"end_location": {
"lat": 55.7446178,
"lng": 37.6146911
},
"html_instructions": "Плавный поворот <b>налево</b> на <b>пл. Болотная</b>",
"maneuver": "turn-slight-left",
"polyline": {
"points": "c~fsI{trdFOn@I\\CNANAN?^B^Hf@^fAVn@n@lBlAjDhAdDX~@@?Nd@nA|D"
},
"start_location": {
"lat": 55.7464196,
"lng": 37.6201379
},
"travel_mode": "DRIVING"
},
{
"distance": {
"text": "0,2 км",
"value": 220
},
"duration": {
"text": "1 мин.",
"value": 65
},
"end_location": {
"lat": 55.7464027,
"lng": 37.6131881
},
"html_instructions": "Поверните <b>направо</b> на <b>ул. Серафимовича</b>",
"maneuver": "turn-right",
"polyline": {
"points": "{rfsIyrqdFk@f@GFOJOLaAr@oCtB}@v@"
},
"start_location": {
"lat": 55.7446178,
"lng": 37.6146911
},
"travel_mode": "DRIVING"
},
{
"distance": {
"text": "0,1 км",
"value": 116
},
"duration": {
"text": "1 мин.",
"value": 32
},
"end_location": {
"lat": 55.7456823,
"lng": 37.6118538
},
"html_instructions": "Поверните <b>налево</b> на <b>ул. Софийская набережная</b>",
"maneuver": "turn-left",
"polyline": {
"points": "_~fsImiqdFRb@J\\Rn@Nd@jArB"
},
"start_location": {
"lat": 55.7464027,
"lng": 37.6131881
},
"travel_mode": "DRIVING"
},
{
"distance": {
"text": "0,4 км",
"value": 362
},
"duration": {
"text": "1 мин.",
"value": 80
},
"end_location": {
"lat": 55.7431661,
"lng": 37.6150421
},
"html_instructions": "Поверните <b>налево</b> на <b>ул. Серафимовича</b>",
"maneuver": "turn-left",
"polyline": {
"points": "oyfsIaaqdFb@q@dAwAb@q@h@{@PSTWr@cAp@s@nAcA^]z@o@RSO{@"
},
"start_location": {
"lat": 55.7456823,
"lng": 37.6118538
},
"travel_mode": "DRIVING"
},
{
"distance": {
"text": "0,2 км",
"value": 229
},
"duration": {
"text": "1 мин.",
"value": 40
},
"end_location": {
"lat": 55.7412352,
"lng": 37.6162912
},
"html_instructions": "<b>ул. Серафимовича</b> поворачивает <b>направо</b> и переходит в <b>Малый Каменный Мост</b>/<b>ул. Большая Полянка</b><div style=\"font-size:0.9em\">Продолжайте движение по ул. Большая Полянка</div>",
"polyline": {
"points": "yifsI_uqdF^UJEJERKBATQRO`@YlC_Bf@UVKVK"
},
"start_location": {
"lat": 55.7431661,
"lng": 37.6150421
},
"travel_mode": "DRIVING"
},
{
"distance": {
"text": "79 м",
"value": 79
},
"duration": {
"text": "1 мин.",
"value": 13
},
"end_location": {
"lat": 55.7405459,
"lng": 37.616611
},
"html_instructions": "Продолжайте движение по <b>пр-д Якиманский</b>",
"polyline": {
"points": "w}esIy|qdFrBy@TE"
},
"start_location": {
"lat": 55.7412352,
"lng": 37.6162912
},
"travel_mode": "DRIVING"
},
{
"distance": {
"text": "0,2 км",
"value": 194
},
"duration": {
"text": "1 мин.",
"value": 22
},
"end_location": {
"lat": 55.7388943,
"lng": 37.6156238
},
"html_instructions": "Плавно поверните <b>направо</b> и продолжайте движение по <b>пр-д Якиманский</b>",
"maneuver": "turn-slight-right",
"polyline": {
"points": "myesIy~qdFh@N^PtAv@NDzCdB"
},
"start_location": {
"lat": 55.7405459,
"lng": 37.616611
},
"travel_mode": "DRIVING"
},
{
"distance": {
"text": "1,0 км",
"value": 1000
},
"duration": {
"text": "2 мин.",
"value": 106
},
"end_location": {
"lat": 55.73020349999999,
"lng": 37.6120644
},
"html_instructions": "Продолжайте движение по <b>ул. Большая Якиманка</b>",
"polyline": {
"points": "aoesIsxqdFxDzBlAp@vAx@fBfAzAz@p@\\xAv@VJ`@Rp@THD~Af@bAZnBh@l@Nz@ThARr@J`AFf@Bd@B|@@jB@n@?|@@"
},
"start_location": {
"lat": 55.7388943,
"lng": 37.6156238
},
"travel_mode": "DRIVING"
},
{
"distance": {
"text": "60 м",
"value": 60
},
"duration": {
"text": "1 мин.",
"value": 9
},
"end_location": {
"lat": 55.7296657,
"lng": 37.6120519
},
"html_instructions": "Продолжайте движение по <b>пл. Калужская</b>",
"polyline": {
"points": "wxcsIkbqdFP?vA@"
},
"start_location": {
"lat": 55.73020349999999,
"lng": 37.6120644
},
"travel_mode": "DRIVING"
},
{
"distance": {
"text": "0,2 км",
"value": 186
},
"duration": {
"text": "1 мин.",
"value": 36
},
"end_location": {
"lat": 55.7304209,
"lng": 37.6093953
},
"html_instructions": "Поверните <b>направо</b>",
"maneuver": "turn-right",
"polyline": {
"points": "mucsIibqdFi@|CUtACL]bBs@jD"
},
"start_location": {
"lat": 55.7296657,
"lng": 37.6120519
},
"travel_mode": "DRIVING"
},
{
"distance": {
"text": "0,4 км",
"value": 394
},
"duration": {
"text": "1 мин.",
"value": 71
},
"end_location": {
"lat": 55.7279873,
"lng": 37.6049469
},
"html_instructions": "Поверните <b>налево</b><div style=\"font-size:0.9em\">Дорога с ограниченным доступом</div><div style=\"font-size:0.9em\">Пункт назначения будет справа</div>",
"maneuver": "turn-left",
"polyline": {
"points": "czcsIwqpdFf@x@dAzAp@|@j@t@j@p@PXNXNb@Nb@\\xA^fAPj@Nb@L`@Nn@Pz@FTHVHR"
},
"start_location": {
"lat": 55.7304209,
"lng": 37.6093953
},
"travel_mode": "DRIVING"
}
],
"traffic_speed_entry": [],
"via_waypoint": []
}
],
"overview_polyline": {
"points": "mkhsIedsdFRFPMhCyD\\g@RO^Sp@QdAEh@?x@@FEFIDM`A?vFa@nCMtCK|BGvDSj@CJ`Cd@rHl@~IA\\KRoAr@MHOn@Ml@C^B~@Hf@^fAfA|CrDpK~AbFs@n@qFbE}@v@Rb@^lANd@jArBhBiClAmBf@k@r@cAp@s@nBaBnAcAO{@^UVKVMh@a@nDyB~@a@jCeATEh@NtBhAjDjBfGlD~D`ClCxApBbArAh@hBl@rDdAhBd@|B^hBJbBDjFBvA@i@|CYbBqAnGlBtC|ArB|@jA^|@l@|BnAxDr@xCHR"
},
"summary": "ул. Большая Якиманка",
"warnings": [],
"waypoint_order": []
}
],
"status": "OK"
}


В качестве ответа нам (ожидаемо) пришел JSON с большим набором разных точек с координатами и названиями этих мест.

А как вообще работать с этой страшной штукой?


Если вы только начинаете работать с Android, то советую вам почитать про такую замечательную библиотеку Retrofit, которая превращает работу с запросами в код из 2 строк. Рассматривать сейчас я её не буду.

Но я сегодня хочу рассмотреть пример использования библиотеки Java Client for Google Maps Services. Библиотека как по мне замечательная, освобождает от необходимости писать (пусть даже очень короткие) запросы вручную и отлично подходит в случаях когда нужно писать очень быстро, как например на хакатоне. Я хочу показать живой пример использования данной библиотеки на примере работы с Directions API.

Подключение библиотеки


Для начала нам потребуется получить ключ для нашего приложения. Топаем на оф. сайт, находим сверху кнопку «получить ключ», создаем новый проект, нажимаем далее и готово!

Firebase
Для правильной работы приложения нам необходимо получить файл google-service.json. Идем на firebase выбираем наш проект и добавляем его. Далее нам нужно выбрать Android проект, ввести название пакета, регистрируем приложение. Скачиваем файл и перетаскиваем в папку app. К слову её не будет видно в дереве проекта, для этого надо в Android Studio поменять отображение с Android на Project или залезть в наш проект через файловый менеджер. Далее следуем инструкциям где какой код писать.

Включаем в консоли
Так же нам необходимо включить Directions API (или любую другую необходимую вам API) в консоли, для этого идем сюда, выбираем наше приложение и включаем Directions API.

Gradle
В Gradle файлы так же необходимо добавить еще пару строк. В итоге новые строки выглядят вот так:

Project.gradle

dependencies {
        ...

        classpath 'com.google.gms:google-services:3.1.0'
    }
...
repositories {
        jcenter()
        mavenCentral()
    }

app.gradle
dependencies {
    ...
    compile 'com.google.maps:google-maps-services:0.2.4'
    compile 'com.google.android.gms:play-services-maps:11.0.4'

    compile 'org.slf4j:slf4j-nop:1.7.25'
}

apply plugin: 'com.google.gms.google-services'

Обязательно проверяйте, актуальная ли это сейчас версия!

Встраиваем карту в приложение


Google map в андроид реализовывается как фрагмент (или как MapView, но об этом в другой раз, нам сейчас особой разницы нет). Просто встраиваем его в наш layout. В нашем классе, который работает с картой, необходимо найти эту карту и заимплементить интерфейс.

Код для фрагмента выглядит вот так. Я буду работать с MainActivity, соответственно если вы используете другой класс вам необходимо поменять контекст.

    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        tools:context=".MainActivity"/>

Отлично, фрагмент встроили, Android Studio на нас не ругается, едем дальше. Переходим в MainActivity.class и имплементим интерфейс OnMapReadyCallback.

implements OnMapReadyCallback
...
@Override
    public void onMapReady(GoogleMap googleMap) {
...
}

В onCreate пишем

SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);

Так же идем в Manifests и прописываем вот такие штуки внутри тэга application

<meta-data
        android:name="com.google.android.geo.API_KEY"
        android:value="@string/google_maps_key" />
<meta-data
        android:name="com.google.android.gms.version"
        android:value="@integer/google_play_services_version" />

Где вместо @string/google_maps_key должен подставиться ваш ключ для карт, который мы получили ранее. Соответственно вам нужно создать нужный ресурс в файле string.

<string name="google_maps_key">...</string>

Пишем всякие интересности


Отлично, карта у нас есть, давайте наконец напишем хоть что-нибудь интересное. Пусть нашей целью будет нарисовать маршрут по Москве через несколько точек:

  • Гум (55.754724, 37.621380)
  • Большой театр (55.760133, 37.618697)
  • Патриаршие пруды (55.764753, 37.591313)
  • Парк культуры (55.728466, 37.604155)

Кладу все наши места в List и делаю это как глобальную переменную.

private List<LatLng> places = new ArrayList<>();

В onCreate

places.add(new LatLng(55.754724, 37.621380));
places.add(new LatLng(55.760133, 37.618697));
places.add(new LatLng(55.764753, 37.591313));
places.add(new LatLng(55.728466, 37.604155));

Для начала создадим по маркеру на каждое место. Маркер это просто объект, которому передаются координаты, а затем они накладываются на карту. Код:

MarkerOptions[] markers = new MarkerOptions[places.size()];
for (int i = 0; i < places.size(); i++) {
    markers[i] = new MarkerOptions()
        .position(places.get(i));
    googleMap.addMarker(markers[i]);
}

Далее мы пишем вот такой код все в том же методе onMapReady

//Получаем контекст для запросов, mapsApiKey хранит в себе String с ключом для карт
GeoApiContext geoApiContext = new GeoApiContext.Builder()
    .apiKey(mapsApiKey)
    .build();

//Здесь будет наш итоговый путь состоящий из набора точек
DirectionsResult result = null;
try {
    result = DirectionsApi.newRequest(geoApiContext)
        .origin(places.get(0))//Место старта
        .destination(places.get(places.size() - 1))//Пункт назначения
        .waypoints(places.get(1), places.get(2)).await();//Промежуточные точки. Да, не очень красиво, можно через цикл, но зато наглядно
} catch (ApiException e) {
    e.printStackTrace();
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

//Преобразование итогового пути в набор точек
List<com.google.maps.model.LatLng> path = result.routes[0].overviewPolyline.decodePath();

//Линия которую будем рисовать
PolylineOptions line = new PolylineOptions();

LatLngBounds.Builder latLngBuilder = new LatLngBounds.Builder();

//Проходимся по всем точкам, добавляем их в Polyline и в LanLngBounds.Builder
for (int i = 0; i < path.size(); i++) {
    line.add(new com.google.android.gms.maps.model.LatLng(path.get(i).lat, path.get(i).lng));
    latLngBuilder.include(new com.google.android.gms.maps.model.LatLng(path.get(i).lat, path.get(i).lng));
}

//Делаем линию более менее симпатичное
line.width(16f).color(R.color.colorPrimary);

//Добавляем линию на карту
googleMap.addPolyline(line);

//Выставляем камеру на нужную нам позицию 
LatLngBounds latLngBounds = latLngBuilder.build();
CameraUpdate track = CameraUpdateFactory.newLatLngBounds(latLngBounds, width, width, 25);//width это размер нашего экрана 
googleMap.moveCamera(track);

При запуске приложения мы получили вот такую картину:

Android phone screenshot with transport mode path

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

try {
    result = DirectionsApi.newRequest(geoApiContext)
        .mode(TravelMode.WALKING)//Говорим: "Нет, спасибо. Я пойду пешком" 
        .origin(places.get(0))
        .destination(places.get(places.size() - 1))
        .waypoints(places.get(1), places.get(2)).await();
...

Теперь наш маршрут выглядит вот так

Android phone screenshot with walking mode path

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

Весь код есть на github.

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


  1. ashumkin
    02.11.2017 17:06

    Так же идем в Manifests и прописываем вот такие штуки внутри тэга application

    <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="@string/google_maps_key" />

    хммм… итого, любой, получивший .apk-шку, может получить этот ключ…
    и это — из стандартной инструкции Google Maps… но там же есть
    Дополнительная информация об ограничениях для ключей API

    Интерфейсы Google Maps API доступны для приложений Android и iOS, веб-браузеров, а также применяются веб-службами HTTP. Интерфейсы API на любой платформе могут использовать общий (без ограничений) ключ API. При необходимости вы можете добавить ограничение. Для приложений Android ключ ограничивается контрольной суммой SHA-1 вашего приложения и названием пакета. Ключи с ограничениями будут работать только на платформах, которые поддерживают такой тип ограничений
    .


    1. ulman95
      02.11.2017 23:30

      подобные ограничения добавляются через консоль разработчика, а не в сорсах


      1. ashumkin
        03.11.2017 09:21

        спасибо, я знаю (по приведённым ссылкам же и написано)