loading...

Optimising markers performance on Google Maps using RxJava

orelzion profile image orelzion Updated on ・2 min read

So today I ran into an interesting problem. I needed to display a large amount of markers on Google Map, and we decided that too many markers will just not look good, and so we are eliminating markers that intersect by over 70 percent, and display just one of them instead.

And so I wrote this little function that takes two Rect objects to see if they intersect.

But how do you get a rect object if all you have is a Lat/Lng of where you want to position your marker?
Well, luckily for us Google Maps already has a utility function, that converts a lat/lng on map into a Point object.

mGoogleMap.getProjection().toScreenLocation(latLng)

And so, I was able to create a Rect object based on that point plus the icon width and height.

Now we need to iterate over all lat/lngs and check which of them are intersecting.

Since this can be heavy (iterating over each object and check it against all other object in the list), I decided to run it inside an RxJava observable.

Seems legit, right?

Well, I thought, let's give it a run. Oh boy, that sucked terribly. I ran the app on a Google Pixel 2 device, and whenever I moved the screen and the markers needed redrawing, the app just stuck for a second.

So I looked at my code again, and although the method 'getPoiForVisibleRegion' is running in the background, the map operation that comes next is not! This is so because I only added the subscribeOn and observeOn methods inside 'getPoiForVisibleRegion' so the map operation that was called later was not included.

Ok, so I just need to include the subscribe method after the map declaration, right?

Not so fast, my friend.
Here's the catch. Google Maps can't get you the screen point while in background, so that means that I was able to run every other parts of my algorithm in RxJava but the intersection filter.

Back to the drawing table then.

Now I thought, what if I could get all the points for the currently visible markers before the filter, and make the actual filter run in background when it already has all the points?

And so I did just that. I created a HashMap that updates every time the map bounds changes (so that means zoom level has changed, or the map moves). I get the currently visible region of the map while still on the UI thread

mGoogleMap.getProjection().getVisibleRegion().latLngBounds;

Then I go and filter out only the markers that I should draw now. And I save a mapping between those locations to their point on screen

Viola!

The result was very satisfying! The map moves smoothly and the markers are getting removed or appeared on screen with a nice animation (not described in this post), without the user has to wait more than a fraction of a second, and more importantly without the screen ever gets stuck

Discussion

pic
Editor guide