11

I'm trying to set bounds where you can drag the map using Google Maps API V3 Here is the solution for V2 http://econym.org.uk/gmap/example_range.htm that works pretty well.

However with API V3 it isn't so good: when you use the same checkbounds() function, the map is twitching when you reach the bound while map.setCenter() changes the center of the map.

How to fix it? What is the solution for API V3?

Ekaterina Prigara
  • 4,697
  • 3
  • 21
  • 17

7 Answers7

19

I've had the same problem, but this should sort it out (it's the same function, the event to listen to is changed from 'move' or 'drag' to 'center_changed', works like a charm!:

google.maps.event.addListener(map,'center_changed',function() { checkBounds(); });

function checkBounds() {    
    if(! allowedBounds.contains(map.getCenter())) {
      var C = map.getCenter();
      var X = C.lng();
      var Y = C.lat();

      var AmaxX = allowedBounds.getNorthEast().lng();
      var AmaxY = allowedBounds.getNorthEast().lat();
      var AminX = allowedBounds.getSouthWest().lng();
      var AminY = allowedBounds.getSouthWest().lat();

      if (X < AminX) {X = AminX;}
      if (X > AmaxX) {X = AmaxX;}
      if (Y < AminY) {Y = AminY;}
      if (Y > AmaxY) {Y = AmaxY;}

      map.setCenter(new google.maps.LatLng(Y,X));
    }
}
izb
  • 50,101
  • 39
  • 117
  • 168
sairafi
  • 222
  • 2
  • 4
  • I used this and it works awesome. Cheers! – Glen Robertson Apr 19 '11 at 04:12
  • 5
    what is `allowedBounds` set to? – travis Sep 20 '12 at 16:28
  • 1
    allowedBounds is a [LatLngBounds object](https://developers.google.com/maps/documentation/javascript/reference#LatLngBounds) (basically a pair of latitudes/longitudes). This works really well BTW, does exactly what it should for me! – strikernl Jun 13 '13 at 10:51
  • 1
    This does not take into consideration zoom level, so you can still get well outside of an intended area. – James Jun 29 '15 at 09:58
6

You might also need to consider wrapping coordinates, curve distortion, and centerizing to the bound dimensions if the map resizes or zooms in/out. This is especially required if your bounds takes up a large percentage of the entire map (eg. like a continent).

One of the problems with checkBounds() though, is that it doesn't take into account that latitude values close to the north/south poles, which have non-linear distortion which make limiting the bounds in-accurate (I use approximate magic number multipliers that won't work in all situations). By right, you should first convert the bounds to linear 2d world coordinates to see how far off the bounds it is in terms of world coordinates, than map the actual target center point in world coordinate to the target actual latitude position. For longitude values, this doesn't seem like much of issue and the linear clipping approach seems accurate enough, the main issue is with the wrapping of longitude coordinates which is accounted for (somewhat) in the below code.

// Persitant variables
var allowedBounds;  // assign something here
var lastValidCenter;  // initialize this using map.getCenter()   

function checkBounds() {  // when bounds changes due to resizing or zooming in/out

    var currentBounds = map.getBounds();
    if (currentBounds == null) return;

      var allowed_ne_lng = allowedBounds.getNorthEast().lng();
      var allowed_ne_lat = allowedBounds.getNorthEast().lat();
      var allowed_sw_lng = allowedBounds.getSouthWest().lng();
      var allowed_sw_lat = allowedBounds.getSouthWest().lat();

    var wrap;
    var cc = map.getCenter();
    var centerH = false;
    var centerV = false;

    // Check horizontal wraps and offsets
    if ( currentBounds.toSpan().lng() > allowedBounds.toSpan().lng() ) {
        centerH = true;
    }
    else {  // test positive and negative wrap respectively
        wrap = currentBounds.getNorthEast().lng() < cc.lng();
        var current_ne_lng = !wrap ?   currentBounds.getNorthEast().lng()  : allowed_ne_lng +(currentBounds.getNorthEast().lng() + 180 )  + (180 - allowed_ne_lng);
        wrap = currentBounds.getSouthWest().lng() > cc.lng();
        var current_sw_lng = !wrap ?  currentBounds.getSouthWest().lng() : allowed_sw_lng - (180-currentBounds.getSouthWest().lng()) - (allowed_sw_lng+180);
    }


    // Check vertical wraps and offsets
    if ( currentBounds.toSpan().lat() > allowedBounds.toSpan().lat() ) {
        centerV = true;
    }
    else { // test positive and negative wrap respectively
    wrap = currentBounds.getNorthEast().lat()   < cc.lat();    if (wrap) { alert("WRAp detected top") } // else alert("no wrap:"+currentBounds); wrap = false;
      var current_ne_lat =  !wrap ? currentBounds.getNorthEast().lat()  : allowed_ne_lat + (currentBounds.getNorthEast().lat() +90) + (90 - allowed_ne_lat);
      wrap = currentBounds.getSouthWest().lat() > cc.lat();  if (wrap) { alert("WRAp detected btm") } //alert("no wrap:"+currentBounds);
      var current_sw_lat = !wrap ?  currentBounds.getSouthWest().lat() : allowed_sw_lat - (90-currentBounds.getSouthWest().lat()) - (allowed_sw_lat+90);
    }


      // Finalise positions
      var centerX = cc.lng();
      var centerY = cc.lat();
     if (!centerH) {
        if (current_ne_lng > allowed_ne_lng) centerX -= current_ne_lng-allowed_ne_lng;
        if (current_sw_lng < allowed_sw_lng) centerX += allowed_sw_lng-current_sw_lng;
     }
     else {
         centerX = allowedBounds.getCenter().lng();
     }

     if (!centerV) {
       if (current_ne_lat > allowed_ne_lat) {
           centerY -= (current_ne_lat-allowed_ne_lat) * 3;  // approximation magic numbeer. Adjust as u see fit, or use a more accruate pixel measurement.
       }
       if (current_sw_lat < allowed_sw_lat) {
           centerY += (allowed_sw_lat-current_sw_lat)*2.8;  // approximation magic number
       }
     }
     else {
        centerY = allowedBounds.getCenter().lat();
     }
     map.setCenter(lastValidCenter = new google.maps.LatLng(centerY,centerX));
}



function limitBound(bound) // Occurs during dragging, pass allowedBounds to this function in most cases. Requires persistant 'lastValidCenter=map.getCenter()' var reference.
     {
        var mapBounds = map.getBounds();

         if (   mapBounds.getNorthEast().lng() >=  mapBounds.getSouthWest().lng() && mapBounds.getNorthEast().lat()  >= mapBounds.getSouthWest().lat()  // ensure no left/right, top/bottom wrapping
            && bound.getNorthEast().lat() > mapBounds.getNorthEast().lat()  // top
            && bound.getNorthEast().lng() > mapBounds.getNorthEast().lng() // right
            && bound.getSouthWest().lat() < mapBounds.getSouthWest().lat() // bottom
            && bound.getSouthWest().lng() < mapBounds.getSouthWest().lng()) // left
            {
                lastValidCenter=map.getCenter();  // valid case, set up new valid center location
            }

        //   if (bound.contains(map.getCenter()))
        // {
                map.panTo(lastValidCenter);
             //  }

         }



// Google map listeners

google.maps.event.addListener(map, 'zoom_changed', function() {
    //var zoom = map.getZoom();
    checkBounds();
});

google.maps.event.addListener(map, "bounds_changed", function() {

    checkBounds();
});

google.maps.event.addListener(map, 'center_changed', function() {
      limitBound(allowedBounds);
}); 

p.s For checkBounds(), to get proper 2d world coordinate from the map's center, given 2 lat/lng values, use map.getProjection().fromLatLngToPoint(). Compare the 2 points, find the linear difference between them, and map the difference in world coordinates back to lat/lng using map.getProjection().fromPointToLatLng(). This will give you accurate clip offsets in lat/lng units.

Glidias
  • 71
  • 1
  • 2
4

This script gets the initial bounds (allowedBounds) and limit the bounds on drag and zoom_changed. Also the zoom is limited on < 7.

var allowedBounds = false;

google.maps.event.addListener(map, 'idle', function() {
 if (!allowedBounds) {
  allowedBounds = map.getBounds();
 }
});

google.maps.event.addListener(map, 'drag', checkBounds);
google.maps.event.addListener(map, 'zoom_changed', checkBounds); 

function checkBounds() {

 if (map.getZoom() < 7) map.setZoom(7); 

 if (allowedBounds) {

  var allowed_ne_lng = allowedBounds.getNorthEast().lng();
  var allowed_ne_lat = allowedBounds.getNorthEast().lat();
  var allowed_sw_lng = allowedBounds.getSouthWest().lng();
  var allowed_sw_lat = allowedBounds.getSouthWest().lat();

  var currentBounds = map.getBounds();
  var current_ne_lng = currentBounds.getNorthEast().lng();
  var current_ne_lat = currentBounds.getNorthEast().lat();
  var current_sw_lng = currentBounds.getSouthWest().lng();
  var current_sw_lat = currentBounds.getSouthWest().lat();

  var currentCenter = map.getCenter();
  var centerX = currentCenter.lng();
  var centerY = currentCenter.lat();

  if (current_ne_lng > allowed_ne_lng) centerX = centerX-(current_ne_lng-allowed_ne_lng);
  if (current_ne_lat > allowed_ne_lat) centerY = centerY-(current_ne_lat-allowed_ne_lat);
  if (current_sw_lng < allowed_sw_lng) centerX = centerX+(allowed_sw_lng-current_sw_lng);
  if (current_sw_lat < allowed_sw_lat) centerY = centerY+(allowed_sw_lat-current_sw_lat);

  map.setCenter(new google.maps.LatLng(centerY,centerX));
 }
}
Christian Specht
  • 35,843
  • 15
  • 128
  • 182
  • This is the only answer I've found that takes into consideration the outer bounds and not just the center. Relying on center alone allows you to still pan well outside of an intended area depending on zoom level. – James Jun 29 '15 at 09:59
1

Thanks @sairafi. Your answer got me very close. I was getting an error where getBounds was undefined, so I wrapped it in another listener to make sure that the map was fully loaded first.

google.maps.event.addListenerOnce(map, 'tilesloaded', function() { 
    allowedBounds = map.getBounds();
    google.maps.event.addListener(map,'center_changed',function() { checkBounds(allowedBounds); });
});

// Limit map area
function checkBounds(allowedBounds) { 

if(!allowedBounds.contains(map.getCenter())) {
  var C = map.getCenter();
  var X = C.lng();
  var Y = C.lat();

  var AmaxX = allowedBounds.getNorthEast().lng();
  var AmaxY = allowedBounds.getNorthEast().lat();
  var AminX = allowedBounds.getSouthWest().lng();
  var AminY = allowedBounds.getSouthWest().lat();

  if (X < AminX) {X = AminX;}
  if (X > AmaxX) {X = AmaxX;}
  if (Y < AminY) {Y = AminY;}
  if (Y > AmaxY) {Y = AmaxY;}

  map.setCenter(new google.maps.LatLng(Y,X));
}
}
Devin
  • 11
  • 1
0
southWest = new google.maps.LatLng(48.59475380744011,22.247364044189453);
            northEast = new google.maps.LatLng(48.655344320891444,22.352420806884766);
            var limBound = new google.maps.LatLngBounds(southWest,northEast);
            var lastCenter;

            var option = {zoom:15,
            center: limBound.getCenter(),
            mapTypeId: google.maps.MapTypeId.ROADMAP};
            var map = new google.maps.Map(document.getElementById('divMap'),option);
            google.maps.event.addListener(map,'zoom_changed', function() {
                minZoom(15);
                });
            google.maps.event.addListener(map,'drag',function(e){
                limitBound(limBound);
                });

         function minZoom(minZoom){
                if (map.getZoom()<minZoom)
                {map.setZoom(minZoom);}
             };       

         function limitBound(bound)
         {
             if (bound.getNorthEast().lat() > map.getBounds().getNorthEast().lat()
                && bound.getNorthEast().lng() > map.getBounds().getNorthEast().lng()
                && bound.getSouthWest().lat() < map.getBounds().getSouthWest().lat()
                && bound.getSouthWest().lng() < map.getBounds().getSouthWest().lng())
                {
                    lastCenter=map.getCenter();
                    $('#divText').text(lastCenter.toString());
                    }
                if (bound.contains(map.getCenter()))
                {
                    map.setCenter(lastCenter);
                    }
             }
Daniel Haley
  • 51,389
  • 6
  • 69
  • 95
vital
  • 1
0

pls check this Google Maps API v3: Can I setZoom after fitBounds?

map.fitBounds(mapBounds);

Community
  • 1
  • 1
M.Hefny
  • 2,677
  • 1
  • 26
  • 30
0

@sairafi and @devin

thanks for both your answers. I could not get this to work in Chrome/Windows 7 because the .comtains() check evlauated to both true and false as soon as you hit the boundary.

So I changed the setCenter() at the bottom to panTo()

The second problem was that if you establish boundaries on initial load, you have to use the google.maps.event.addListenerOnce(map,'idle'...) event otherwise it keeps resetting the boundaries to the currently viewable map.

Finally I do use the drag event for tracking centers, for some reason that worked more smoothly.

The resulting code is this:

    google.maps.event.addListenerOnce(map,'idle',function() {
        allowedBounds = map.getBounds();
    });
    google.maps.event.addListener(map,'drag',function() {
        checkBounds(); 
    });
    function checkBounds() {    
        if(! allowedBounds.contains(map.getCenter()))
        {
            var C = map.getCenter();
            var X = C.lng();
            var Y = C.lat();
            var AmaxX = allowedBounds.getNorthEast().lng();
            var AmaxY = allowedBounds.getNorthEast().lat();
            var AminX = allowedBounds.getSouthWest().lng();
            var AminY = allowedBounds.getSouthWest().lat();
            if (X < AminX) {X = AminX;}
            if (X > AmaxX) {X = AmaxX;}
            if (Y < AminY) {Y = AminY;}
            if (Y > AmaxY) {Y = AmaxY;}
            map.panTo(new google.maps.LatLng(Y,X));
        } 
    }
tim
  • 3,823
  • 5
  • 34
  • 39