Поскольку я являюсь начинающим разработчиком в данной области, то на первом этапе пользуюсь готовыми идеями, чтобы понять суть решения типовых задач. В данный момент мне стало необходимо проложить схематический маршрут между двумя точками на картах Google. Самым интересным аналогом для решения поставленной задачи, обнаруженным в сети Интернет, оказался следующий: "Маршруты на картах Google в Android-приложении". Однако, при его дальнейшем рассмотрении и реализации появились некоторые подводные камни, о которых я и хочу рассказать.
Во первых, возникла проблема постановки двух маркеров для указания отправной и конечной точек. Может это сделано и криво, но я выбрал следующее решение — контроль за их наличием с передачей данных при постановке. Получилось примерно так:
После проверки установки маркеров и запоминания исходных и конечных координат, стал вопрос о реализации отображения маршрута. Предложенный в указанной выше статье «Маршруты на картах Google в Android-приложении» вариант реализации оказался не совсем работоспособным.
Во-первых, возникала ошибка при обращении с запросом к сайту maps.googleapis.com. Ошибка выражалась в невозможности выполнения запроса GET с передачей в главный поток. Проверка выполнялась на устройстве с Android 5,0. По описанию, автор примера делает синхронный GET запрос, который имеет вид:
Поскольку ошибку давал именно Retrofit, на базе которого и построены все запросы и парсинг JSON-ответа, то решено было разобраться в нем. Для получения информации воспользовался статьей — «Retrofit – библиотека для работы с REST API» по ссылке java-help.ru/retrofit-library.
Помня, что ранее в статьях писалось о нежелательности работы в синхронном режиме при обращении к сети, было решено переделать запрос в асинхронную версию. В результате получилось так:
Соответственно произошли изменения в дальнейшем коде. Вместо:
Получилось:
После чего, по запросу начал возвращаться JSON-ответ без ошибок.
В дальнейшем осталось только выделить из всего кода маршрут и отразить его на карте. Автор цитируемой статьи упоминает, что для получения точек маршрута необходимо воспользоваться классом PolyUtil. Цитирую: "...PolyUtil содержит метод decode(), принимающий строку Points и возвращающий набор объектов LatLng, узлов нашего маршрута. Этого нам достаточно для того, чтобы нарисовать наш маршрут на карте." Однако, образца применения метода в статье нет. В моей реализации это выглядит вот так:
Далее уже можно строить полилинию методом, описанным у автора.
Итоговый код приложения будет иметь следующий вид:
Результат работы ниже на скрине:
Ссылки на материалы, которыми воспользовался при решении задачи:
Retrofit – библиотека для работы с REST API
A type-safe HTTP client for Android and Java
Маршруты на картах 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) {
}
});
}
}
Результат работы ниже на скрине:
Ссылки на материалы, которыми воспользовался при решении задачи:
Retrofit – библиотека для работы с REST API
A type-safe HTTP client for Android and Java
Маршруты на картах Google в Android-приложении