Routes on Google Maps in the Android App

Recently, I had a need to display the route between two points on a Google map in my application. On Habré there were already publications on this subject. For example, “Routes on Google maps in your Android application . However, these materials are already happy with a lot of time and they do not use new opportunities. I want to show another way to draw routes, maybe it will be useful to someone.

I will omit the process of integrating Google maps into the application, those interested can find all the information in a detailed guide from Google . The entire process of displaying routes consists of several stages:

  • Getting information about the route;
  • Processing the received response;
  • Drawing a route on a map.

Consider these steps.

Getting a route


To obtain route information, we must complete a request to the Google Route Service. A full description of queries and return requests is available on Google . I just note that in order to get the route, we must fulfill a query of the form:

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

As output, we can choose XML, or (in our case) JSON. The mandatory parameters include origin and destination - they can be specified as a text representation of the address, or as latitude and longitude values, separated by a comma. The third required parameter sensor indicates whether the request comes from a device with a position sensor or not - in our case it will always be true .

After we figured out the format of the request, you need to choose the way we will fulfill our request to the Google Route Service and get a response. I am using the Retrofit library, which will allow you to form requests to REST services in just a couple of lines.

To use Retrofit, you must connect the library to your project. Using gradle, this is done by adding one line of dependency to your gradle file:

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


Next, we need to describe the Google Route Services API. To do this, we create a new Java interface where we create a number of methods and use Retrofit annotations to map them to various methods on the server. Since we will receive only information, we need to describe only one method for a GET request:

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


The GET annotation takes as an argument the directory on the server to which the query should be executed, and already in the method itself annotate each parameter with the Query annotation . As an argument for each annotation, the name of the parameter that we include in the request. In this case, for the origin and destination parameters, I set my values ​​for the encodeValue flag , with which I tell Retrofit so that it does not encode a comma that separates the latitude and longitude values ​​in my request. I also add another language parameterin order for the response from the server to come in Russian. Our REST method should return an object, let's call it RouteResponse. We will describe it later, but for now just create another class called RouteResponse.

After we have described the API of our service, we can fulfill the request. To do this, we need to create a RestAdapter, create a service representing the remote service and call our API method on it:

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


That's all it takes to get a route from Google's route service. By adding a line to the RestAdapter constructor
.setLogLevel(RestAdapter.LogLevel.FULL)
, you can execute the request and see the response from the server in your log. But we do not stop there.

Processing the received response


As a result of the query, we get a RouteResponse object. In fact, since we requested JSON from the server, the response from the server will come in JSON format. Retrofit, having received a response from the server, independently launches JSON parsing using the Google GSON parser , and that JSON parses into the RouteResponse object. If desired, you can choose another parser - Jackson , or a JSON parser from Instagram , but I prefer to use GSON. GSON comes with Retrofit, so we don’t need to include any additional dependencies for its use in the project.

In order to get some data from the JSON response, we need to create a class that describes this data. We have already created the RouteResponse class, it remains to fill it with some content. The general structure of the response from the Google Route Service server is as follows:

{
  "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"
}


As you can see, in the answer we get an array of Routes routes , which contains an array of Legs segments , consisting of Steps steps that make up the route segment, and information about the segment. In the earlier examples, the routes were built on the basis of information about each step of the segment, however, the Route object already contains the Overview_polyline object - this is an object with an array of encoded points that represent the approximate (smooth) path of the resulting route. In most cases, this smooth route will suffice. Therefore, I will use it for drawing.

Based on this information, we write our model class for GSON:

public class RouteResponse {
    public List routes;
    public String getPoints() {
        return this.routes.get(0).overview_polyline.points;
    }
    class Route {
        OverviewPolyline overview_polyline;
    }
    class OverviewPolyline {
        String points;
    }
}


Having fulfilled the request and getting the RouteResponse object, we can get the points line from it . In its initial state, it gives us little. In order to get some information from it, we need to decrypt it. Here, the PolyUtil class from the Google Maps Android API utility library will come to our aid . To use it, you need to include the following dependency in your project:

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


PolyUtil contains a decode () method that accepts a Points string and returns a set of LatLng objects , nodes of our route. This is enough for us to draw our route on the map.

Drawing a route on a map


In the old examples, Overlay was used to draw the route, but we will do with the Polyline class - in this case we do not need to create an additional class inherited from Overlay and the amount of code that we need to write is drastically reduced. Polyline is a list of points on the map and the line connecting them. Polyline can then be added to the map:


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


First, we create an instance of the PolylineOptions class and set the thickness and color of the line. Then we get an instance of LatLngBuilder to build a bounding box that will be used to scale the map. Next, we go through the list of LatLng objects obtained as a result of decoding the response from the Google routes API and add each point to the line in and LatLngBuilder . For the first and last objects in the list, which are the coordinates of the start and end points, respectively, we create markers and add them to the map. After completing the enumeration of the list items, we add the constructed line to the map by calling the addPolyline () method.

Then we need to scale the map in such a way as to display the entire route. Moving around the map is performed using the moveCamera () method of the Camera class, which receives the camera settings in the UpdateCamera object as an input. We create the CameraUpdate object by calling the newLatLngBoudns method of the UpdateCameraFactory class. We pass it the LatLngBounds object that we created, which contains all the points of our route and pass it the width of our screen and add an indent from the edges. After that, we call the method to move the camera. And that’s it, the route is drawn.

image

In conclusion, I will again give all the links to the materials used by me:

Also popular now: