Search code examples
javaandroidgoogle-mapsgoogle-polyline

Polyline between two locations not snapped to road


I'm trying to draw a smooth polyline between two locations that are some distance apart (say over 100 miles) on a Google Map in Android. I've been following this guide and also this guide in terms of utilising the Directions and Snap to Roads API, yet due to the limit of 100 coordinates from the Snap to Roads API, it appears it is virtually impossible to draw a smooth polyline from one location to another that follows the smooth contours of the road.

I've managed to extract all coordinates of the directions to plot a polyline using the returned overview_points and have decoded this using the decode method from the PolyUtil API, yet the polyline drawn on the map is not snapped to the road for the vast majority of the time. Instead, I've tried using the Snap to Roads API and set a limit of 100 coordinates (maximum GPS points allowed) that all seem to be very accurately snapped to the road from destination A to B (only covering some distance between the two locations if far apart).

Basically, is there something I am missing entirely, or is it a case of coming up with some resolution to spread the 100 coordinates allocation of the Snap to Roads API using the GPS points retrieved from the overview_points from the Directions API, i.e. plot a coordinate every XXX meters.

Here is the bulk of the code I've implemented through a Volley request minus the Snap to Roads request, which the later is fairly straightforward to implement:

StringRequest stringRequest = new StringRequest(Request.Method.GET, 
"https://maps.googleapis.com/maps/api/directions/json?
origin=START_LOCATION_HERE&destination=END_LOCATION_HERE&key=API_KEY_HERE",
            new Response.Listener<String>() {
                @Override
                public void onResponse(String response) {

                    JSONObject directions;
                    try {
                        directions = new JSONObject(response);

                        JSONArray routes = directions.getJSONArray("routes");

                        mCoordinates = new ArrayList<>();
                        for (int i = 0; i < routes.length(); i++) {
                            JSONObject routesObject = routes.getJSONObject(i);

                            JSONObject overviewPolyline = routesObject.getJSONObject("overview_polyline");
                            String points = overviewPolyline.getString("points");

                            List<LatLng> coordinates = new ArrayList<>();
                            coordinates.addAll(PolyUtil.decode(points));

                            PolylineOptions routeCoordinates = new PolylineOptions();
                            for (LatLng latLng : coordinates) {
                                routeCoordinates.add(new LatLng(latLng.latitude, latLng.longitude));
                            }
                            routeCoordinates.width(5);
                            routeCoordinates.color(Color.BLUE);

                            Polyline route = mGoogleMap.addPolyline(routeCoordinates);
                            for (LatLng latLng : coordinates) {
                                mGoogleMap.addMarker(new MarkerOptions().position(new LatLng(latLng.latitude, latLng.longitude)));
                            }

                        }

                    } catch (JSONException e) {
                        e.printStackTrace();
                    }

                }
            }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            // TODO handle error
        }

    });

Solution

  • Okay, if anyone experiences this issue or similar, I've managed to fix this and it seems the way to correctly plot a polyline on a map that follows the smooth contours of the road is to use each individual polyline > point encoded string within each steps object. It also seems I misread the documentation that clearly states the polyline encoded string in each steps object provides an approximate smoothed path of each step, which I'd imagine others may also have missed.

    Basically, the overview_polyline encoded string will only provide an overview of the route and thus not provide a smooth path between locations. It may be perfect for routes that consist of largely straight lines (not great in the UK obviously), so each individual polyline encoded string is what is needed. I've just tested this and it works perfectly, without the need at all for the Snap to Roads API.

    My altered code to fix the problem in my use case is as follows:

        StringRequest stringRequest = new StringRequest(Request.Method.GET, "https://maps.googleapis.com/maps/api/directions/json?origin=START_LOCATION&destination=END_LOCATION&key=API_KEY",
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
    
                        JSONObject directions;
                        try {
                            directions = new JSONObject(response);
    
                            JSONArray routes = directions.getJSONArray("routes");
    
                            mCoordinates = new ArrayList<>();
                            for (int a = 0; a < routes.length(); a++) {
                                JSONObject routesObject = routes.getJSONObject(a);
    
                                JSONArray legsArray = routesObject.getJSONArray("legs");
                                for (int b = 0; b < legsArray.length(); b++) {
                                    JSONObject legsObject = legsArray.getJSONObject(b);
                                    JSONArray steps = legsObject.getJSONArray("steps");
                                    for (int c = 0; c < steps.length(); c++) {
                                        JSONObject stepsObject = steps.getJSONObject(c);
                                        JSONObject polyLineObject = stepsObject.getJSONObject("polyline");
                                        mCoordinates.addAll(PolyUtil.decode(polyLineObject.getString("points")));
                                    }
                                }
    
                                PolylineOptions routeCoordinates = new PolylineOptions();
                                for (LatLng latLng : mCoordinates) {
                                    routeCoordinates.add(new LatLng(latLng.latitude, latLng.longitude));
                                }
                                routeCoordinates.width(5);
                                routeCoordinates.color(Color.BLUE);
    
                                Polyline route  = mGoogleMap.addPolyline(routeCoordinates);
    
                            }
    
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
    
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                // TODO handle error
            }
    
        });