Dynamic google map with custom tiles prevent repeating pan - google-maps

I have a dynamic tile set where I do NOT want to allow panning outside of its bounds.
The below code gets me close, but the user can still scroll horizontally outside of strict bounds because it uses the map center for comparison
var strictBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(sw_lat, sw_lon),
new google.maps.LatLng(ne_lat, ne_lon)
);
google.maps.event.addListener(map, 'drag', function()
{
if (strictBounds.contains(map.getCenter())) return;
// We're out of bounds - Move the map back within the bounds
var c = map.getCenter(),
x = c.lng(),
y = c.lat(),
maxX = strictBounds.getNorthEast().lng(),
maxY = strictBounds.getNorthEast().lat(),
minX = strictBounds.getSouthWest().lng(),
minY = strictBounds.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));
});

For quick access, here is the jsfiddle for this solution: http://jsfiddle.net/nYz6k/
Supposed you're happy with the current solution of detecting bounds against the center, all you have to do is to restrict the bounds based on the current map canvas size.
The result that you're getting now is that every corner of the restricting bounds appears on the center of the map when it's full restricting (i.e. both latitude and longitude exceed the bounds corners).
The solution is to actually remove the x and y offset from the bounds, and still be able to check against the center (which is the most efficient solution, compared to other bounds-based checking solutions).
Also, you have to restrict the bounds only when you initialize the map and when the window is resizing, which means that when you pan there is no extra processing overhead besides the check method that you have already provided.
Don't forget to set the maxZoom property for the map (tweak it as needed) because above a determined zoom level the restricting bounds will by themselves fit in the viewport and there's no solution for that.
Important! Use 'center_changed' instead of 'drag' because 'drag' has that sliding behavior that when you finished dragging and set the center, the maps still slides in the direction of the panning.
Before you implement the solution I recommend you to check out how the Maps API coordinate system and projection works, because this solution is heavily based on it, and if you want to tweak it out, it's useful to know this information.
https://developers.google.com/maps/documentation/javascript/maptypes and check out the Custom Map Types -> Map Coordinates section.
Here's what you need to do. First, you need to implement 2 short methods of converting between geographic coordinates (LatLng) and pixel coordinates.
var fromLatLngToPixel = function (latLng) {
var point = map.getProjection().fromLatLngToPoint(latLng);
var zoom = map.getZoom();
return new google.maps.Point(
Math.floor(point.x * Math.pow(2, zoom)),
Math.floor(point.y * Math.pow(2, zoom))
);
}
var fromPixelToLatLng = function (pixel) {
var zoom = map.getZoom();
var point = new google.maps.Point(
pixel.x / Math.pow(2, zoom),
pixel.y / Math.pow(2, zoom)
);
return map.getProjection().fromPointToLatLng(point);
}
Next, implement the method that effectively restricts the bounds. Note that you always have to keep a variable that stores the original bounds, because each time the map canvas is resized, the resulting bounds will change.
For this, let's say originalBounds keeps the bounds you provided with sw_lat, sw_long etc, and shrinkedBounds is modified by this method. You can rename it to strictBounds to still work in your method, but it's up to you. This uses jQuery for getting the width and height of the canvas object.
var shrinkBounds = function () {
zoom = map.getZoom();
// The x and y offset will always be half the current map canvas
// width and height respectively
xoffset = $('#map_canvas').width() / 2;
yoffset = $('#map_canvas').height() / 2;
// Convert the bounds extremities to global pixel coordinates
var pixswOriginal = fromLatLngToPixel(originalBounds.getSouthWest());
var pixneOriginal = fromLatLngToPixel(originalBounds.getNorthEast());
// Shrink the original bounds with the x and y offset
var pixswShrinked = new google.maps.Point(pixswOriginal.x + xoffset, pixswOriginal.y - yoffset);
var pixneShrinked = new google.maps.Point(pixneOriginal.x - xoffset, pixneOriginal.y + yoffset);
// Rebuild the shrinkedBounds object with the modified
shrinkedBounds = new google.maps.LatLngBounds(
fromPixelToLatLng(pixswShrinked),
fromPixelToLatLng(pixneShrinked));
}
Next, all you have to do is to call this method:
once when the map is initialized. Be aware that depending on when you call the method, you may be getting some weird errors because the map might not have yet initialized all it's properties. Best way is to use the projection_changed event.
google.maps.event.addListener(map, 'projection_changed', function (e) {
shrinkBounds();
});
every time the map canvas is resized
google.maps.event.addListener(map, 'resize', function (e) {
shrinkBounds();
});
But the most important part of this is that the 'resize' event is never triggered for programmatic resizes of the map container, so you have to trigger it manually every time you resize the canvas programmatically (if you do, but I doubt you do).
The most common way is to trigger it when the window is resized:
$(window).resize(function () {
google.maps.event.trigger(map, 'resize');
});
After all of this, you can now safely use your method, but as a handler for 'center_changed' and not for 'drag' as I've mentioned earlier.
I set up a jsfiddle with the full working code.
http://jsfiddle.net/nYz6k/
You can see the restricting at the bottom left corner with that little half-marker that shows up, which is positioned in the SW corner of the bounds. There is also one at the NW corner but naturally you can't see it because it's displayed above the position.

Instead of using map.getCenter() as a check, use map.getBounds()
Untested, but a simple way to accomplish this would be to check if the union of both bounds were identical (if not, your map bounds is outside your strict bounds):
var union = strictBounds.union(map.getBounds());
if (strictBounds.equals(union)) { //new map bounds is within strict bounds

Tiborg version above is the best version so far in keeping the bounds...
I noticed a bug that occurs when zoom level and width/height are larger than original bounds
I have modified his code and added a rectangle to see it in action.
var buildBounds = function(sw, ne) {
var swY = sw.lat();
var swX = sw.lng();
var neY = ne.lat();
var neX = ne.lng();
if (swY > neY) {
var cY = (swY + neY) / 2;
swY = cY;
neY = cY;
}
if (swX > neX) {
var cX = (swX + neX) / 2;
swX = cX;
neX = cX;
}
return new google.maps.LatLngBounds(
new google.maps.LatLng(swY, swX),
new google.maps.LatLng(neY, neX));
}
http://jsfiddle.net/p4wgjs6s/

Related

Convert (x, y) pixel coordinates in google.maps.Point

I am trying to find out the LatLng based on my x,y pixel coordinates (and of course map options, such as zoom and center).
In order to do so, I posted another question and someone came up with this solution, from this post:
/**
* #param {google.maps.Map} map
* #param {google.maps.Point} point
* #param {int} z
* #return {google.maps.LatLng}
*/
var pointToLatlng = function(map, point, z){
var scale = Math.pow(2, z);
var normalizedPoint = new google.maps.Point(point.x / scale, point.y / scale);
var latlng = map.getProjection().fromPointToLatLng(normalizedPoint);
return latlng;
};
As you can notice from the code sample, the function uses as argument a google.maps.Point, therefore I need to convert my screen pixel coordinate into a google.maps.Point and I have no clue how, since their documentation of the API is not quite verbose...
Can you please help me? Or am I missing something on the way?
After some research and some fails I came up with a solution.
Following the documentation from this link I found out that the google Points are computed in the range of x:[0-256], y:[0-256] (a tile being 256x256 pixels) and the (0,0) point being the leftmost point of the map (check the link for more information).
However, my approach is as it follows:
having the x and y coordinates (which are coordinates on the screen - on the map) I computed the percentage where the x and y coordinates were placed in response to the div containing the map (in my case, the hole window)
computed the NortEast and SouthWest LatLng bounds of the (visible) map
converted the bounds in google Points
computed the new lat and lng, in google points, with the help of the boundaries and percentage of x and y
// retrieve the lat lng for the far extremities of the (visible) map
var latLngBounds = map.getBounds();
var neBound = latLngBounds.getNorthEast();
var swBound = latLngBounds.getSouthWest();
// convert the bounds in pixels
var neBoundInPx = map.getProjection().fromLatLngToPoint(neBound);
var swBoundInPx = map.getProjection().fromLatLngToPoint(swBound);
// compute the percent of x and y coordinates related to the div containing the map; in my case the screen
var procX = x/window.innerWidth;
var procY = y/window.innerHeight;
// compute new coordinates in pixels for lat and lng;
// for lng : subtract from the right edge of the container the left edge,
// multiply it by the percentage where the x coordinate was on the screen
// related to the container in which the map is placed and add back the left boundary
// you should now have the Lng coordinate in pixels
// do the same for lat
var newLngInPx = (neBoundInPx.x - swBoundInPx.x) * procX + swBoundInPx.x;
var newLatInPx = (swBoundInPx.y - neBoundInPx.y) * procY + neBoundInPx.y;
var finalResult = new google.maps.Point(newLngInPx, newLatInPx);
You could use an overlay that draws nothing in order to get at the super valuable function, fromContainerPixelToLatLng()
var overlay = new google.maps.OverlayView();
overlay.draw = function() {}; // empty function required
overlay.setMap(map);
var coordinates = overlay.getProjection().fromContainerPixelToLatLng(
new google.maps.Point(x, y)
);
console.log(coordinates.lat + ", " + coordinates.lng);

Is it possible to center a map at a fixed position (lat lng) - 100px?

I mean, I'd like to center a map to a fixed position :
map.setCenter(markerCliccato.getPosition());
and less (Y-axis) 100px down. Is it possible? Or I need the Lat/Lng of this "virtual" point?
This is because I have a InfoWindow very height, and I need to put it 100px from top of the map! Can't use "margin" on infowindow because can make troubles with other marker in the map.
Example
I have this point as Lat/Lng : https://maps.google.com/?q=54.572062,-3.038818
But I want to center the map at https://maps.google.com/?q=54.486793,-3.042046 (which is more or less 100px down of my point, in the map)
ok, i need to post pictures :)
this is before the click
and this is after
there is no gray area, the map is all rendered.
also, to see the marker you must allow the site to track your location.
function offsetCenter(latlng, offsetx, offsety) {
var scale = Math.pow(2, map.getZoom());
var nw = new google.maps.LatLng(
map.getBounds().getNorthEast().lat(), map.getBounds().getSouthWest().lng());
var worldCoordinateCenter = map.getProjection().fromLatLngToPoint(latlng);
var pixelOffset = new google.maps.Point((offsetx / scale) || 0, (offsety / scale) || 0)
var worldCoordinateNewCenter = new google.maps.Point(
worldCoordinateCenter.x - pixelOffset.x, worldCoordinateCenter.y + pixelOffset.y);
var newCenter = map.getProjection().fromPointToLatLng(worldCoordinateNewCenter);
map.setCenter(newCenter);
}
see it working here: http://jsfiddle.net/RASG/vA4eQ/
tested with FF 15, IE 9 and Chrome 21

How to make fitBounds aware of custom controls

I have a large (300*500px) custom control on the left side of my google map. I'm clustering my markers together. When a user clicks on a marker, I want to zoom the map in to show the markers in that cluster.
The problem is:
When I get the bounds of my marker collection, then map.fitBounds(collection_bounds), I end up with markers underneath my large control. Is there a way to prevent fitBounds from using the whole view port?
I have tried getting the LatLng of my south west bounds point, converting that to pixels, moving that 300px in, then converting that back to a LatLng to use as the new south west bounds point. This doesn't work though because the calculations are done before the zoom, so the 300px shift ends up being too much... I thought about writing my own fitBounds, but I hit the same issue, in that it's done before the zoom.
What you said works:
I have tried getting the LatLng of my south west bounds point,
converting that to pixels, moving that 300px in, then converting that
back to a LatLng to use as the new south west bounds point.
if you do it in two steps, which is pretty much transparent to the user because it is executed so fast that you hardly notice it. So, first you do a normal map.fitBounds(bounds); where bounds is only defined by your markers, and then you re-adjust with the technique you described. So:
google.maps.event.addListenerOnce(map,'bounds_changed',function(){
// re-adjust bounds here as you described.
// This event fires only once and then the handler removes itself.
});
map.fitBounds(bounds);
I had a 400pixel wide panel on the right hand size.
Starting from a call to zoomWithPoints passing two points to include in view, the following code implements the approach others have described: 1) Zoom to bounds 2) Using the resulting zoom level calculate how much to add onto the 'maxLon' 3) Zoom to bounds again.
function zoomToBbox(minLat, minLon, maxLat, maxLon) {
var southwest = new google.maps.LatLng(minLat, minLon);
var northeast = new google.maps.LatLng(maxLat, maxLon);
var bounds = new google.maps.LatLngBounds(southwest, northeast);
map.fitBounds(bounds);
}
function zoomWithPoints(lat1, lon1, lat2, lon2) {
var maxLat = Math.max(lat1, lat2);
var maxLon = Math.max(lon1, lon2);
var minLat = Math.min(lat1, lat2);
var minLon = Math.min(lon1, lon2);
zoomToBbox(minLat, minLon, maxLat, maxLon);
var z = map.getZoom(); //get the zoom level after zooming
// Add a bit to maxLon, to result in it panning left by 400 pixels
var widthOfPanel = 400;
var degreesPerTile = 360 / (Math.pow(2,z));
var degreesPerPx = degreesPerTile / 256;
var degreesForPanel = degreesPerPx * widthOfPanel;
maxLon = maxLon + degreesForPanel;
//zoom (pan across a bit) to take account of the added bit
zoomToBbox(minLat, minLon, maxLat, maxLon);
map.setZoom(z); //put it back to right zoom level if necessary
}
...call zoomWithPoints

Google Maps API 3 fitBounds padding - ensure markers are not obscured by overlaid controls

I'd like to be able add padding to a map view after calling a map.fitBounds(), so all markers can be visible regardless of map controls or things like sliding panels that would cover markers when opened. Leaftlet has an option to add padding to fitBounds, but Google Maps does not.
Sometimes the northmost markers partially hide above the viewport. The westmost markers also often lay under the zoom slider. With API 2 it was possible to form a virtual viewport by reducing given paddings from the map viewport and then call the method showBounds() to calculate and perform zooming and centering based on that virtual viewport:
map.showBounds(bounds, {top:30,right:10,left:50});
A working example of this for API 2 can be found here under the showBounds() example link.
I cannot find similar functionality in API V3, but hopefully there is another way this can be accomplished. Maybe I could grab the northeast and southwest points, then add fake coordinates to extend the bounds further after including them?
UPDATE
(Codepen in case the code below doesn't work)
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
draggable: true,
streetViewControl: false,
zoomControl: false
});
var marker1 = new google.maps.Marker({
position: {lat: 37, lng: -121},
map: map,
});
var marker2 = new google.maps.Marker({
position: {lat: 39.3, lng: -122},
map: map,
});
var bounds = new google.maps.LatLngBounds();
bounds.extend(marker1.position);
bounds.extend(marker2.position);
map.fitBounds(bounds);
}
#map {
height: 640px;
width: 360px;
}
#overlays {
position: absolute;
height: 50px;
width: 340px;
background: white;
margin: -80px 10px;
text-align: center;
line-height: 50px;
}
/* Optional: Makes the sample page fill the window. */
html, body {
height: 100%;
margin: 0;
padding: 0;
}
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<title>Simple markers</title>
</head>
<body>
<div id="map"></div>
<div id="overlays">Controls / Order pizza / ETA / etc.</div>
<script async defer
src="https://maps.googleapis.com/maps/api/js?&callback=initMap">
</script>
</body>
</html>
The problem is this:
I've tried adding a control as documented at Custom controls, but the map isn't exactly aware of it - see this fiddle forked from the Maps custom control example. One of the markers is still obscured by the control.
This is some kind of a hack-ish solution, but after the fitBounds, you could zoom one level out, so you get enough padding for your markers.
Assume map variable is your reference to the map object;
map.setZoom(map.getZoom() - 1);
As of June 2017 the Maps JavaScript API is supporting the padding parameter in the fitBounds() method.
fitBounds(bounds:LatLngBounds|LatLngBoundsLiteral, padding?:number)
Please refer to the documentation for further details
https://developers.google.com/maps/documentation/javascript/reference#Map
You can use map.fitBounds() with API V3 with the same padding syntax as you mentioned with map.showBounds().
Simply using map.fitBounds(bounds, {top:30,right:10,left:50}); worked for me.
(this could be comment to xomena's or Roman86' post, I don't have enough reputation to comment)
I solved this problem by extended the map bounds to include a latlng that sufficiently pushed the markers into view.
Firstly you need to create an overlay view
var overlayHelper = new google.maps.OverlayView();
overlayHelper.onAdd = function() {};
overlayHelper.onRemove = function() {};
overlayHelper.draw = function() {};
overlayHelper.setMap(map);
Once you have an overlay helper you need to get the map projection and perform calcs based on that.
Note that the control that I have on my map is a 420 pixel wide, 100% height div on the far right of the map. You will obviously need to change the code to accomodate your controls.
var mapCanvas = $("#map_canvas"),
controlLeft = mapCanvas.width() - 420, // canvas width minus width of the overlayed control
projection = overlayHelper.getProjection(),
widestPoint = 0,
latlng,
point;
// the markers were created elsewhere and already extended the bounds on creation
map.fitBounds(mapBounds);
// check if any markers are hidden behind the overlayed control
for (var m in markers) {
point = projection.fromLatLngToContainerPixel(markers[m].getPosition());
if (point.x > controlLeft && point.x > widestPoint) {
widestPoint = point.x;
}
}
if (widestPoint > 0) {
point = new google.maps.Point(
mapCanvas.width() + (widestPoint - controlLeft),
mapCanvas.height() / 2); // middle of map height, since we only want to reposition bounds to the left and not up and down
latlng = projection.fromContainerPixelToLatLng(point);
mapBounds.extend(latlng);
map.fitBounds(mapBounds);
}
If you're doing this when the map loads for the first time, then you will need to wrap this in a map event to wait for idle. This allows the overlay view to initialize. Don't include the overlay helper creation within the event callback.
google.maps.event.addListenerOnce(map, 'idle', function() { <above code> });
Updated
Google Maps API now supports a native "padding" param in the fitBounds method (from version 3.32, correct me if earlier).
I had no chance yet to test it, but if you're able to upgrade - I would recommend to use a native way. If you're using version < 3.32 and can't upgrade - my solution is for you.
I took working solution by erzzo and improved it a little bit.
Example
fitBoundsWithPadding(googleMapInstance, PolygonLatLngBounds, {left:250, bottom:10});
Arguments description:
gMap - google map instance
bounds - google maps LatLngBounds object to fit
paddingXY - Object Literal: 2 possible formats:
{x, y} - for horizontal and vertical paddings (x=left=right, y=top=bottom)
{left, right, top, bottom}
function listing to copy
function fitBoundsWithPadding(gMap, bounds, paddingXY) {
var projection = gMap.getProjection();
if (projection) {
if (!$.isPlainObject(paddingXY))
paddingXY = {x: 0, y: 0};
var paddings = {
top: 0,
right: 0,
bottom: 0,
left: 0
};
if (paddingXY.left){
paddings.left = paddingXY.left;
} else if (paddingXY.x) {
paddings.left = paddingXY.x;
paddings.right = paddingXY.x;
}
if (paddingXY.right){
paddings.right = paddingXY.right;
}
if (paddingXY.top){
paddings.top = paddingXY.top;
} else if (paddingXY.y) {
paddings.top = paddingXY.y;
paddings.bottom = paddingXY.y;
}
if (paddingXY.bottom){
paddings.bottom = paddingXY.bottom;
}
// copying the bounds object, since we will extend it
bounds = new google.maps.LatLngBounds(bounds.getSouthWest(), bounds.getNorthEast());
// SW
var point1 = projection.fromLatLngToPoint(bounds.getSouthWest());
// we must call fitBounds 2 times - first is necessary to set up a projection with initial (actual) bounds
// and then calculate new bounds by adding our pixel-sized paddings to the resulting viewport
gMap.fitBounds(bounds);
var point2 = new google.maps.Point(
( (typeof(paddings.left) == 'number' ? paddings.left : 0) / Math.pow(2, gMap.getZoom()) ) || 0,
( (typeof(paddings.bottom) == 'number' ? paddings.bottom : 0) / Math.pow(2, gMap.getZoom()) ) || 0
);
var newPoint = projection.fromPointToLatLng(new google.maps.Point(
point1.x - point2.x,
point1.y + point2.y
));
bounds.extend(newPoint);
// NE
point1 = projection.fromLatLngToPoint(bounds.getNorthEast());
point2 = new google.maps.Point(
( (typeof(paddings.right) == 'number' ? paddings.right : 0) / Math.pow(2, gMap.getZoom()) ) || 0,
( (typeof(paddings.top) == 'number' ? paddings.top : 0) / Math.pow(2, gMap.getZoom()) ) || 0
);
newPoint = projection.fromPointToLatLng(new google.maps.Point(
point1.x + point2.x,
point1.y - point2.y
));
bounds.extend(newPoint);
gMap.fitBounds(bounds);
}
}
I will provide a more generic solution for this issue.
If we have a position e.g. marker.getPosition(), we can find a another position (x, y) pixel away from it using this function.
function extendedLocation(position, x, y) {
var projection = map.getProjection();
var newMarkerX = projection.fromLatLngToPoint(position).x + x/(Math.pow(2, map.getZoom()))
var newMarkerY = projection.fromLatLngToPoint(position).y + y/(Math.pow(2, map.getZoom()))
var newMarkerPoint = new google.maps.Point(newMarkerX, newMarkerY);
return projection.fromPointToLatLng(newMarkerPoint)
}
Note: positive x is in right direction and positive y is in down direction. So, in general case, to bring marker in view, we need to pass negative value of y e.g. extendedLocation(marker.getPosition(), 4, -18)
If you have a persistent slider or any such element at the top of suppose 30 px height, just use y parameter in the function as -30.
A more generalised function can be created which return array of 4 points, each (x, y) pixels away from the given pointing up, down, right and left direction.
function getAllExtendedPosition(position, x, y){
var positionArray = [];
postitionArray.push(extendedLocation(position, x, y);
postitionArray.push(extendedLocation(position, -x, -y);
postitionArray.push(extendedLocation(position, -x, y);
postitionArray.push(extendedLocation(position, x, -y);
return positionArray
}
The way I've done it seems pretty clean. E.g. Apply a 10% pad using a pixel coordinate basis on each side of the map. This is with Maps API v3.44:
map.fitBounds(bounds)
let mapDiv = map.getDiv()
let padding = {
bottom: mapDiv.offsetHeight * 0.1,
left: mapDiv.offsetWidth * 0.1,
right: mapDiv.offsetWidth * 0.1,
top: mapDiv.offsetHeight * 0.1,
}
map.fitBounds(bounds, padding);
Another way to do this would be to extend your boundaries with an additional LatLong point a calculated distance away. The google.maps.LatLngBounds() object has functions to get the SouthWest and NorthEast points of the bounding box, and that can be used to calculate a distance X miles North, South, East or West.
For example, if you were trying to push your markers to the right to account for overlay elements on the left side of the map, you might try the following:
// create your LatLngBounds object, like you're already probably doing
var bounds = new google.maps.LatLngBounds();
// for each marker call bounds.extend(pos) to extend the base boundaries
// once your loop is complete
// *** add a calculated LatLng point to the west of your boundary ***
bounds.extend(new google.maps.LatLng(bounds.getSouthWest().lat(), bounds.getSouthWest().lng() - .9));
// finally, center your map on the modified boundaries
map.fitBounds(bounds);
In the example above, adding a LatLong point to the bounds by subtracting .9 from the longitude of the western-most point moves the boundary about 52 miles further to the west.
A whole point (pos.lng() - 1.0) is about 58 miles, so you can either guess a good distance or use some other method to calculate that longitudinal offset when figuring out what kind of padding you need.

Google Maps v3 fitBounds() Zoom too close for single marker

Is there a way to set a max zoom level for fitBounds()?
My problem is that when the map is only fed one location, it zooms in as far as it can go, which really takes the map out of context and renders it useless. Perhaps I am taking the wrong approach?
I like mrt's solution (especially when you don't know how many points you will be mapping or adjusting for), except it throws the marker off so that it isn't in the center of the map anymore. I simply extended it by an additional point subtracting .01 from the lat and lng as well, so it keeps the marker in the center. Works great, thanks mrt!
// Pan & Zoom map to show all markers
function fitToMarkers(markers) {
var bounds = new google.maps.LatLngBounds();
// Create bounds from markers
for( var index in markers ) {
var latlng = markers[index].getPosition();
bounds.extend(latlng);
}
// Don't zoom in too far on only one marker
if (bounds.getNorthEast().equals(bounds.getSouthWest())) {
var extendPoint1 = new google.maps.LatLng(bounds.getNorthEast().lat() + 0.01, bounds.getNorthEast().lng() + 0.01);
var extendPoint2 = new google.maps.LatLng(bounds.getNorthEast().lat() - 0.01, bounds.getNorthEast().lng() - 0.01);
bounds.extend(extendPoint1);
bounds.extend(extendPoint2);
}
map.fitBounds(bounds);
// Adjusting zoom here doesn't work :/
}
You can setup your map with maxZoom in the MapOptions (api-reference) like this:
var map = new google.maps.Map(document.getElementById("map"), { maxZoom: 10 });
This would keep the map from zooming any deeper when using fitBounds() and even removes the zoom levels from the zoom control.
Another solution is to expand bounds if you detect they are too small before you execute fitBounds():
var bounds = new google.maps.LatLngBounds();
// here you extend your bound as you like
// ...
if (bounds.getNorthEast().equals(bounds.getSouthWest())) {
var extendPoint = new google.maps.LatLng(bounds.getNorthEast().lat() + 0.01, bounds.getNorthEast().lng() + 0.01);
bounds.extend(extendPoint);
}
map.fitBounds(bounds);
Once you've added all of the real bounds add these lines
var offset = 0.002;
var center = bounds.getCenter();
bounds.extend(new google.maps.LatLng(center.lat() + offset, center.lng() + offset));
bounds.extend(new google.maps.LatLng(center.lat() - offset, center.lng() - offset));
it get the center of the real bounds then adds two additional points one to the northeast and one to the southwest of you center
This effectively sets the minimum zoom, change the value of offset to increase or decrease the zoom
var map = new google.maps.Map(document.getElementById("map"), { maxZoom: 10 });
Using the MaxZoom option works best for not zooming to close on to the marks you have.
If it is for a single location, you can use setCenter() and setZoom() instead.
u can use
map.setOptions({
maxZoom: [what u want],
minZoom: [what u want]
});
this way u set the properties of the map after the map has been initialized .... u can set them as many times as u want ... but in ur case ... u can set them before fitBounds()
good luck,
rarutu
The way I prevent the map from zooming in to far is by adding this line of code:
var zoomOverride = map.getZoom();
if(zoomOverride > 15) {
zoomOverride = 15;
}
map.setZoom(zoomOverride);
Directly after this line:
map.setCenter(bounds.getCenter(), map.getBoundsZoomLevel(bounds));
Feel free to change the zoom level to whatever level you don’t want the map to zoom past.
If you have any problems or questions, just leave me a comment on the blog post I wrote about this at http://icode4you.net/creating-your-own-store-locator-map-how-to-prevent-the-map-from-zooming-in-too-close-on-a-single-marker
I really like mrt's solution and it works perfectly if you've always only have one point to work with. I did however find that if the bounding box was not based on one point, but the points were very close together, this could still cause the map to be zoomed in too far.
Here's a way to first check if the points are within a defined distance of each other, then if they are smaller than that minimum distance, extend the bounds by that minimum distance:
var bounds = new google.maps.LatLngBounds();
// here you extend your bound as you like
// ...
var minDistance = 0.002;
var sumA = bounds.getNorthEast().lng() - bounds.getSouthWest().lng();
var sumB = bounds.getNorthEast().lat() - bounds.getSouthWest().lat();
if((sumA < minDistance && sumA > -minDistance)
&& (sumB < minDistance && sumB > -minDistance)){
var extendPoint1 = new google.maps.LatLng(bounds.getNorthEast().lat() + minDistance, bounds.getNorthEast().lng() + minDistance);
var extendPoint2 = new google.maps.LatLng(bounds.getNorthEast().lat() - minDistance, bounds.getNorthEast().lng() - minDistance);
bounds.extend(extendPoint1);
bounds.extend(extendPoint2);
}
Hope this helps someone!
As for me guys i solve it by creating an idle event after fitBounds. Working perfectly. Guess that's one of the most clean solutions here
var locations = [['loc', lat, lng], ['loc', lat, lng]];
.....
for (i = 0; i < locations.length; i++) {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 10
});
.... create markers, etc.
}
....
map.fitBounds(bounds);
google.maps.event.addListenerOnce(map, 'idle', function() {
if (locations.length == 1) {
map.setZoom(11);
}
});
This gives you a direct control upon max allowed zoom on bounds fitting.
var fitToMarkers = function(map, markers, maxZoom) {
if (typeof maxZoom == 'undefined') maxZoom = 15;
google.maps.event.addListenerOnce(map, 'bounds_changed', function(event) {
if (this.getZoom() > maxZoom) {
this.setZoom(maxZoom);
}
});
var bounds = new google.maps.LatLngBounds();
for (var m = 0; m < markers.length; m++) {
var marker = markers[m];
var latlng = marker.getPosition();
bounds.extend(latlng);
}
map.fitBounds(bounds);
};
I solved with this chunk, since Google Maps V3 is event driven:
you can tell the API to set back the zoom to a proper amount when the zoom_changed event triggers:
var initial = true
google.maps.event.addListener(map, "zoom_changed", function() {
if (intial == true){
if (map.getZoom() > 11) {
map.setZoom(11);
intial = false;
}
}
});
I used intial make the map not zooming too much loading when the eventual fitBounds permorfed, without it any zoom event over 11 would be impossible for the user.
After calling fitBounds() method, try to setup zoom level again. It will force the map to be at that zoom level whilst being centered at the right place.
I have soulution based on limiting max zoom when fitting bounds. Works for me (tested on Win 7 - IE 9, FF 13, Chrome 19):
// When fitting bounds:
var bounds = new google.maps.LatLngBounds();
// ...
// extend bounds as you like
// ..
// now set global variable when fitting bounds
window.fittingBounds = true;
map.fitBounds(bounds);
window.fittingBounds = false;
// attach this event listener after map init
google.maps.event.addListener(map, 'zoom_changed', function() {
// set max zoom only when fitting bounds
if (window.fittingBounds && map.getZoom() > 16) {
this.setZoom(16);
}
});
And .. here is another one.
Same idea as mrt and Ryan, but
also works if bounds size is not exactly zero (*)
prevents distortion near the poles
uses getCenter() instead of getNorthEast()
(*) Note: If the box is already big enough, then adding those two extra points should have no effect. So we don't need any further checking.
function calcBounds(markers) {
// bounds that contain all markers
var bounds = new google.maps.LatLngBounds();
// Using an underscore _.each(). Feel free to replace with standard for()
_.each(markers, function(marker) {
bounds.extend(marker.getPosition());
});
// prevent lat/lng distortion at the poles
var lng0 = bounds.getNorthEast().lng();
var lng1 = bounds.getSouthWest().lng();
if (lng0 * lng1 < 0) {
// Take the cos at the equator.
var cos = 1;
}
else {
var cos0 = Math.cos(lng0);
var cos1 = Math.cos(lng1);
// Prevent division by zero if the marker is exactly at the pole.
var cos_safe = Math.max(cos0, cos1, 0.0001);
}
var cos0 = Math.cos(bounds.getNorthEast.lng() * Math.PI / 180);
var cos1 = Math.cos(bounds.getSouthWest.lng() * Math.PI / 180);
// "radius" in either direction.
// 0.0006 seems to be an ok value for a typical city.
// Feel free to make this value a function argument.
var rLat = 0.0006;
var rLng = rLat / cos_safe;
// expand the bounds to a minimum width and height
var center = bounds.getCenter();
var p0 = new google.maps.LatLng(center.lat() - rLat, center.lng() - rLng);
var p1 = new google.maps.LatLng(lat.center() + rLat, center.lng() + rLng);
bounds.extend(p0);
bounds.extend(p1);
return bounds;
}
EDIT: I am not exactly sure if my ratio calculation correctly, considering we have a Mercator projection. I might re-edit this..
Its already answered here Google Maps v3: Enforcing min. zoom level when using fitBounds it works as expected :) so now if after fit bounds zoom is less then lets say 13 then you can set new zoom which you preffer
Here is my go at a solution, which also works when two markers are very close. The effective maximum zoom level is the same in both situations. So we do not end up zooming unneccesarily out, when there are more than one marker
The effect, again is ensuring a maximum zoom, without using the maxZoom option, which has the probably unwanted effect of making it impossible for the user to zoom further than the maxZoom level with the zoom control
I have calculated maxLat, minLat, maxLng and minLng beforehand...
var minLatSpan = 0.002;
if (maxLat - minLat < minLatSpan) {
// ensures that we do not zoom in too much
var delta = (minLatSpan - (maxLat - minLat)) / 2;
maxLat += delta;
minLat -= delta;
}
map.fitBounds({
east: maxLng,
west: minLng,
north: maxLat,
south: minLat,
});