Simple Clustering with Android Maps V2

Hello

this is a little post about make “clustering” in google android maps v2

Assumptions
- You have a class that contains latitude and longitude attributes

Strategy

  • Create a common interface for your model and cluster
  • Implement the methods of the interface in your model (and cluster) class
  • Create a parametrizable cluster as a set of model objects
  • Create a method that compute and show the clusters in the MapFragment

Common Interface to be used by the clusters and model objects.

To get an api that could be flexible to implement with more than one model object, first we have to define some rules.
In general, when we want to show an object into map, we must have the coordinates and the MarkerOptions objects. If we are thinking in to draw a cluster of pins, we need to consider the use of the bounding box for zoom operations, etc.

In that way, creating an interface with such methods is a good idea.

// MapMarker.java
public interface MapMarker {
	public MarkerOptions getMarkerOptions(Context context);
	public LatLng getLatLng();
}

The getMarkerOptions methods receive a context object. This is because may you want to use a drawable resource to build your marker.

Implementing the interface in our model

Consider a POJO called “User”, and, a pin resource named “my_pin_resource”

public interface User implements MapMarker{
	protected String name;
	protected String address;
	protected double latitude;
	protected double longitude;
        
	@Override
	public MarkerOptions getMarkerOptions(Context context) {
		MarkerOptions markerOptions = new MarkerOptions();
		markerOptions.position(getLatLng());
		markerOptions.title(name);
		markerOptions.snippet(address);
		int res = R.drawable.my_pin_resource;

                markerOptions.icon(BitmapDescriptorFactory.fromResource(res));
		return markerOptions;
	}

	@Override
	public LatLng getLatLng() {
		return (latitude != 0 && longitude != 0) ? new LatLng(latitude,
				longitude) : null;
	}
}

Create Parametrizable Cluster

The idea of the cluster is

  • To be a cluster when its contains more than a single object
  • To be a single user when the size of the cluster is 1.
  • The cluster position in the map is the centroid of all the object wich contains
public class Cluster<T extends MapMarker> implements MapMarker {

	protected List objects = new ArrayList();
	protected int size = objects.size();

	public Cluster(T object) {
		this.objects.add(object);
	}

	public void add(T object) {
		objects.add(object);
	}

	public T getSingle() {
		return (isSingle())? objects.get(0) : null;
	}

	public boolean isSingle() {
		return objects.size() == 1;
	}

	public static <T extends User> Cluster<T> init(T object) {
		return new Cluster<T>(object);
	}

	@Override
	public LatLng getLatLng() {
		// computing the centroid
		double lat = 0, lon = 0;
		int n = 0;

		for (T object : objects) {
			lat += object.getLatLng().latitude;
			lon += object.getLatLng().longitude;
			n++;
		}
		return new LatLng(lat / n, lon / n);
	}

	@Override
	public MarkerOptions getMarkerOptions(Context context) {
	
		if (isSingle())
			return getSingle().getMarkerOptions(context);

		MarkerOptions markerOptions = null;
		int res = R.drawable.my_cluster_resource;

		markerOptions = new MarkerOptions();
		markerOptions.position(getLatLng());
		markerOptions.icon(BitmapDescriptorFactory.fromResource(res));

		return markerOptions;
	}
}

Method in map Fragment to computing the clusters

The Strategy is very simple

  • Get an array of objects (in this case “Users”)
  • Divide the map screen into a Mesh, where each block in mesh is (x,y) tuple.
  • Get the bounding box of the map screen to build the mesh
  • Create a hashmap of clusters with (x,y) key of the mesh
  • For each position present in mesh, create a cluster with the object
public class UsersMapFragment extends MapFragment {
	User[] users;

	public void placeUsers(User[] users) {

		HashMap<String, Cluster<User>> clusters = new HashMap<String, Cluster<User>>();

		// bounding box of the screen
		LatLng ne = getMap().getProjection().getVisibleRegion().latLngBounds.northeast;
		LatLng sw = getMap().getProjection().getVisibleRegion().latLngBounds.southwest;

		// mesh rows and columns
		int x = 10;
		int y = 15;

		// row and column sizes
		double dx = (ne.longitude - sw.longitude) / x;
		double dy = (ne.latitude - sw.latitude) / y;

		int i = 0;
		for (User user : users) {

			if (user.getLatLng() == null)
				continue;

			// user position in the mesh (box)
			int nx = (int) (user.getLatLng().longitude / dx);
			int ny = (int) (user.getLatLng().latitude / dy);

			// obtain the key for the box
			String key = String.format("%d_%d", nx, ny);

			// Create or add to cluster 
			if (clusters.containsKey(key)) {
				clusters.get(key).add(user);
			} else {
				clusters.put(key, Cluster.init(user));
			}

			i++;

		}


		// Drawing process. Before draw clear the map
		getMap().clear();

		// Marker object to be drawed into map
		Marker marker;
		
		// Cluster object to be added to hashmap
		Cluster<User> cluster;

		// Drawing process, all cluster are drawn
		for (String key : clusters.keySet()) {
			cluster = clusters.get(key);
			MarkerOptions mo = cluster.getMarkerOptions(getActivity());
			marker = getMap().addMarker(mo);
		}
}

That is.

About these ads

Acerca de arnaldog

Estudiante de Ingeniería Civíl Informática en la UTFSM
Esta entrada fue publicada en Uncategorized y etiquetada , , , . Guarda el enlace permanente.

Deja un comentario

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s