Hiding and Showing on screen Markers with Google Maps Android API V2

In this blog post, I will demonstrate how to add Markers as they move on screen and remove Markers as they move off screen using the new Google Maps Android API V2 that was released this week by google. My goal was to update one of my existing apps to use the new framework, and hopefully get rid of a lot of custom code needed in the previous version of the API.

Background:

My app has about 2600 markers that I want to place on the map. In the old version of the API, I found that having 2600 markers on the screen at the same time produced horrendous results and the map became unusably slow. I was hoping with the new API this would be resolved and having that many markers on the map at the same time…unfortunately, this is not the case and the new version of the API still can’t handle 1000s of markers without choking – even on a brand new Nexus 4.

I don’t have the time right now to implement any Marker clustering (although this is something I hope to do in the future…or even better maybe google will update the API to support this by default like they do with the javascript version of the API), so I had to come up with another solution: only showing markers that are currently within the viewable bounds of the GoogleMap as perceived by the user. This is not a perfect solution, but it greatly increases the responsiveness of the app when there are lots of markers to show and you know your user probably only cares to see a few at a time.

I’m assuming you have already setup your project to use the new API V2 and I will not get into any of those specifics in this post.

Alright, on to the code.

First of all, you’re going to need to keep track of all the Markers that you’ve added to the Map. Unfortunately, there doesn’t currently seem to be a way to access Markers that are added to a GoogleMap once they have been added, so create some sort of way to track them, such as using a HashMap:

private HashMap<Integer, Marker> visibleMarkers = new HashMap<Integer, Marker>();

For me, the Integer key that I’m using in the HashMap is the id of the item that I’m marking on the map. In my case, the ids are coming from database objects, so I know they are unique.

When you initially set up your GoogleMap instance, you will need to add an OnCameraChangedListener so you know when the map has been panned or zoomed by the user, so let’s do that:

//mMap is an instance of GoogleMap that has already been initialized else where
mMap.setOnCameraChangeListener(getCameraChangeListener());

getCameraChangeListener() just returns an OnCameraChangeListener implementation. When the camera changes, we’ll want to check to see what Markers are on the viewable map region and what Markers are no longer on the viewable map region and hide/show them appropriately. So, the OnCameraChangeListener will call a method to do just that:

public OnCameraChangeListener getCameraChangeListener()
{
    return new OnCameraChangeListener() 
    {
        @Override
        public void onCameraChange(CameraPosition position) 
        {
            addItemsToMap(this.items);
        }
    };
}

Now, let’s get to the fun part – the method to add Markers when they are on the screen, and hide them when they are off the screen. I’ll add comments in the code to walk you through it, but it’s pretty straight-forward.

//Note that the type "Items" will be whatever type of object you're adding markers for so you'll
//likely want to create a List of whatever type of items you're trying to add to the map and edit this appropriately
//Your "Item" class will need at least a unique id, latitude and longitude.
private void addItemsToMap(List<Item> items)
{
    if(this.mMap != null)
    {
        //This is the current user-viewable region of the map
        LatLngBounds bounds = this.mMap.getProjection().getVisibleRegion().latLngBounds;

        //Loop through all the items that are available to be placed on the map
        for(Item item : items) 
        {

            //If the item is within the the bounds of the screen
            if(bounds.contains(new LatLng(item.getLatitude(), item.getLongitude()))
            {
                //If the item isn't already being displayed
                if(!courseMarkers.containsKey(item.getId()))
                {
                    //Add the Marker to the Map and keep track of it with the HashMap
                    //getMarkerForItem just returns a MarkerOptions object
                    this.courseMarkers.put(item.getId(), this.mMap.addMarker(getMarkerForItem(item)));
                }
            }

            //If the marker is off screen
            else
            {
                //If the course was previously on screen
                if(courseMarkers.containsKey(item.getId()))
                {
                    //1. Remove the Marker from the GoogleMap
                    courseMarkers.get(item.getId()).remove();
                
                    //2. Remove the reference to the Marker from the HashMap
                    courseMarkers.remove(item.getId());
                }
            }
        }
    }
}

And that’s it! Now as the user pans/drags/zooms/tilts the map, only items that are “on screen” will be added to the map, greatly increasing responsiveness when there are lots of Markers.

I know this isn’t the best solution on the planet but it works pretty well. If anyone has tips/comments/suggestions, please feel free to let me know!

Advertisements

3 thoughts on “Hiding and Showing on screen Markers with Google Maps Android API V2

  1. Reza

    Thanks for sharing.
    I believe your post help me to reduce some unnecessary operations.

    Although my use case a little different.
    I need to display a marker and a title at the top of it. In this case a make a new icon composed of icon for marker and TextView converted to bitmap.

    And at certain zoom level i need to hide the title part. For this case i clear all the marker and recreate the icon without title now.
    I believe this is worst way. In V1 i just simply add flag in draw method of overlayitem.

    Seems in V2 i need to duplicate every marker with and without title. Any idea for this? 🙂

    Reply
    1. discgolfsoftware Post author

      Hi Reza,

      When I was reading the API documentation, I noticed that once you’ve set an icon for a Marker, it cannot be changed. I think this implies that you have to remove it and create then add a new Marker in order to modify the icon.

      From the documentation for Marker:

      Icon
      A bitmap that’s displayed for the marker. If the icon is left unset, a default icon is displayed. You can specify an alternative coloring of the default icon using defaultMarker(float). You can’t change the icon once you’ve created the marker.

      I think with the current state of the API you’ll have to remove the Marker, update the icon, and then add it again.

      In my actual app that I’m using this for I have a need change the Marker icon when I’ve zoomed out so that the markers are smaller and don’t overlap, and to do that I’m just passing a drawable for the icon to my addItemsToMap() method like -> addItemsToMap(List items, int drawableId) and setting a member variable to let the addItemsToMap method know that I’m redrawing the Markers because I’m performing a zoom operation. It’s not super fast, but it’s the best I can come up with for now.

      Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s