如何限制谷歌地图 API V3中的平移?

在 V2中,有一种限制平移/拖动的方法,这样地图就会保持在一定的范围内。在 V3中是怎么做到的?

假设我希望用户只关注欧洲。我已经限制了缩放,但是如果我允许拖动(在这种情况下,由于其他原因,我必须这样做) ,那么用户就可以在我想要显示的区域之外进行平移。

请给工作的例子或代码片段-我不是一个专家编码..。

62217 次浏览

The trick is to listen to the dragend event, and if the map is dragged outside the allowed bounds, move it back inside. If you define your allowed bounds as a LatLngBounds object, you can use the contains() method, since it returns true if the given lat/lng argument is within the bounds.

It is also important to limit the zoom level, but it seems you are already doing this.

Therefore, you may want to try the following example:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<title>Google Maps JavaScript API v3 Example: Limit Panning</title>
<script type="text/javascript"
src="http://maps.google.com/maps/api/js?sensor=false"></script>
</head>
<body>
<div id="map" style="width: 400px; height: 300px;"></div>


<script type="text/javascript">


var minZoomLevel = 5;


var map = new google.maps.Map(document.getElementById('map'), {
zoom: minZoomLevel,
center: new google.maps.LatLng(38.50, -90.50),
mapTypeId: google.maps.MapTypeId.ROADMAP
});


// Bounds for North America
var allowedBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(28.70, -127.50),
new google.maps.LatLng(48.85, -55.90));


// Listen for the dragend event
google.maps.event.addListener(map, 'dragend', function() {
if (allowedBounds.contains(map.getCenter())) return;


// Out of bounds - Move the map back within the bounds


var c = map.getCenter(),
x = c.lng(),
y = c.lat(),
maxX = allowedBounds.getNorthEast().lng(),
maxY = allowedBounds.getNorthEast().lat(),
minX = allowedBounds.getSouthWest().lng(),
minY = allowedBounds.getSouthWest().lat();


if (x < minX) x = minX;
if (x > maxX) x = maxX;
if (y < minY) y = minY;
if (y > maxY) y = maxY;


map.setCenter(new google.maps.LatLng(y, x));
});


// Limit the zoom level
google.maps.event.addListener(map, 'zoom_changed', function() {
if (map.getZoom() < minZoomLevel) map.setZoom(minZoomLevel);
});


</script>
</body>
</html>

Screenshot from the above example. The user will not be able to drag further south or far east in this case:

Google Maps JavaScript API v3 Example: Limit Panning

Here is a nice extension to the above that will reset the Map's center to the last valid position by listening to the dragstart event.

// Limit panning
var lastCenter = map.getCenter();


google.maps.event.addListener(map, 'dragstart', function() {
lastCenter = map.getCenter();
});


google.maps.event.addListener(map, 'dragend', function() {
if(allowedBounds.contains(map.getCenter())) return;


map.setCenter(lastCenter);
});

I guess I'm a little bit late to the party, but since this was exactly what I needed just now AND I improved on it, I thought I'd post an answer anyway.

With both the answers of Daniel Vassallo and brendo, the user can still use the pan-control (if it's activated) to move away from the wanted area. The thing @Yauhen.F mentioned in a comment.

So instead of using the dragend event, I use the center_changed event. This is continuously fired during dragging and every time someone uses the pan control.

// bounds of the desired area
var allowedBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(70.33956792419954, 178.01171875),
new google.maps.LatLng(83.86483689701898, -88.033203125)
);
var lastValidCenter = map.getCenter();


google.maps.event.addListener(map, 'center_changed', function() {
if (allowedBounds.contains(map.getCenter())) {
// still within valid bounds, so save the last valid position
lastValidCenter = map.getCenter();
return;
}


// not valid anymore => return to last valid position
map.panTo(lastValidCenter);
});

By saving the last valid position continuously during the dragging, the movement will just stop once it's out of bounds, instead of yerking back once the dragging ended. ......

There is another thread on the subject that is also very good. The problem I had to solve was that instead of setting boundaries manually and checking center containment, I wanted a boundary set on page load, then allow dragging to the edge if zoomed in.

So I set panning boundaries on map load, once. Then I check if map is still at max zoom and if so, return the initial center. If zoomed in, I want to pan to the EDGE of initial boundaries, not just check if CENTER contained, because that would extend the allowed panning by half the viewport.

Unfortunately, although this gets the job done and works fine when paning slowly, it's a bit jerky if you pan quickly.

If you have suggestions on how this can be avoided, I'd be grateful.

    map = new google.maps.Map( // defaults
document.getElementById("map22"),
{
disableDefaultUI        : true,
zoomControl             : true,
zoom                    : 7,
minZoom                 : 7,
maxZoom                 : 10,
center                  : new google.maps.LatLng(
64.99473104134819,
-19.22332763671875
),
mapTypeId               : google.maps.MapTypeId.ROADMAP
}
);






function borders(){
return {
maxLat : map.getBounds().getNorthEast().lat(),
maxLng : map.getBounds().getNorthEast().lng(),
minLat : map.getBounds().getSouthWest().lat(),
minLng : map.getBounds().getSouthWest().lng(),
center : map.getCenter()
}
}


google.maps.event.addListenerOnce(map,'idle',function() {
limit = borders();
});


google.maps.event.addListener(map,'drag',function() {
if(map.getZoom() == 7) return map.setCenter(limit.center);
current = borders();
if( current.maxLng < limit.maxLng && current.minLng > limit.minLng ) activeCenterLng = current.center.lng();
if( current.maxLat < limit.maxLat && current.minLat > limit.minLat ) activeCenterLat = current.center.lat();
map.setCenter(
new google.maps.LatLng(
activeCenterLat,
activeCenterLng
)
);
});

When I'm using drag or dragend or whatever, the map jumps back into allowed bounds instead of simply restricting overflowing movement. Just change the event to 'center_changed' to stop it from jumping around like that.

Modified jsfiddle: http://jsfiddle.net/Vjdde/1/

Edit: Not sure why the fiddle doesn't produce a stack overflow but it should, since setCenter will call center_changed again.. Just watch out

I tried the answer from HenningJ and the map wouldn't stop panning until the center was in a corner of the bounds which was not ideal. Here is my solution:

google.maps.event.addListener(map, 'center_changed', function() {
var mapBounds = map.getBounds();
if(allowedBounds.contains(mapBounds.getNorthEast()) && allowedBounds.contains(mapBounds.getSouthWest())) {
lastCenter = map.getCenter();
return;
}


map.panTo(lastCenter);
}, this));

My version, based on the one from @HenningJ, but with some modification of the lastValidCenter to allow smooth dragging along the edges of the bounds.

<!DOCTYPE html>
<html>
<head>
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0; padding: 0 }
#map-canvas { height: 100% }
</style>
<script type="text/javascript"
src="http://maps.google.com/maps/api/js?sensor=false"></script>
</script>
<script type="text/javascript">
function initialize() {
var mapOptions = {
center: new google.maps.LatLng(28.70, -127.50),
zoom: 4,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("map-canvas"),
mapOptions);


// bounds of the desired area
var allowedBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(28.70, -127.50),
new google.maps.LatLng(48.85, -55.90)
);
var boundLimits = {
maxLat : allowedBounds.getNorthEast().lat(),
maxLng : allowedBounds.getNorthEast().lng(),
minLat : allowedBounds.getSouthWest().lat(),
minLng : allowedBounds.getSouthWest().lng()
};


var lastValidCenter = map.getCenter();
var newLat, newLng;
google.maps.event.addListener(map, 'center_changed', function() {
center = map.getCenter();
if (allowedBounds.contains(center)) {
// still within valid bounds, so save the last valid position
lastValidCenter = map.getCenter();
return;
}
newLat = lastValidCenter.lat();
newLng = lastValidCenter.lng();
if(center.lng() > boundLimits.minLng && center.lng() < boundLimits.maxLng){
newLng = center.lng();
}
if(center.lat() > boundLimits.minLat && center.lat() < boundLimits.maxLat){
newLat = center.lat();
}
map.panTo(new google.maps.LatLng(newLat, newLng));
});
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="map-canvas"/>
</body>
</html>

Fiddle here: http://jsfiddle.net/koenpunt/n7h6t/

The solutions here left me with 2 issues. First of all, if you held down the arrow key so that it started panning rapidly, when it hit the edge, it wouldn't go all the way to the edge because due to the panning acceleration taking large "steps", the next "step" would have gone outside the bounds, so it doesn't take that last "step". So once it stopped, you could release the arrow key and then press it again, and it would pan just a little bit further. Second, these solutions didn't properly contain the panning after a zoom change. I managed to solve both, and posted my solution in another thread, but I thought I'd link it here, since this was the thread that first got me started in the right direction.

The best method to restrict is, set the zoom level and center point and disable the controls like zoom, scroll etc like below.

 var latlng = new google.maps.LatLng(18.283078,84.047556);
var myOptions = {
zoom: 12,


center: latlng,
zoomControl: false,
mapTypeId: google.maps.MapTypeId.ROADMAP,
scrollwheel: false,
navigationControl: false,
mapTypeControl: false,
scaleControl: false,
draggable: false,
disableDoubleClickZoom: true,
};
map = new google.maps.Map(document.getElementById("map_canvas"),   myOptions);

This is HenningJ - but HenningJ on ios uses lastValidCentre - which is not good, as its old, so I clamp, which makes it better on iOS.

var mapObject = plugin_map.gmap3('get');
var allowedBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(41.3957556, -88.4345472),
new google.maps.LatLng(49.4010417, -78.4286389)
);
google.maps.event.addListener(mapObject, 'center_changed', function() {
if (allowedBounds.contains(mapObject.getCenter())) {
// still within valid bounds, so save the last valid position
return;
}


// not valid anymore => return to last valid position
var c = mapObject.getCenter(),
x = c.lng(),
y = c.lat(),
maxX = allowedBounds.getNorthEast().lng(),
maxY = allowedBounds.getNorthEast().lat(),
minX = allowedBounds.getSouthWest().lng(),
minY = allowedBounds.getSouthWest().lat();


if (x < minX) x = minX;
if (x > maxX) x = maxX;
if (y < minY) y = minY;
if (y > maxY) y = maxY;


//mapObject.setCenter(new google.maps.LatLng(y, x));
mapObject.panTo(new google.maps.LatLng(y, x));
});

Here's a solution which is a merge together of Tom Andersen's answer and the currently accepted HenningJ answer. The benefits of this is it 1) allows for smoother scrolling along edges (which HenningJ's solution seemed clunky with), and 2) doesn't have any issues when zooming in an out of an area (again HenningJ's answer seemed to break when zooming in and out near the boundaries).

Tom's answer was close to working for me, except it positioned the locked off area into the center of the screen, which wasn't acceptable for the application I was working on.

// bounds of the desired area
var allowedBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(70.33956792419954, 178.01171875),
new google.maps.LatLng(83.86483689701898, -88.033203125)
);
var lastValidCenter = map.getCenter();


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


var mapBounds = map.getBounds();
var mapNe = mapBounds.getNorthEast();
var mapSw = mapBounds.getSouthWest();
var center = map.getCenter();


if( allowedBounds.contains(mapNe) && allowedBounds.contains(mapSw) ) {
//the user is scrolling within the bounds.
lastValidCenter = center;
return;
}


//the user has scrolled beyond the edge.


var mapWidth = mapNe.lng() - mapSw.lng();
var mapHeight = mapNe.lat() - mapSw.lat();


var x = center.lng();
var y = center.lat();


var maxX = allowedBounds.getNorthEast().lng();
var maxY = allowedBounds.getNorthEast().lat();
var minX = allowedBounds.getSouthWest().lng();
var minY = allowedBounds.getSouthWest().lat();


//shift the min and max dimensions by 1/2 of the screen size, so the bounds remain at the edge of the screen


maxX -= (mapWidth / 2);
minX += (mapWidth / 2);


maxY -= (mapHeight / 2);
minY += (mapHeight / 2);




if (x < minX) {
x = minX;
}
if (x > maxX) {
x = maxX;
}
if (y < minY){
y = minY;
}
if (y > maxY){
y = maxY;
}


map.panTo(new google.maps.LatLng(y, x));


});

I'll post my answer in case anyone's interested because I couldn't achieve what I needed with any of the other solutions posted here.

What I needed was to restrict the vertical bounds (latitude) of the map so that the user would not be able to pan beyond the latitude bounds of the earth (~ +/-85 degrees), but any other bounds would work too.

This approach uses the same center_changed event as described elsewhere and simply fixes the center in case parts of the prohibited bounds are shown.

This mechanism only works if the minimum zoom of the map is set so that zooming out can never show more area than that within the allowed bounds.

Working example: http://jsbin.com/vizihe

function initMap() {
// sample bounds, can be anything and goes hand in hand with minZoom
var northBoundary = 40
var southBoundary = -40


var map = new google.maps.Map(document.getElementById('map'), {
center: {lat: 0, lng: 0},
zoom: 4,
// it's important to set this to a large enough value
// so that zooming out does not show an area larger than allowed
minZoom: 4
})


map.addListener('center_changed', function () {
var bounds = map.getBounds();
var ne = bounds.getNorthEast()
var sw = bounds.getSouthWest()
var center = map.getCenter()


if(ne.lat() > northBoundary) {
map.setCenter({lat: center.lat() - (ne.lat() - northBoundary), lng: center.lng()})
}


if(sw.lat() < southBoundary) {
map.setCenter({lat: center.lat() - (sw.lat() - southBoundary), lng: center.lng()})
}
})
}
html, body, #map {
height: 100%;
margin: 0;
padding: 0;
}
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="limit google map panning">
<title>Simple Map</title>
<meta name="viewport" content="initial-scale=1.0">
<meta charset="utf-8">
</head>
<body>
<div id="map"></div>
<script src="https://maps.googleapis.com/maps/api/js?callback=initMap"
async defer></script>
</body>
</html>

I know I am little late to the party, but it seems that as of middle 2016, there is no official way to restrict viewable area.

There are some solutions to restrict the bounds (some of them in this question) but for me they have a flaw though, because they don't restrict the bounds exactly to fit the map view, they only restrict the map center be contained within the specified bounds. If you want to restrict the bounds to overlaying image like me, this can result in a behavior like illustrated below, where the underlaying map is visible under our image overlay:

enter image description here

To tackle this issue, I have created a library, which successfully restrict the bounds so you cannot pan out of the overlay.

However, as other existing solutions, it has a "vibrating" issue. When the user pans the map aggressively enough, after they release the left mouse button, the map still continues panning by itself, gradually slowing. I always return the map back to the bounds, but that results in kind of vibrating, which settles after a moment. This panning effect cannot be stopped with any means provided by the Js API at the moment. It seems that until google adds support for something like map.stopPanningAnimation() we won't be able to create a smooth experience.

Example using the mentioned library, the smoothest strict bounds experience I was able to get:

function initialise(){
  

var myOptions = {
zoom: 5,
center: new google.maps.LatLng(0,0),
mapTypeId: google.maps.MapTypeId.ROADMAP,
};
var map = new google.maps.Map(document.getElementById('map'), myOptions);
  

addStrictBoundsImage(map);
}


function addStrictBoundsImage(map){
var bounds = new google.maps.LatLngBounds(
new google.maps.LatLng(62.281819, -150.287132),
new google.maps.LatLng(62.400471, -150.005608));


var image_src = 'https://developers.google.com/maps/documentation/' +
'javascript/examples/full/images/talkeetna.png';


var strict_bounds_image = new StrictBoundsImage(bounds, image_src, map);
}
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
google.load("maps", "3",{other_params:"sensor=false"});
</script>
<body style="margin:0px; padding:0px;" onload="initialise()">
<div id="map" style="height:500px; width:1000px;"></div>
<script  type="text/javascript"src="https://raw.githubusercontent.com/matej-pavla/StrictBoundsImage/master/StrictBoundsImage.js"></script>
</body>

Here's a simple solution that will work across mobile and desktop. It will stop the map panning beyond the world's maximum or minimum latitude, and allows a minimum zoom level to be set, which can help to prevent grey areas becoming visible through zooming too far out (depending on the size you set for your map):

(I recommend caution in using the center_changed event as suggested in the accepted answer by HenningJ. In my case the number of events this creates caused stack overflow errors in Google Chrome. Instead, the 'dragend' event can be used - although this will allow the user to drag outside of the areas, and will then immediately 'snap back' to a valid area of the map).

var lastValidCenter;
var minZoomLevel = 2;


setOutOfBoundsListener();


function setOutOfBoundsListener() {
google.maps.event.addListener(map, 'dragend', function () {
checkLatitude(map);
});
google.maps.event.addListener(map, 'idle', function () {
checkLatitude(map);
});
google.maps.event.addListener(map, 'zoom_changed', function () {
checkLatitude(map);
});
};


function checkLatitude(map) {
if (this.minZoomLevel) {
if (map.getZoom() < minZoomLevel) {
map.setZoom(parseInt(minZoomLevel));
}
}


var bounds = map.getBounds();
var sLat = map.getBounds().getSouthWest().lat();
var nLat = map.getBounds().getNorthEast().lat();
if (sLat < -85 || nLat > 85) {
//the map has gone beyone the world's max or min latitude - gray areas are visible
//return to a valid position
if (this.lastValidCenter) {
map.setCenter(this.lastValidCenter);
}
}
else {
this.lastValidCenter = map.getCenter();
}
}

The new version of google map API now provides a way to restrict boundary. Just specify your restriction boundaries when you initialize the map. For example if you want to restrict boundary to Newzeland only, this is how you do it:

  <body>
<div id="map"></div>
<script>
var map;
var NEW_ZEALAND_BOUNDS = {
north: -34.36,
south: -47.35,
west: 166.28,
east: -175.81,
};
var AUCKLAND = {lat: -37.06, lng: 174.58};


function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
center: AUCKLAND,
restriction: {
latLngBounds: NEW_ZEALAND_BOUNDS,
strictBounds: false,
},
zoom: 7,
});
}
</script>
<script async defer
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap">
</script>
</body>

FYI: To restrict the map of the whole world you can use the following bounds. The tricky part here is to use strictBounds:true. This will make sure that the user can not zoom out of the world view:

map = new google.maps.Map(document.getElementById('map'), {
restriction: {
latLngBounds: {
north: 85,
south: -85,
west: -180,
east: 180
},
strictBounds: true,
},
zoom: 7,
});