Dynamically Adjust Google Maps Zoom with Xamarin.iOS

One of the challenges when displaying information within a map is determining the right zoom level. The map needs to be zoomed out far enough to allow the user to see a broad set of information but zoomed in close enough to provide enough detail to give the user context.

In our app at Spectafy, getting this right is very important to us. Since the focus of our app is providing user’s with information about things that are going on nearby them, we want to be sure to get that map zoom level just right.

For a user in a large city like San Francisco or New York, a zoom level that shows just a few blocks may show them everything they need. For someone like me who lives in Central Florida, we may need to zoom out a bit further.

To resolve this challenge, we have our app start initially at a reasonably zoomed-in level and then adjust the zoom level dynamically to to include a minimum number of markers.

Determining that appropriate zoom level is pretty straightforward because Google Maps’ zooming changes at a very predictable rate … each reduction in the zoom level (a lower zoom level value is further away) doubles the amount of map that’s visible.

Knowing that, we’re able to determine our perfect zoom level with a method like the following.

float CalculateMarkerInclusiveZoomLevel(
  MapView mapView, List<Marker> markers, 
  int minVisible)
{
 var bounds = 
   new CoordinateBounds(mapView.Projection.VisibleRegion);
 var initialZoomLevel = mapView.Camera.Zoom;
 var markerInclusiveZoomLevel = initialZoomLevel;

 var count = markers.Count(
   m => bounds.ContainsCoordinate(m.Position));

 while (count < markers.Count && count < minVisible)
 {
   // Each zoom level doubles the viewable area
   var latGrowth = 
     (bounds.NorthEast.Latitude - bounds.SouthWest.Latitude) / 2;
   var lngGrowth = 
     (bounds.NorthEast.Longitude - bounds.SouthWest.Longitude) / 2;
   markerInclusiveZoomLevel--;

   bounds = new CoordinateBounds(
      new CLLocationCoordinate2D(
          bounds.NorthEast.Latitude + latGrowth, 
          bounds.NorthEast.Longitude + lngGrowth),
      new CLLocationCoordinate2D(
          bounds.SouthWest.Latitude - latGrowth, 
          bounds.SouthWest.Longitude - lngGrowth));

   count = 
     markers.Count(m => bounds.ContainsCoordinate(m.Position));
 }

return markerInclusiveZoomLevel;
}

The above method accepts in a reference to the MapView being displayed, the list of markers on the map, and the minimum number of markers that should be visible to the user.

The method then gets the bounds of the map that’s visible at the current zoom level and what that zoom level is. From there it’s just simple math.

The app counts how many markers are visible within the current bounds. It then keeps doubling the size of the bounds and decrementing the zoom level (lower zoom level is further away) until the desired number of markers is visible within that bounds(either the value of minVisible or all of the markers if the list contains less then minVisible markers).

We can then use the above method similar to the following

// Assumes _mapView is already visible and contains the list of markers in _markers
var newZoomLevel = 
  CalculateMarkerInclusiveZoomLevel(_mapView, _markers, 10);
_mapView.Animate(
  CameraUpdate.SetTarget(_mapView.Camera.Target, newZoomLevel));

With this code, the map will now animate to the appropriate zoom level to make at least 10 markers visible.

That easily, we’re now able to give the user just what the want … the right amount of information with the right amount of context.

Use Xamarin.iOS to Control Google Maps Zoom Speed for a More Dramatic Look

We’re working on the next major update to our app at Spectafy and as part of that work, we plusing a lot of the visual effects.

One of the things we’ve discovered is that there are some scenarios where slowing down the rate we zoom in or out on the map is helpful in drawing the user’s attention to the new information exposed at the new zoom level.

Of course, there’s one problem …

Google Maps on iOS doesn’t expose a way to control the rate of zoom.

The good news is …

Google Maps uses standard iOS animation features to perform the zoom animation. As a result, we can use Core Animation classes to override the rate of zoom.

Here’s the code…

CATransaction.Begin();
CATransiaction.AnimationDuration = 3;
_mapView.Animate(CameraUpdate.SetTarget(new CLLocationCoordinate2D(lat, lng), zoomLevel));
CATransaction.Commit();

In the above code, we simply place the standard Google Maps call that triggers the zoom animation [ _mapView.Animate( … ) ] within a CATransaction, and set the AnimationDuration to 3 seconds within that transaction. That easily we’ve slowed down the animation so that the zoom effect takes 3 seconds.

Try it out … you’ll find that subtle changes in the zoom speed can have a dramatic effect on user experience.

My Xamarin Forms Course is Live at Pluralsight

My Introduction to Xamarin Forms course is now up on Pluralsight. The initial feedback has been fantastic.

Here’s a list of the modules from the course.

  • Building Your First Xamarin Forms App
  • Understanding Xamarin Forms
  • Providing UI Behavior with Views and XAML
  • Xamarin Forms ListViews
  • Xamarin Forms Layouts
  • Xamarin Forms Pages
  • Handling Platform Specific Requirements

The course is designed to give you a high-level look at Xamarin Forms and give you an idea of what working in Xamarin Forms is like. It’s super code intensive … lots of codes w/ very few slides.

I encourage you to check it out. I think you’ll like it.

BTW: If you haven’t worked in Xamarin (which is not the same thing as Xamarin Forms … but they’re closely related), you might want to check out my 2-part series Building Cross-Platform iOS/Android Apps with Xamarin, Visual Studio, and C#.

And if you’re looking to have a little more fun, I talk about using  Xamarin to build a Google Glass app in Creating a Google Glass App with C# and Xamarin.

I love writing these courses … I hope you enjoy them.