Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exclude overlaid element from Google Maps viewport bounds

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: enter image description here

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?

like image 736
Niels Keurentjes Avatar asked Feb 20 '14 16:02

Niels Keurentjes


People also ask

How do I hide labels on Google Maps?

Find the “Layers” menu in the bottom left corner of the screen. Hover your cursor over the box and wait until more options appear. Click “More” to open the Map Details menu. Under “Map Type,” you'll see a checked box next to “Labels.” Uncheck it to remove all labels.

Can I overlay maps in Google Maps?

Use map images to create extra information without embedding it into your original map. To see how an overlay image corresponds to the map image underneath it: Select the overlay in the viewer. Then, change the transparency so that it's fully opaque.

How do I turn off Pegman on Google Maps?

> You should be able to set streetViewControl:false inhttp://code.google.com/apis/maps/documentation/javascript/reference.h... > > streetview pegman on my map. > > pegman too.


1 Answers

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 :)

like image 159
Niels Keurentjes Avatar answered Oct 10 '22 19:10

Niels Keurentjes