47
votes

I'd like to be able to zoom in smoothly on a marker in Google Maps. If one just sets the zoom on double click, the map is suddenly on that zoom level, without any smooth transition.

Zooming in only one level further than the current level, Google Maps shows a nice smooth transition. So it must be possible to zoom in smoothly for more than one level, but how?

6

6 Answers

65
votes

As luck would have it, I wanted to achieve the same effect recently, and found a solution, which I made a post about. Basically, just setting a timeout for each transition isn't enough, because it could easily result in a 'start-stop' kind of zoom if Google's zoom effect isn't finished yet, or has long finished.

As Martin mentioned, there are some disadvantages to this, which I won't reiterate. Whether you use this in the end is your choice, and depends largely on your users' CPU power and/or browser. It is a nice effect though, and sure to impress some, when used wisely.

My solution was the following:

// example marker:
var marker = new google.maps.Marker({
    map: map, 
    position: new google.maps.LatLng(-20.3,30.3)
});


// add the double-click event listener
google.maps.event.addListener(marker, 'dblclick', function(event){
    map = marker.getMap();    
    map.setCenter(overlay.getPosition()); // set map center to marker position
    smoothZoom(map, 12, map.getZoom()); // call smoothZoom, parameters map, final zoomLevel, and starting zoom level
});


// the smooth zoom function
function smoothZoom (map, max, cnt) {
    if (cnt >= max) {
        return;
    }
    else {
        z = google.maps.event.addListener(map, 'zoom_changed', function(event){
            google.maps.event.removeListener(z);
            smoothZoom(map, max, cnt + 1);
        });
        setTimeout(function(){map.setZoom(cnt)}, 80); // 80ms is what I found to work well on my system -- it might not work well on all systems
    }
}  

Basically what it comes down to is adjusting the zoom level by one, listening for the zoom_changed event, waiting 80ms before adjusting the zoom-level by one again, etc. What's nice about this is that the zoom_changed event seems to be called after the smooth transition provided by Google Maps, but before the actual images are loaded, so it doesn't waste bandwidth too much.

The 80ms in the timeout is also a magic number I came up with - you would be well-advised to do a more thorough test and see what works on different systems and browsers, and perhaps change the algorithm slightly based on your findings or for different systems.

It's probably also not necessary to add and remove the listener every time, but you can make that small improvement yourself if you so wish.

12
votes

This one worked for me nicely:

function animateMapZoomTo(map, targetZoom) {
    var currentZoom = arguments[2] || map.getZoom();
    if (currentZoom != targetZoom) {
        google.maps.event.addListenerOnce(map, 'zoom_changed', function (event) {
            animateMapZoomTo(map, targetZoom, currentZoom + (targetZoom > currentZoom ? 1 : -1));
        });
        setTimeout(function(){ map.setZoom(currentZoom) }, 80);
    }
}
4
votes

You could try to use a setInterval to zoom in one level at a time, and clear it when you reach your desired level.

The problem with this is that the interval that will make it work is entirely dependent on the cpu and bandwidth of the users machine (how fast can it load and render the new set of image tiles).

Tbh, i'm not sure it can be done so that it will work great in every situation, but a small interval between zoom levels might help a bit.

A few things to keep in mind tho:

  1. this will put a lot more stress on the users cpu and bandwidth than going directly to the chosen zoomlevel
  2. the user will have to wait until this is done to start interacting with the map, which could easily become a very bad user experience.

Those two and probably other reasons are why google didn't build the kind of zoom you wish into maps in the first place - because it's a bad idea...

3
votes

@Herman Schaaf Your solution is great but when you double click it skips a few zooms :D So I have made a solution + smoothZoom out I can't take all the credit I have edited JesseDobbelaere's code from jsfiddle.net It's a mix of yours and Jesse's code.

function smoothZoom(map, level, cnt, mode)
{
    if(mode == true)
    {
        if (cnt >= level) {
            return;
        }
        else
        {
            if((maxZoomOut + 2) <= cnt)
            {
                var z = google.maps.event.addListener(map, 'zoom_changed', function(event)
                {
                    google.maps.event.removeListener(z);
                    map.setCenter(marker.getPosition());
                    smoothZoom(map, level, cnt + 1, true);
                });
                setTimeout(function(){map.setZoom(cnt);}, timeOut);
            }
            else
            {
                map.setZoom(cnt);
                smoothZoom(map, level, cnt + 1, true);
            }
        }
    }
    else 
    {
        if (cnt < level) {
            return;
        }
        else
        {
            var z = google.maps.event.addListener(map, 'zoom_changed', function(event)
            {
                google.maps.event.removeListener(z);
                map.setCenter(marker.getPosition());
                smoothZoom(map, level, cnt - 1, false);
            });
            if(maxZoomIn - 2 <= cnt)
            {
                map.setZoom(cnt);
            }
            else
            {
                setTimeout(function(){map.setZoom(cnt);}, timeOut);
            }
        }
    }
}    

I have made few more variables like timeOut and maxZoomIn Out... you can find full code on jsfiddle http://jsfiddle.net/dexy86/9afy9/

0
votes

I've tried the alternatives provided above, however, whenever I view the transition in a desktop browser or mobile app, it all occurs instantly, or at least, within a period of time which is imperceptible to the eye due to the time between each loop iteration, at least on the hardware I've trialled it on.

I used the same loop as the other methods, but altered the timeout so that the zoom occurred more quickly when the zoom level was higher, and more slowly when it was lower, so the results were more visible.

Instead of using a fixed timeout period, I set the first timeout to 0, and then subsequent timeouts to the following quick and dirty equation

((1+ currentZoom) / maxZoom) * 500;

In code I assumed the max zoom as 18, but you can use maxZoom to find out what the zoom level is for a given lat/long. My code is:

timeout = (1+map.getZoom() / 18) * 500;
-2
votes

This will zoom smoothly on desired location. Try adding these lines:

LatLng latLng = new LatLng(lat,lng);
map.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, ZOOM_FACTOR));