Google Maps API v3: Can I setZoom after fitBounds?
Asked Answered
C

24

220

I have a set of points I want to plot on an embedded Google Map (API v3). I'd like the bounds to accommodate all points unless the zoom level is too low (i.e., zoomed out too much). My approach has been like this:

var bounds = new google.maps.LatLngBounds();

// extend bounds with each point

gmap.fitBounds(bounds); 
gmap.setZoom( Math.max(6, gmap.getZoom()) );

This doesn't work. The last line "gmap.setZoom()" doesn't change the zoom level of the map if called directly after fitBounds.

Is there a way to get the zoom level of a bounds without applying it to the map? Other ideas to solve this?

Cordovan answered 13/3, 2010 at 7:49 Comment(1)
See https://mcmap.net/q/88499/-using-setzoom-after-using-fitbounds-with-google-maps-api-v3Thursby
B
374

Edit: See Matt Diamond's comment below.

Got it! Try this:

map.fitBounds(bounds);
var listener = google.maps.event.addListener(map, "idle", function() { 
  if (map.getZoom() > 16) map.setZoom(16); 
  google.maps.event.removeListener(listener); 
});

Modify to your needs.

Barca answered 31/10, 2010 at 20:41 Comment(14)
Great solution, totally saved me a lot of a trouble. Just wanted to add that you could simplify it further by using the addListenerOnce method... that way, you don't have to save the listener and manually remove it, as the method will take care of that for you.Burdick
This has the behavior of seeing the map "skip". Instead listen for the "zoom_change" event, and set it up before calling fitBounds(). See my answer belowCadaver
Thanks! google.maps.event.addListenerOnce(map, "idle", function() { } --> worked great for my maps package for SugarCRM.Glossotomy
The listener doesn't get triggered for me. I tried this in document.ready and window.load. Any ideas? map object is OK and tried addListener also: ` var map=$("#js-main-map-canvas"); var listener = google.maps.event.addListenerOnce(map, "idle", function() { alert('hello'); });`Americanize
Henrik, try $("#js-main-map-canvas")[0] or $("#js-main-map-canvas").get(0);Elvinaelvira
Rafael, didn't help. I do get an object with my code, with yours I get a HTMLdivelement. This is in document.ready.Americanize
@Henrik Sorry for the late reply. With your code, you get an object that's not a Google Map object but a jQuery object (or whatever they are called). Try to use the pointer/reference that you get when you create the map, like so: var map = new google.maps.Map(document.getElementById("gmap-canvas"), options);Barca
in case map.fitBounds(bounds); didn't update the map (in my case i was merging some points and sometimes it does not change the bounds) then ideal event doesn't get triggered at that time, it gets triggered after next change_bound event may that be a manual operation. this is how i did it: https://mcmap.net/q/87493/-google-maps-api-v3-can-i-setzoom-after-fitboundsInharmonic
If fitBounds isn't causing the 'bounds_changed' event to fire, then use 'center_changed' instead. It changes every time the fitBounds() function is called. See #35761916Greed
Or you can set the maxZoom option when you first initiate the map (new google.maps.Map(...)).Bezonian
With event "bounds_changed" is zoom animations more quick and smooth.Jeter
You can use addListnerOnce to avoid the logic to remove it.Reputed
Honestly, this fixed my problem with just pasting - thanks a lot!Contributory
Still relevant!!Fabi
T
83

I solved a similar problem in one of my apps. I was a little confused by your description of the problem, but I think you have the same goal I had...

In my app I wanted to plot a one or more markers and ensure the map was showing them all. The problem was, if I relied solely on the fitBounds method, then the zoom-level would be maxed out when there was a single point - that was no good.

The solution was to use fitBounds when there was many points, and setCenter+setZoom when there was only one point.

if (pointCount > 1) {
  map.fitBounds(mapBounds);
}
else if (pointCount == 1) {
  map.setCenter(mapBounds.getCenter());
  map.setZoom(14);
}
Tertiary answered 16/7, 2010 at 18:23 Comment(3)
Your answer have solve my problem with Google Maps V3. Thanks!Tremendous
I was trying the same approach by myself but it did not work because I wasn't invoking setCenter() before setting the zoom... I'm still wondering why this step is needed, but anyway now it works... thanks a lot Jim!Damalis
This is nice because you don't have to deal with the animation jump of the accepted answer.Medlock
C
46

I have come to this page multiple times to get the answer, and while all the existing answers were super helpful, they did not solve my problem exactly.

google.maps.event.addListenerOnce(googleMap, 'zoom_changed', function() {
    var oldZoom = googleMap.getZoom();
    googleMap.setZoom(oldZoom - 1); //Or whatever
});

Basically I found that the 'zoom_changed' event prevented the UI of the map from "skipping" which happened when i waited for the 'idle' event.

Hope this helps somebody!

Cadaver answered 1/2, 2011 at 22:32 Comment(4)
Excellent, I had the 'skipping / auto-zooming' behavior as well. Just as a note for other readers: be sure to use "addListenerOnce" as Benjamin does above instead of "addListener", or break your head on why your browser crashes all the time ;-)Claudineclaudio
Also note that the listener should be added before calling fitBounds() if using zoom_changedSwabber
Thanks for this, seems like v4 should have a fitBounds(bounds,minZoomLevel) :)Rudd
Benjamin, I think it is better to use bounds_changed, because zoom_changed doesn't have to be triggered in the case when fitBounds keeps the zoom level. My attempt jsfiddle.net/rHeMp/10 doesn't confirm that, but one should not rely on undefined behaviour.Schleswig
L
11

I’ve just fixed this by setting maxZoom in advance, then removing it afterwards. For example:

map.setOptions({ maxZoom: 15 });
map.fitBounds(bounds);
map.setOptions({ maxZoom: null });
Lectureship answered 24/3, 2014 at 15:13 Comment(3)
The best solution by far for preventing zooming in too much. Simple and intuitive. No drawing and re-drawing of map.Fordham
This is indeed the best walkaround so far.Diastema
I found that this doesn't work if you reset maxZoom immediately after calling fitBounds, because the zoom happens asynchronously (so maxZoom is back to null by the time it zooms in). Waiting until "idle" to reset it would fix this though I guess.Mears
L
5

Please try this:

map.fitBounds(bounds);

// CHANGE ZOOM LEVEL AFTER FITBOUNDS
zoomChangeBoundsListener = google.maps.event.addListenerOnce(map, 'bounds_changed', function(event) {
  if (this.getZoom()){
    this.setZoom(15);
  }
});
setTimeout(function(){
  google.maps.event.removeListener(zoomChangeBoundsListener)
}, 2000);
Lotion answered 23/12, 2015 at 11:11 Comment(0)
A
4

If I'm not mistaken, I'm assuming you want all your points to be visible on the map with the highest possible zoom level. I accomplished this by initializing the zoom level of the map to 16(not sure if it's the highest possible zoom level on V3).

var map = new google.maps.Map(document.getElementById('map_canvas'), {
  zoom: 16,
  center: marker_point,
  mapTypeId: google.maps.MapTypeId.ROADMAP
});

Then after that I did the bounds stuff:

var bounds = new google.maps.LatLngBounds();

// You can have a loop here of all you marker points
// Begin loop
bounds.extend(marker_point);
// End loop

map.fitBounds(bounds);

Result: Success!

Areaway answered 22/7, 2010 at 18:2 Comment(0)
P
4

I saw many incorrect or too complicated solutions, so decided to post a working, elegant solution.

The reason setZoom() doesn't work as you expect is that fitBounds() is asynchronous, so it gives no guarantee that it would immediately update the zoom, but your call to setZoom() relies on that.

What you might consider is setting the minZoom map option before calling fitBounds() and then clearing it after it completes (so that users can still zoom out manually if they want to):

var bounds = new google.maps.LatLngBounds();
// ... (extend bounds with all points you want to fit)

// Ensure the map does not get too zoomed out when fitting the bounds.
gmap.setOptions({minZoom: 6});
// Clear the minZoom only after the map fits the bounds (note that
// fitBounds() is asynchronous). The 'idle' event fires when the map
// becomes idle after panning or zooming.
google.maps.event.addListenerOnce(gmap, 'idle', function() {
  gmap.setOptions({minZoom: null});
});

gmap.fitBounds(bounds);

In addition, if you want to also limit the max zoom, you can apply the same trick with the maxZoom property.

See the MapOptions docs.

Patras answered 19/5, 2017 at 22:22 Comment(0)
B
3

I use:

gmap.setZoom(24); //this looks a high enough zoom value
gmap.fitBounds(bounds); //now the fitBounds should make the zoom value only less

This will use the smaller of 24 and the necessary zoom level according to your code, however it probably changes the zoom anyway and doesn't care about how much you zoomed out.

Blucher answered 25/5, 2010 at 9:35 Comment(1)
I hate to say this, but as weird as this answer looked like, it's the only thing that worked so far and I've been searching for 4-5hrs on a 3 days span. Will still be searching for a better solution though.Leukas
P
2

I had the same issue and I was able to solve it using the following code. This listener (google.maps.addListenerOnce()) event will only get fired once, right after map.fitBounds() is executed. So, there is no need to

  1. Keep track of and manually remove the listener, or
  2. Wait until the map is idle.

It sets the appropriate zoom level initially and allows the user to zoom in and out past the initial zoom level because the event listener has expired. For example, if only google.maps.addListener() was called, then the user would never be able to zoom-in past the stated zoom level (in the case, 4). Since we implemented google.maps.addListenerOnce(), the user will be able to zoom to any level he/she chooses.

map.fitBounds(bounds);

var zoom_level_for_one_marker = 4;

google.maps.event.addListenerOnce(map, 'bounds_changed', function(event){
   if (this.getZoom() >= zoom_level_for_one_marker){  
       this.setZoom(zoom_level_for_one_marker) 
   }
});
Piselli answered 29/6, 2012 at 14:37 Comment(0)
S
2

Had the same problem, needed to fit many markers on the map. This solved my case:

  1. Declare bounds
  2. Use scheme provided by koderoid (for each marker set bounds.extend(objLatLng))
  3. Execute fitbounds AFTER map is completed:

    google.maps.event.addListenerOnce(map, 'idle', function() { 
        map.fitBounds( bounds );
    });
    
Serenaserenade answered 9/11, 2012 at 18:18 Comment(0)
L
2

I've found a solution that does the check before calling fitBounds so you don't zoom in and suddenly zoom out

var bounds = new google.maps.LatLngBounds();

// extend bounds with each point

var minLatSpan = 0.001;
if (bounds.toSpan().lat() > minLatSpan) {
    gmap.fitBounds(bounds); 
} else {
    gmap.setCenter(bounds.getCenter());
    gmap.setZoom(16);
}

You'll have to play around with the minLatSpan variable a bit to get it where you want. It will vary based on both zoom-level and the dimensions of the map canvas.

You could also use longitude instead of latitude

Leaseback answered 2/4, 2013 at 14:53 Comment(1)
Works for me, but I'm not using minLatSpan - I need to set zoom only when 1 marker is in the map. Therefore I'm only checking if there're more then 1 markers.Cascabel
A
1

In this function, you need to dynamically add metadata to store the geometry type only because the function accepts any geometry.

"fitGeometries" is a JSON function extending a map object.

"geometries" is an generic javascript array not an MVCArray().

geometry.metadata = { type: "point" };
var geometries = [geometry];

fitGeometries: function (geometries) {
    // go and determine the latLngBounds...
    var bounds = new google.maps.LatLngBounds();
    for (var i = 0; i < geometries.length; i++) {
        var geometry = geometries[i];
        switch (geometry.metadata.type)
        {
            case "point":
                var point = geometry.getPosition();
                bounds.extend(point);
                break;
            case "polyline":
            case "polygon": // Will only get first path
                var path = geometry.getPath();
                for (var j = 0; j < path.getLength(); j++) {
                    var point = path.getAt(j);
                    bounds.extend(point);
                }
                break;
        }
    }
    this.getMap().fitBounds(bounds);
},
Administrate answered 16/7, 2010 at 14:15 Comment(1)
Alternately, I did all the work without knowing that there is an extend() method on the LatLngBounds object. This would be much easier.Administrate
A
1

I use this to ensure the zoom level does not exceed a set level so that I know satellite images will be available.

Add a listener to the zoom_changed event. This has the added benefit of controlling the zoom control on the UI also.

Only execute setZoom if you need to, so an if statement is preferable to Math.max or to Math.min

   google.maps.event.addListener(map, 'zoom_changed', function() { 
      if ( map.getZoom() > 19 ) { 
        map.setZoom(19); 
      } 
    });
    bounds = new google.maps.LatLngBounds( ... your bounds ... )
    map.fitBounds(bounds);

To prevent zooming out too far:

   google.maps.event.addListener(map, 'zoom_changed', function() { 
      if ( map.getZoom() < 6 ) { 
        map.setZoom(6); 
      } 
    });
    bounds = new google.maps.LatLngBounds( ... your bounds ... )
    map.fitBounds(bounds);
Abeu answered 21/7, 2010 at 6:26 Comment(0)
Z
1

this work's for me with API v3 but with setting fixed zoom:

var bounds = new google.maps.LatLngBounds();
// extend bounds with each point

gmap.setCenter(bounds.getCenter()); 
gmap.setZoom( 6 );
Zavala answered 13/3, 2013 at 13:46 Comment(0)
I
1

Like me, if you are not willing to play with listeners, this is a simple solution i came up with: Add a method on map which works strictly according to your requirements like this one :

    map.fitLmtdBounds = function(bounds, min, max){
        if(bounds.isEmpty()) return;
        if(typeof min == "undefined") min = 5;
        if(typeof max == "undefined") max = 15;

        var tMin = this.minZoom, tMax = this.maxZoom;
        this.setOptions({minZoom:min, maxZoom:max});
        this.fitBounds(bounds);
        this.setOptions({minZoom:tMin, maxZoom:tMax});
    }

then you may call map.fitLmtdBounds(bounds) instead of map.fitBounds(bounds) to set the bounds under defined zoom range... or map.fitLmtdBounds(bounds,3,5) to override the zoom range..

Inharmonic answered 10/10, 2015 at 5:44 Comment(0)
F
1

Promise based solution based on LGT's answer. Useful if you're having to do this more than once.

fitMapToBounds(map, bounds) {
    return new Promise((resolve) => {
        map.fitBounds(bounds);

        google.maps.event.addListenerOnce(map, "idle", () =>
            resolve(map.getZoom()));
    });
}

Then use it like:

const zoom = await fitMapToBounds(map, bounds);
map.setZoom(Math.min(zoom, 14));
Flaxen answered 30/3, 2023 at 18:46 Comment(0)
V
0

Please try this.

// Find out what the map's zoom level is
zoom = map.getZoom();
if (zoom == 1) {
  // If the zoom level is that low, means it's looking around the
world.
  // Swap the sw and ne coords
  viewportBounds = new
google.maps.LatLngBounds(results[0].geometry.location, initialLatLng);
  map.fitBounds(viewportBounds);
}

If this will helpful to you.

All the best

Vendible answered 7/7, 2010 at 6:13 Comment(0)
C
0

I don't like to suggest it, but if you must try - first call

gmap.fitBounds(bounds);

Then create a new Thread/AsyncTask, have it sleep for 20-50ms or so and then call

gmap.setZoom( Math.max(6, gmap.getZoom()) );

from the UI thread (use a handler or the onPostExecute method for AsyncTask).

I don't know if it works, just a suggestion. Other than that you'd have to somehow calculate the zoom level from your points yourself, check if it's too low, correct it and then just call gmap.setZoom(correctedZoom)

Children answered 17/7, 2010 at 1:33 Comment(0)
C
0

After calculation of the boundries you can check the distance between upper left and down right corner; then you can understand the zoom level by testing the distance (if distance is too far zoom level would be low) then you can select wheter using setbound method or setZoom..

Cultch answered 20/7, 2010 at 12:16 Comment(0)
G
0

If 'bounds_changed' is not firing correctly (sometimes Google doesn't seem to accept coordinates perfectly), then consider using 'center_changed' instead.

The 'center_changed' event fires every time fitBounds() is called, although it runs immediately and not necessarily after the map has moved.

In normal cases, 'idle' is still the best event listener, but this may help a couple people running into weird issues with their fitBounds() calls.

See google maps fitBounds callback

Greed answered 4/3, 2016 at 21:4 Comment(0)
J
0

To chime in with another solution - I found that the "listen for bounds_changed event and then set new zoom" approach didn't work reliably for me. I think that I was sometimes calling fitBounds before the map had been fully initialized, and the initialization was causing a bounds_changed event that would use up the listener, before fitBounds changed the boundaries and zoom level. I ended up with this code, which seems to work so far:

// If there's only one marker, or if the markers are all super close together,
// `fitBounds` can zoom in too far. We want to limit the maximum zoom it can
// use.
//
// `fitBounds` is asynchronous, so we need to wait until the bounds have
// changed before we know what the new zoom is, using an event handler.
//
// Sometimes this handler gets triggered by a different event, before
// `fitBounds` takes effect; that particularly seems to happen if the map
// hasn't been fully initialized yet. So we don't immediately remove the
// listener; instead, we wait until the 'idle' event, and remove it then.
//
// But 'idle' might happen before 'bounds_changed', so we can't set up the
// removal handler immediately. Set it up in the first event handler.

var removeListener = null;
var listener = google.maps.event.addListener(map, 'bounds_changed', () => {
  console.log(map.getZoom());
  if (map.getZoom() > 15) {
    map.setZoom(15);
  }

  if (!removeListener) {
    removeListener = google.maps.event.addListenerOnce(map, 'idle', () => {
      console.log('remove');
      google.maps.event.removeListener(listener);
    });
  }
});
Jasonjasper answered 5/3, 2018 at 12:57 Comment(0)
E
0

For me the easiest solution was this:

map.fitBounds(bounds);

function set_zoom() {
    if(map.getZoom()) {map.setZoom(map.getZoom() - 1);}
    else {setTimeout(set_zoom, 5);}
}
setTimeout(set_zoom, 5);
Erastus answered 23/3, 2018 at 19:9 Comment(0)
I
-1
google.maps.event.addListener(marker, 'dblclick', function () {
    var oldZoom = map.getZoom(); 
    map.setCenter(this.getPosition());
    map.setZoom(parseInt(oldZoom) + 1);
});
Illogical answered 5/12, 2013 at 11:13 Comment(0)
C
-3

All I did is:

map.setCenter(bounds.getCenter(), map.getBoundsZoomLevel(bounds));

And it works on V3 API.

Countershading answered 28/7, 2010 at 10:36 Comment(3)
map.setCenter accepts only one attribute, latlng, and map has no function like getBoundsZoomLevelSloganeer
I thought there is a function like getBoundsZoomLevel after I see this post, and I am disappointed after I realized there is not.Jijib
i think getBoundsZoomLevel() was available in Google Maps V2 API and not in V3 APIPazpaza

© 2022 - 2024 — McMap. All rights reserved.