Skip to content Skip to sidebar Skip to footer

Google Map Cluster With Direction

We have a Google map library to show multiples markers in cluster. I have two questions: 1. Can we show multiple Directions on Google map just like below image? 2. Can we show mu

Solution 1:

You can achieve your goal by using the Directions API. You supply the starting point and the ending point (it can be either lat/lon or name of the place). Another required field is the traveling mode (driving by default).

Now the Directions return the vertexes which can be transformed to a polyline and then drawn on the map. This answer does that pretty well and I'll be using it's code.

package com.example.simon.maps;

import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Document;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;
import android.graphics.Color;
import android.os.Bundle;
import android.os.StrictMode;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.util.SparseArray;

/**
 * Created by Simon on 2014 Jul 25.
 */publicclassMainActivityextendsFragmentActivity {

    finalstaticStringTAG="MainActivity";
    GoogleMap mMap;
    GMapV2Direction md;
    int mZoomLevel;
    finalfloatSTARTING_ZOOM=5.0f;
    // List of polylines for each zoom level
    SparseArray<List<Polyline>> mPolylines = newSparseArray<List<Polyline>>();

    publicclassdirection {
        String start, end;
        int zoomLevel;

        direction(String pStart, String pEnd, int pZoomLevel) {
            start = pStart;
            end = pEnd;
            zoomLevel = pZoomLevel;
        }
    }

    publicvoidonCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (android.os.Build.VERSION.SDK_INT > 9) {
            StrictMode.ThreadPolicyp=newStrictMode.ThreadPolicy.Builder().permitAll().build();
            StrictMode.setThreadPolicy(p);
        }

        md = newGMapV2Direction();
        mMap = ((MapFragment)getFragmentManager().findFragmentById(R.id.map)).getMap();

        List<direction> directions = newArrayList<direction>();
        directions.add(newdirection("Ahmedabad,Gujarat,India", "delhi,India", 4));
        directions.add(newdirection("Ahmedabad,Gujarat,India","Bombay,Maharashtra,India", 4));
        directions.add(newdirection("Jeypore,Odisha,India", "delhi,India", 5));
        for (MainActivity.direction direction : directions) {
            // QueryDocumentdoc= md.getDocument(direction.start, direction.end);
            // Parse the xmlif (doc == null) {
                Log.e(TAG, "Failed to get the route from " + direction.start
                        + " to " + direction.end);
                continue;
            }
            // Get points
            ArrayList<LatLng> directionPoint = md.getDirection(doc);
            // Convert vertexes to a polylinePolylineOptionsrectLine=newPolylineOptions().width(3).color(Color.RED);
            for (LatLng aDirectionPoint : directionPoint) {
                rectLine.add(aDirectionPoint);
            }
            // Add poly to the map
            addPolyline(rectLine, direction.zoomLevel);
        }
        // Get the starting point of the first directionLatLngstart= mPolylines.valueAt(0).get(0).getPoints().get(0);
        mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(start, STARTING_ZOOM), 1000, null);
        // Set the initial zoom level and show the necessary polylines
        mZoomLevel = (int) STARTING_ZOOM;
        initPolylines(mZoomLevel);

        // Listen for the camera zoom level changes
        mMap.setOnCameraChangeListener(newGoogleMap.OnCameraChangeListener() {
            @OverridepublicvoidonCameraChange(CameraPosition cameraPosition) {
                // Note that because we are casting the zoomLevel to int,// it will be considered as changed only when it reaches// a new integer when rounded (e.g. 5.0, 6.0 etc.)intnewZoomLevel= (int) cameraPosition.zoom;
                if (newZoomLevel != mZoomLevel) {
                    Log.d(TAG, "New zoom level: " + newZoomLevel);
                    // Loop all the changed zoom levels// E.g. zoomed-out from 15 to 13, then hide [14, 15]if (newZoomLevel < mZoomLevel) { // Zoomed outfor (int i=1; i<=mZoomLevel-newZoomLevel; i++)
                            hidePolylines(mPolylines.get(newZoomLevel+i));
                    } else { // Zoomed-infor (int i=1; i<=newZoomLevel-mZoomLevel; i++)
                            showPolylines(mPolylines.get(mZoomLevel + i));
                    }
                    mZoomLevel = newZoomLevel;
                }
            }
        });
    }

    privatevoidaddPolyline(PolylineOptions polyOpts, int zoomLevel) {
        List<Polyline> polylines = mPolylines.get(zoomLevel);
        // Create polyline list for this zoom level, if it still doesn't existif (polylines == null) {
            polylines = newArrayList<Polyline>();
            mPolylines.put(zoomLevel, polylines);
        }
        // Append a new item to this poly listPolylinepolyline= mMap.addPolyline(polyOpts);
        polyline.setVisible(false);
        polylines.add(polyline);
    }

    privatevoidinitPolylines(int zoomLevel) {
        for(int i=0; i<mPolylines.size(); i++) {
            // Loop until zoom level is reachedif  (mPolylines.keyAt(i) > zoomLevel) break;

            showPolylines(mPolylines.get(mPolylines.keyAt(i)));
        }
    }

    privatevoidshowPolylines(List<Polyline> polylines) {
        if (polylines != null)
            for (Polyline polyline : polylines) polyline.setVisible(true);
    }

    privatevoidhidePolylines(List<Polyline> polylines) {
        if (polylines != null)
            for (Polyline polyline : polylines) polyline.setVisible(false);
    }

}

This code adds the polylines to a SparseArray which holds the polylines for each zoom level. All polies are hidden by default and are revealed only when a specific zoom level is reached (also at initPolylines).

There are a few things to note here:

  1. Because the zoom can change by a few levels at once we loop each level to hide/show specific directions.
  2. The location names have to be precise or they won't be found and you might receive a bunch of errors in the code (I've only added a Log.e)
  3. If you want to create markers at, let's say the starting and the ending point of the direction, you can use the following code to get the coordinates:

    List<LatLng> points = mPolylines.valueAt(0).get(0).getPoints();
    LatLng start = points.get(0);
    LatLng end = points.get(points.size()-1);
    
  4. Markers can be added/removed at specific zoom levels just like it's done here with the polylines.

And here's the code of the xml parser (GMapV2Direction):

package com.example.simon.maps;

import java.io.InputStream;
import java.util.ArrayList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.google.android.gms.maps.model.LatLng;

publicclassGMapV2Direction {

    publicGMapV2Direction() { }

    public Document getDocument(String start, String end) {
        Stringurl="http://maps.googleapis.com/maps/api/directions/xml?"
                + "origin="+start+"&destination="+end+"&units=metric&mode=driving";
        try {
            HttpClienthttpClient=newDefaultHttpClient();
            HttpContextlocalContext=newBasicHttpContext();
            HttpPosthttpPost=newHttpPost(url);
            HttpResponseresponse= httpClient.execute(httpPost, localContext);
            InputStreamin= response.getEntity().getContent();
            DocumentBuilderbuilder= DocumentBuilderFactory.newInstance().newDocumentBuilder();
            return builder.parse(in);
        } catch (Exception e) {
            e.printStackTrace();
        }
        returnnull;
    }

    public ArrayList<LatLng> getDirection(Document doc) {
        NodeList nl1, nl2, nl3;
        ArrayList<LatLng> listGeopoints = newArrayList<LatLng>();
        nl1 = doc.getElementsByTagName("step");
        if (nl1.getLength() > 0) {
            for (inti=0; i < nl1.getLength(); i++) {
                Nodenode1= nl1.item(i);
                nl2 = node1.getChildNodes();

                NodelocationNode= nl2.item(getNodeIndex(nl2, "start_location"));
                nl3 = locationNode.getChildNodes();
                NodelatNode= nl3.item(getNodeIndex(nl3, "lat"));
                doublelat= Double.parseDouble(latNode.getTextContent());
                NodelngNode= nl3.item(getNodeIndex(nl3, "lng"));
                doublelng= Double.parseDouble(lngNode.getTextContent());
                listGeopoints.add(newLatLng(lat, lng));

                locationNode = nl2.item(getNodeIndex(nl2, "polyline"));
                nl3 = locationNode.getChildNodes();
                latNode = nl3.item(getNodeIndex(nl3, "points"));
                ArrayList<LatLng> arr = decodePoly(latNode.getTextContent());
                for (LatLng anArr : arr) {
                    listGeopoints.add(newLatLng(anArr.latitude, anArr.longitude));
                }

                locationNode = nl2.item(getNodeIndex(nl2, "end_location"));
                nl3 = locationNode.getChildNodes();
                latNode = nl3.item(getNodeIndex(nl3, "lat"));
                lat = Double.parseDouble(latNode.getTextContent());
                lngNode = nl3.item(getNodeIndex(nl3, "lng"));
                lng = Double.parseDouble(lngNode.getTextContent());
                listGeopoints.add(newLatLng(lat, lng));
            }
        }

        return listGeopoints;
    }

    privateintgetNodeIndex(NodeList nl, String nodename) {
        for(inti=0 ; i < nl.getLength() ; i++) {
            if(nl.item(i).getNodeName().equals(nodename))
                return i;
        }
        return -1;
    }

    private ArrayList<LatLng> decodePoly(String encoded) {
        ArrayList<LatLng> poly = newArrayList<LatLng>();
        intindex=0, len = encoded.length();
        intlat=0, lng = 0;
        while (index < len) {
            int b, shift = 0, result = 0;
            do {
                b = encoded.charAt(index++) - 63;
                result |= (b & 0x1f) << shift;
                shift += 5;
            } while (b >= 0x20);
            intdlat= ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
            lat += dlat;
            shift = 0;
            result = 0;
            do {
                b = encoded.charAt(index++) - 63;
                result |= (b & 0x1f) << shift;
                shift += 5;
            } while (b >= 0x20);
            intdlng= ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
            lng += dlng;

            LatLngposition=newLatLng((double) lat / 1E5, (double) lng / 1E5);
            poly.add(position);
        }
        return poly;
    }
}

Some final notes:

  • If you've a lot of directions load them inside initPolylines and showPolylines and unload them in hidePolylines
  • If the directions you want to show on the map are constant, the best way would be to use tiles with polylines on specific zoom levels. A good free tool for that is Maperitive, it can export the tiles to as many zoom levels as you want. Then you would store the tiles online and use UrlTileProvider to display them on your map.

Solution 2:

I dont't know if I understood your question correctly, but I'll try to answer anyway. You can create multiple polylines and add to map from the JSON response from google maps direction API. This way you can add several routes in a single map.

As far as I understood the second question, you want the cluster marker to give details of the routes you are drawing. For that, I guess you can cluster the points returned by the API and use custom marker clusters to show the info you want.

Hope it helps

-Edit- If you want to draw routes in an Android app, you don't get any DirectionRenderer you can use. You need to render the routes using Polylines. That's why I have mentioned creating polylines in the answer above. As a response to the Direction request, you get a JSON containing several coordinate points which can be joined by drawing a polyline through them. You can have multiple such polylines on a map (from any point to any point). Case 1: you want 2 routes from Bombay to Goa, set the alternatives parameter to true. Case 2: you want two different routes, Bangalore to Bombay, Goa to Delhi, make two different requests to the API. You'll get two different routes and draw them on map.

For your second question, since there is no inbuilt clustering for routes (polylines), you have to code the logic of cluster icons showing up in the map. For getting the current zoom level, use the following.

GoogleMap map;    
float zoom = map.getCameraPosition().zoom;

For showing and hiding polylines

Polyline line = map.addPolyline(new PolylineOptions()
 .add(new LatLng(51.5, -0.1), new LatLng(40.7, -74.0))

 // multiple points that you get from the response of directions API

 .width(5)
 .color(Color.RED));

//your logic for visibilityif(zoom<=5){
  line.setVisible(true);
}

I couldn't find any way in the API to hide the cluster Icon using code. I guess it should hide itself after certain zoom level.

Post a Comment for "Google Map Cluster With Direction"