Exclude overlaid element from Google Maps viewport bounds - javascript

I am using Google Maps API v3 to create an inline map on a website. In its container element, I also have an absolute positioned overlay which shows some detail information, visually hovering over the map. Determining on context this element may grow up to the size of the entire map element.
All this is working fine, however the Maps instance of course still considers the overlaid part of the map a valid usable part of the map. This means that, especially if the overlay is at maximum height, setCenter doesn't focus on the visible center, and routes drawn with DirectionsRenderer are partially underneath the overlay.
See this image:
Is there a way to limit the actual viewport to the blueish area, so that setCenter centers on the arrow tip and setBounds fits to the blue part?

I have managed to implement an acceptably functional workaround for the time being.
Some general notes which are good to know:
Every Map object has a Projection, which can convert between LatLng points to map points.
The map points a Projection uses for calculation are in 'world' coordinates, meaning they are pixels on the world map at zoom level 0.
Every zoom level exactly doubles the number of pixels shown. This means that the number of pixels in a given map point equals 2 ^ zoom.
The samples below assume a 300px wide sidebar on the right - adapting to other borders should be easy.
Centering
Using this knowledge, it becomes trivial to write a custom function for off-center centering:
function setCenter(latlng)
{
var z = Math.pow(2, map.getZoom());
var pnt = map.getProjection().fromLatLngToPoint(latlng);
map.setCenter(map.getProjection().fromPointToLatLng(
new google.maps.Point(pnt.x + 150/z, pnt.y)));
}
The crucial bits here are the z variable, and the pnt.x + 150/z calculation in the final line. Because of the above assumptions, this moves the point to center on 150 pixels to the left for the current zoom level, and as such compensates for the missing 300 pixels on the right sidebar.
Bounding
The bounds issue is far less trivial. The reason for this is that to offset the points correctly, you need to know the zoom level. For recentering this doesn't change, but for fitting to previously unknown bounds it nearly always will. Since Google Maps uses unknown margins itself internally when fitting to bounds, there is no reliable way to predict the required zoom level.
Thus a possible solution is to invoke a two-step rocket. First off, call fitBounds with the entire map. This should make the bounds and zoom level at least nearly correct. Then right after that, do a second call to fitBounds corrected for the sidebar.
The following sample implementation should be called with a LatLngBounds object as parameter, or no parameters to default to the current bounds.
function setBounds(bnd, cb)
{
var prj = map.getProjection();
if(!bnd) bnd = map.getBounds();
var ne = prj.fromLatLngToPoint(bnd.getNorthEast()),
sw = prj.fromLatLngToPoint(bnd.getSouthWest());
if(cb) ne.x += (300 / Math.pow(2, map.getZoom()));
else google.maps.event.addListenerOnce(map,'bounds_changed',
function(){setBounds(bnd,1)});
map.fitBounds(new google.maps.LatLngBounds(
prj.fromPointToLatLng(sw), prj.fromPointToLatLng(ne)));
}
What we do here at first is get the actual points of the bounds, and since cb isn't set we install a once-only event on bounds_changed, which is then fired after the fitBounds is completed. This means that the function is automatically called a second time, after the zoom has been corrected. The second invocation, with cb=1, then offsets the box to correct for the 300 pixel wide sidebar.
In certain cases, this can lead to a slight off-animation, but in practice I've only seen this occur when really spamclicking on buttons causing a fit operation. It's running perfectly well otherwise.
Hope this helps someone :)

You can use the map panBy() method which allows you to change the center of the map by a given distance in pixels.
Hope this helps!

I had a similar need and ended up just forcing some "padding" to the east of a LatLngBounds object.
On the upside, it's simple and it works. On the downside it's not really versatile. Just a quick little hack.
// start with a standard LatLngBounds object, extending as you usually would...
bounds = new google.maps.LatLngBounds();
// ...
ne = bounds.getNorthEast();
sw = bounds.getSouthWest();
// the multiplier used to add space; positive for east, negative for west
lngPadding = 1.5
extendedLng = ne.lng() + (ne.lng() - sw.lng()) * lngPadding;
// copy original and extend with the new Lng
extendedBounds = bounds;
extendedBounds.extend(new google.maps.LatLng(ne.lat(), extendedLng));
map.fitBounds(extendedBounds);

Related

Google Maps API setBounds to exact coordinates

In the Google Maps API it allows you to set the bounds of a map given a list of coordinates. That's awesome. My issue is that it gives a little bit of breathing room on the sides. I'm trying to get the bounding box I'm looking at to be barely containing the bounds.
For example, I want to view California so I set the bounds to be the Northeast and Southwest corners. Instead of showing just California though, I get all of Oregon and half of Mexico.
Here's what I'm currently doing:
var geo_region = {
northeast: {
lat: 42.0095169
lng: -114.131211
}
southwest: {
lat: 32.528832
lng: -124.482003
}
}
var map_bounds = new google.maps.LatLngBounds();
map_bounds.extend(new google.maps.LatLng(geo_region.northeast.lat, geo_region.northeast.lng));
map_bounds.extend(new google.maps.LatLng(geo_region.southwest.lat, geo_region.southwest.lng));
var plot_map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
plot_map.fitBounds(map_bounds);
EDIT:
A clearer example might be Wyoming since it's such a nice rectangle. If my map dimensions are the same ratio as Wyoming, I only want it to show me Wyoming.
EDIT:
Somebody suggested that I offset the bounds manually so I grabbed some data on the offsets that Google is using but I can't figure out what their formula is for deciding those offsets so I'm a long ways away from being able to do that. I even used the viewport coordinates from Google's Geocoding API but those didn't help much either.
Here's my data: https://docs.google.com/a/dovidev.com/spreadsheets/d/1HZLdDt5uiGwEtY0NbX0pfkmYVuUDndptm_-kzq0vh_w/edit?usp=sharing
This cannot be done EXACTLY because of the way google's zoom level's work. Google sets the bounds of the area but zooms in as closely as possible without cutting anything out. Because the zoom levels are incremental and their increments are so large, this often means that you'll end up with a lot of extra space.
When I tried to zoom in even once from what I thought was grossly oversized, I found that parts of the bounds had been cut off.
And thus we see that Google is already getting it as tight as it can be.

How do you get the currently visible features in ol3 when the map was been rotated?

To get the current features I use
var extent = map.getView().calculateExtent(map.getSize());
var features = layer.getSource().getFeaturesInExtent(extent);
However when a user spins the map around (e.g scrolling left 20 times.) this no longer works.
I can't figure out the function I need to use to map the current extent into the 'base' extent of the projection.

If current bounds lie in others (many) bounds?

I am using google maps v3.
I want to know IF current map bounds lies in a sum of all previous bounds (all points of current bounds are covered by other bounds) (if not, I will load markers for current bounds via ajax)
Let say, I have an array of previous viewport bounds, that I do so:
google.maps.event.addListener(map, 'idle',function() {
var bounds = map.getBounds();
boundsArray.push(bounds);
});
The algorithm to check that I invented by now is a hard one and it is the following:
1) Generate all edge points (lan/lon) of current bounds with some $step (0.0001?) and put them in "pointsArray".
2) Iterate over pointsArray, and check if current point exists in at least one previous bounds:
From LatLngBounds doc: contains(latLng:LatLng) boolean Returns true if the given
lat/lng is in this bounds.
so, something like:
if (boundsArray[y].contains(pointsArray[i]) {
and if contains, then remove that point from pointsArray
3) Finally, after all iterations, if the pointsArray is empty — it means all points of current bounds are inside another/s bounds and function returns true...
There are in my opinion 2 disadvantages of my algorithm:
1) it is not 100% precise (depends on $step)
2) and that is worse, it my cause performance drop on a client, checking so many points by all bounds... as javascipt is implemented on users PC.
So, if the any more precise & easy, faster solution for my problem? maybe using another math approaches, google geometry library & so on? How to do that correctly?
You can delete the bounds array when it becomes too big. Then you can start again. It's much easier then to compare the bounds.

Google Maps API V3: getBounds() convert result for fitBounds()

NO, THE ABOVE ANSWERS DON'T ANSWER MY QUESTION. PLEASE READ MY UPDATE BELOW TO SEE THE CLARIFICATION WHY THIS IS A DIFFERENT CASE!!!
I'm using Google maps API V3. When I write this code:
map.fitBounds(map.getBounds());
the map zooms out!
I understand that it's according to the documentation since fitBounds ensures to show the given bounds on the map so that all the edges are shown inside the map. Therefore, the answer I'm looking for lies into the following:
How to modify the result of getBounds to be used for fitBounds without zoom effect afterwards?
Basically, I'm quite sure this can be calculated because that's what the map does, but in the opposite direction, when adding margins, to show the given bounds completely. In these terms, tell me how to calculate the margins (and therefore, how to calculate the correct input for fitBounds having output from getBounds) and your answer will be accepted.
Thanks in advance!
Update:
Zoom and Center retrieval and setting does not work for me! here's the reason why:
I am going to hold the map's viewport information in the database. Later on, I want to re-create the map and show the exact same location again. However, the mapping platform can differ from user to user. Therefore, zoom and center is not standard between different mapping platforms/frameworks and cannot be used in all them with the same results. Therefore, the only standard way is to hold the bounds in the database. So the question needs an answer exactly in the direction it is asking for.
Store instead of the bounds the zoom and center of the map and restore these values later.
I had the exact same problem and decided to shrink the bounding box resulting from getBounds() with 15%. That does the job nicely for me:
var neLat = map.getBounds().getNorthEast().k;
var neLong = map.getBounds().getNorthEast().B;
var swLat = map.getBounds().getSouthWest().k;
var swLong = map.getBounds().getSouthWest().B;
var boundingBoxPerc = 0.15;
var mapWidth = neLong - swLong;
var mapHeight = neLat - swLat;
var ne = new google.maps.LatLng(neLat - mapHeight * boundingBoxPerc, neLong - mapWidth * boundingBoxPerc);
var sw = new google.maps.LatLng(swLat + mapHeight * boundingBoxPerc, swLong + mapWidth * boundingBoxPerc);
map.fitBounds(new google.maps.LatLngBounds(sw, ne));

How do i determine the Max Zoom Level integer for Google Maps?

Is there a way of generating the actual integer that represents the max zoom level of Google Maps? In either the Static or Java API?
Yes you can generate the maximum zoom level possible for the place you are looking at as:
getMaxZoomAtLatLng(latlng:LatLng, callback:function(MaxZoomResult))
Returns the maximum zoom level available at a particular LatLng for the Satellite map type. As this request is asynchronous, you must pass a callback function which will be executed upon completion of the request, being passed a MaxZoomResult.
You can also set the maximum allowed zoom level (to prevent users from fully zooming in for instance) by using the maxZoom property of your MapOptions
The maximum zoom level which will be displayed on the map. If omitted, or set to null, the maximum zoom from the current map type is used instead.
Read everything about it here. (CTRL+F and look for "maximum zoom")
http://code.google.com/apis/maps/documentation/javascript/v2/introduction.html
Each map also contains a zoom level, which defines the resolution of the current view. Zoom levels between 0 (the lowest zoom level, in which the entire world can be seen on one map) to 19 (the highest zoom level, down to individual buildings) are possible within the normal maps view. Zoom levels vary depending on where in the world you're looking, as data in some parts of the globe is more defined than in others. Zoom levels up to 20 are possible within satellite view.
Seems like it's relatively safe to just hard code 19, but if you need the exact max for the places where 19 zoom is disallowed (military bases and whatnot) or places where 20 is allowed (not sure), I'm not sure how to determine that. Perhaps you can detect this by setZoom and then immediately calling getZoom and if the number returned from getZoom is not the one you just set, then you're in one of the non-standard locations.
Here's actual code, if it's helpful.
The accepted answer points in the right direction. The documentation you want is [right here][1].
And here's working modern ES6 code for 2019:
/* Determine max zoom at location */
const location = { lat: _LATITUDE_, lng: _LONGITUDE_ }
const getMaxZoom = new google.maps.MaxZoomService()
getMaxZoom.getMaxZoomAtLatLng(location, (response) => {
console.log('Max zoom at this location:', response.zoom)
})
[1]: https://developers.google.com/maps/documentation/javascript/maxzoom

Categories

Resources