Google Maps API V3 fitbounds() zooms out but never in
Asked Answered
T

12

10

I've created a quite complex store locator of sorts. The user enters their zip and a table returns results with corresponding markers on a map. They can page through results and the markers move further and further out and the fitbounds() function works great for zoom out to the appropriate zoom level to fit the markers. The problem is that if you page back to closer locations the fitbounds() doesn't zoom in. Even if you enter a new search it doesn't zoom in around those new markers -- it centers over them but stays at whatever zoom level it was at previously. I hope this makes sense. If anyone knows what I need to add to get it to zoom back in on closer results please help. These are the google functions that I'm call at the end of my marker pushing function:

  map.setCenter(bounds.getCenter());
  map.panToBounds(bounds);
  map.fitBounds(bounds);

Thanks!

Tempietempla answered 15/7, 2010 at 1:10 Comment(0)
C
11

Nothing fancy needed here. First fit bounds then pan to it. This will give you the proper zoom and contain the entire bounds.

map.fitBounds(bounds);
map.panToBounds(bounds);
Countable answered 6/9, 2011 at 22:29 Comment(1)
This seems to work. I wonder what panToBounds is supposed to do without fitBounds.Bigamist
T
11

The problem is this: we set

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

so that we can later fit our markers to a bounded area on the map. GMaps will always zoom out asynchronously to fitBounds() accordingly, but will not zoom in to achieve the same (as previously noted by @broady). This is not ideal for many applications as once you have gone and displayed a series of markers on the map that caused the map to zoom out (maybe <10), it will not zoom back in without the user manually doing so.

GMaps will continue to use the bounds of the (lack of better words) most zoomed out marker collection status (sorry). Setting to 'null' before each call for new markers gives you a fresh map to work with.

To auto-zoom in, simply set the LatLngBounds(); to 'null' like so (see pseudo example below to see its placement):

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

Pseudo example:

// map stuff/initiation
...

var bounds = new google.maps.LatLngBounds();
var gmarkers = [];

function CreateMarker (obj) {
    myLatLng = new google.maps.LatLng(obj['latitude'], obj['longitude']);
    marker = new google.maps.Marker({
        position: myLatLng,
        map: map
    });
    google.maps.event.addListener(marker, 'click', (function(marker, i) {
        return function() {
            infowindow.setContent(obj['job']);
            infowindow.open(map, marker);
        }
    })(marker, i));
    bounds.extend(myLatLng);
    gmarkers.push(marker);
}

....

// here's an AJAX method I use to grab marker coords from a database:

$.ajax({
    beforeSend: function() {
        clear_markers(gmarkers); // see below for clear_markers() function declaration
    },
    cache: false,
    data: params,
    dataType: 'json',
    timeout: 0,
    type: 'POST',
    url: '/map/get_markers.php?_=<?php echo md5(session_id() . time() . mt_rand(1,9999)); ?>',
    success: function(data) {
        if (data) {
            if (data['count'] > 0) {
                var obj;
                var results = data['results'];

                // Plot the markers
                for (r in results) {
                    if (r < (data['count'])) {
                        CreateMarker(results[r]);
                    }
                }
            }
        }
    },
    complete: function() {
        map.fitBounds(bounds);
    }
});

// clear_markers()
function clear_markers(a) {
    if (a) {
        for (i in a) {
            a[i].setMap(null);
        }
        a.length = 0;
    }
    bounds = new google.maps.LatLngBounds(null); // this is where the magic happens; setting LatLngBounds to null resets the current bounds and allows the new call for zoom in/out to be made directly against the latest markers to be plotted on the map
}
Thorley answered 24/4, 2012 at 16:13 Comment(1)
Thanks John Smith! Your suggestion was the only way I was able to get mine to zoom back in after deleting a marker. I appreciate the detail in your post.Sudatory
C
7

That's right, fitBounds only ensures that the provided bounds are visible. It doesn't zoom in to an appropriate level.

You could first call setZoom(20), then fitBounds.

Cnidoblast answered 11/10, 2010 at 16:5 Comment(1)
For anyone reading this later on, fitBounds does Zoom with current versions, panToBounds does not and matches what is described above.Thetisa
G
4

Hmm, interesting.. I use PHP to loop through all (to be) marker coordinates and calculate the values of southWest and northEast; the coords of origin are halfway between the two. If all marker coordinates are very close to each other, the zoom factor set by fitBounds is much higher (zoomed in) than the 15 used at map creation. That's why I have added that last row..

var map;

function mapInit() {
  var origin = new google.maps.LatLng(59.33344615, 18.0678188);
  var options = {
    zoom: 15,
    center: origin,
    mapTypeControlOptions: {
      mapTypeIds: [google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.HYBRID]
    },
    mapTypeId: google.maps.MapTypeId.ROADMAP
  };

  map = new google.maps.Map(document.getElementById("googlemap"), options);

  var southWest = new google.maps.LatLng(59.3308415, 18.0643054);
  var northEast = new google.maps.LatLng(59.3360508, 18.0713322);
  var bounds = new google.maps.LatLngBounds(southWest, northEast);
  map.fitBounds(bounds);

  google.maps.event.addListenerOnce(map, "idle", function() {
    if (map.getZoom() > 16) map.setZoom(16);
  });

So, either Google has reprogrammed the function since you posted the question or.. I need more information about your code.

Gooseflesh answered 4/11, 2010 at 3:4 Comment(1)
thanks only this thing worked for me tried many solutions modified little with my requirements google.maps.event.addListenerOnce(map, "idle", function() { if (map.getZoom() < parseInt(getParameterByName("zoomlevel"))) map.setZoom(parseInt(getParameterByName("zoomlevel"))); });Coachwork
P
4

What helped me was using 0 padding as the second parameter to fitBounds(bounds, padding), here is the full code sample:

function initMap() {
    var mapOptions = {
        center: new google.maps.LatLng(0, 0),
        zoom: 1,
        minZoom: 1
    };
    map = new google.maps.Map(document.getElementById('officeMap'), mapOptions);
    google.maps.event.addListenerOnce(map, 'idle', function() {
        //Map is ready
        worldViewFit(map);
    });
}
function worldViewFit(mapObj) {
    var worldBounds = new google.maps.LatLngBounds(
        new google.maps.LatLng(70.4043,-143.5291),  //Top-left
        new google.maps.LatLng(-46.11251, 163.4288)  //Bottom-right
    );
    mapObj.fitBounds(worldBounds, 0);
    var actualBounds = mapObj.getBounds();
    if(actualBounds.getSouthWest().lng() == -180 && actualBounds.getNorthEast().lng() == 180) {
        mapObj.setZoom(mapObj.getZoom()+1);
    }
}
Perfecto answered 10/7, 2018 at 16:55 Comment(1)
After spending a day of search & debug, this answer was the solution. Thank you Tom.Mayotte
C
3

I also had an issue with the map zooming way out when calling fitBounds a second time on the same map with new markers. This is the frankensolution that works for me:

// This is a stationary point tho dynamic will work too

var myLat = someLat,
    myLng = someLong;

var map = false,
    markersArray = [];

function makeOrderMap(lat, lng) { // pass in dynamic point when updating map

  var mapOptions = {
      center: new google.maps.LatLng(myLat, myLng),
      zoom: 16,
      mapTypeId: google.maps.MapTypeId.ROADMAP
  };
  deleteOverlays(); // remove any existing markers..
  if(!map) {  
    map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
  } 

  // Find the mid-point to center the map

  midLat = (parseFloat(lat) + parseFloat(myLat)) / 2;
  midLng = (parseFloat(lng) + parseFloat(myLng)) / 2;
  map.setCenter(new google.maps.LatLng(midLat, midLng));
  var newSpot = new google.maps.LatLng(lat, lng); 

  placeMarker(mapOptions.center);    
  placeMarker(newSpot);   

  // determine the distance between points for deciding the zoom level

  var dist = distHaversine(mapOptions.center, newSpot);

  var zoom = 10;
  if(dist < 1.25) {
    zoom = 15;
  } else if(dist < 2.5) {
    zoom = 14;
  } else if(dist < 5) {
    zoom = 13;
  } else if(dist < 10) {
    zoom = 12;
  } else if(dist < 20) {
    zoom = 11;
  }
  map.setZoom(zoom);  
}


rad = function(x) {return x*Math.PI/180;}

distHaversine = function(p1, p2) {
  var R = 6371; // earth's mean radius in km
  var dLat  = rad(p2.lat() - p1.lat());
  var dLong = rad(p2.lng() - p1.lng());

  var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
          Math.cos(rad(p1.lat())) * Math.cos(rad(p2.lat())) * Math.sin(dLong/2) * Math.sin(dLong/2);
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
  var d = R * c;

  return d.toFixed(3);
}

function deleteOverlays() {
    if (markersArray) {
        for (i in markersArray) {
            markersArray[i].setMap(null);
        }
    markersArray = [];    
    markersArray.length = 0;
    }    
}

function placeMarker(location, alt_icon) {

    var marker = new google.maps.Marker({
        position: location, 
        map: map
      });

    // add marker in markers array
    markersArray.push(marker);
}
Cobol answered 11/10, 2013 at 22:47 Comment(1)
Thanks, got it working from you implementation as an angular service: gist.github.com/zmijevik/047c40f4fc0bea27fd7cGlutamine
Q
2

It turns out that when you call map.getBounds() it returns the viewport with a little margin around the edges. Since this new bounds is larger than the current bounds, the map will always zoom out. I was able to solve it by avoiding using the current map's bounds altogether and maintaining a separate LatLngBounds variable. Every time I added a point I called:

markersBounds.extend(newMarkersLatLng);
map.fitBounds(markersBounds);
map.panToBounds(markersBounds);

To remove points (I was always adding them), you could make a new LatLngBounds object with the first point, then extend each remaining point to get your new bounds:

var markersBounds = new google.maps.LatLngBounds(markers[0].getPosition(),
                                                 markers[0].getPosition());
for(var i=1; i<markers.length; i++){
    markersBounds.extend(markers[i].getPosition());
}

map.fitBounds(markersBounds);
map.panToBounds(markersBounds);
Quadrisect answered 12/4, 2013 at 15:24 Comment(0)
P
1

I encountered this general problem today, thought I'd share a solution. I realise this is slightly different to your 'store locator' problem, but it does apply in many ways. In my case I have a collection of markers on the map and am adding one, and want to ensure all markers are now in view. This code checks each existing marker to see if its in the view, and along the way, builds a new bounding box that would contain them all, if required. At the end, if any have been found to not be in view, the view is reset so they all are.

(this uses Dojo's array module, simply a convenience wrapper around basic array iteration)

var origBounds = map.getBounds(),
    newBounds = new google.maps.LatLngBounds(null),
    anyFailed = false;
array.forEach(markers, function (m) {
    newBounds.extend(m.position);
    if (!origBounds.contains(m.position)) {
        anyFailed = true;
    }
});
if (anyFailed) {
    map.setCenter(newBounds.getCenter());
    map.fitBounds(newBounds);
}

One could easily modify this to only ensure the new marker is in view, by not looping and just doing a contains() check on the new marker.

Parthenogenesis answered 6/1, 2014 at 1:36 Comment(0)
T
1

Check this quote from the GMap docs on marker.setMap(null); (which is used for removing markers from a map):

Note that the above method does not delete the marker. It removes the marker from the map. If instead you wish to delete the marker, you should remove it from the map, and then set the marker itself to null.

My problem was that when I was removing markers from the map they were still stored in map.markers. So when setting bounds it is grabbing all previous markers also:

map.markers.forEach( (marker) => {
  var latlng = new google.maps.LatLng(marker.position.lat(), marker.position.lng())
  bounds.extend(latlng)
})

Console.logging bounds showed that the bounds would always accommodate all earlier bounds because of this.

My solution was to keep track of the markers in a seperate markers array/object that stores a unique key for each marker (in my case linked to a places autocomplete input field) and remove a marker from it when removing the marker from a map. This way I can run a check when generating bounds to see if the current marker in map.markers is in the (up-to-date) markers array.

map.markers.forEach( (marker) => {
  // check if map marker is in this.markers (to make sure only current)
  for (const [key, value] of Object.entries(markers)) {
    if (marker == value) {
      var latlng = new google.maps.LatLng(marker.position.lat(), marker.position.lng())
      bounds.extend(latlng)
    }
  }
})

Now my map zooms in correctly after changing the markers. If anyone can come up with any improvements to above method I would love to hear it!

Rens

Toast answered 22/10, 2020 at 10:57 Comment(0)
C
0
map.setZoom(10);

I believe the acceptable range is 1-20. Whole numbers only, decimals broke the map in my experience.

Comparative answered 15/7, 2010 at 4:18 Comment(0)
W
0
fitBounds: function(bounds, mapId)
    {
        //bounds: bounds, id: map id
        if (bounds==null) return false;
        maps[mapId].fitBounds(bounds);
    },  

This should help, i use this method and works fine, on map v2 a little bit different way.

Weingartner answered 13/3, 2011 at 14:48 Comment(0)
C
0

Make sure there are no CSS animations on the width or height of your map. The animations interfere with the zoom behaviour of fitBound()

I spent most of the day on this problem yesterday, and I encountered this StackOverflow post at least a dozen times. But I eventually had to find the problem the hard way on my own.

Deleting the transition: all 0.5s ease; line below fixed it for me.

.google-map {
  width: 100%;
  height: 0px;
  transition: all 0.5s ease;
}

.google-map.loaded {
    height: 400px;
}

Chromatin answered 23/1, 2020 at 15:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.