1

I'm using the Google Maps API to embed a map in a web page. The map fills the entire screen, but there's ~400px worth of content on the left side of the screen that mostly covers the map. When I want to pan the map, that area to the left should be treated as though it isn't visible.

I came up with the following code to calculate the "usable part" of the map, which I'd like to be 50px in from the map's edge, and should also avoid the 400px area to the left side of the map:

panIfNotClose = function(latLngs){
    if(latLngs.length === 0)
        return;

    // Get some important values that may not be available yet
    // http://stackoverflow.com/a/2833015/802335
    var bounds = map.getBounds();
    if(!bounds)
        return setTimeout(panIfNotClose.bind(this, latLngs), 10);
    var overlay = new google.maps.OverlayView();
    overlay.draw = function(){};
    overlay.setMap(map);
    var proj = overlay.getProjection();
    if(!proj)
        return setTimeout(panIfNotClose.bind(this, latLngs), 10);

    // Calculate the "usable part" of the map
    var center = bounds.getCenter();
    var northEast = bounds.getNorthEast();
    var southWest = bounds.getSouthWest();
    var northEastPx = proj.fromLatLngToContainerPixel(northEast);
    var southWestPx = proj.fromLatLngToContainerPixel(southWest);
    var menuPadding = 400;
    var newNorthEastPx = new google.maps.Point(northEastPx.x + 50, northEastPx.y + 50);
    var newSouthWestPx = new google.maps.Point(southWestPx.x - (50 + menuPadding), southWestPx.y - 50);
    var newNorthEast = proj.fromContainerPixelToLatLng(newNorthEastPx);
    var newSouthWest = proj.fromContainerPixelToLatLng(newSouthWestPx);
    var centerBounds = new google.maps.LatLngBounds(newSouthWest, newSouthWest);

    // Decide if any of the new LatLngs are far enough away that the map should move 
    var shouldMove = false;
    var targetBounds = new google.maps.LatLngBounds();
    for(var i = 0; i < latLngs.length; i++){
        targetBounds.extend(latLngs[i]);
        if(!centerBounds.contains(latLngs[i]))
            shouldMove = true;
    }

    // If the LatLngs aren't all near the center of the map, pan to it
    if(latLngs.length === 1){
        if(shouldMove || map.getZoom() !== 18)
            map.panTo(latLngs[0]);
        map.setZoom(18);
    }else{
        var targetZoom = Math.min(getBoundsZoomLevel(targetBounds), 18);
        if(shouldMove || map.getZoom() !== targetZoom)
            map.panTo(targetBounds.getCenter());
        map.setZoom(targetZoom);
    }
}

This code should test the "valid" area to make sure all the given LatLngs fit inside, but it doesn't yet make any changes to panTo to "move" the center 200px to the right to account for the 400px worth of content on the left.

The code doesn't work as I intended, but I'm not sure why. I suspect I'm probably doing something wrong when converting from a LatLng to a Point or vice-versa. I may also be doing far more work than is necessary, although I can't think of a way to simplify it.

Dan Hlavenka
  • 3,697
  • 8
  • 42
  • 60
  • there is no function `getBoundsZoomLevel` in V3 – Dr.Molle Apr 27 '14 at 10:37
  • see:http://stackoverflow.com/questions/9837017/equivalent-of-getboundszoomlevel-in-gmaps-api-3 – Dr.Molle Apr 27 '14 at 10:51
  • I actually have that exact function from the linked answer defined elsewhere in my code (notice how it's not prefixed with `map.`). I didn't include it because it's not really relevant to my question. – Dan Hlavenka Apr 27 '14 at 21:48

1 Answers1

0

This turned out to be a pretty simple mistake. In the line var centerBounds = new google.maps.LatLngBounds(newSouthWest, newSouthWest);, I accidentally used the same LatLng twice, rather than the northeast and southwest corners. I also had a couple simple arithmetic issues when calculating newNorthEastPx and newSouthWestPx. Drawing a google.maps.Rectangle using centerBounds helped work that out quickly and easily. For anyone interested, here's the end result:

function panIfNotClose(latLngs, zoomOnly){
    if(latLngs.length === 0)
        return;

    if(typeof latLngs !== "object")
        latLngs = [latLngs];

    // Calculate the "middle half" of the map
    var bounds = map.getBounds();
    if(!bounds) // http://stackoverflow.com/a/2833015/802335
        return setTimeout(panIfNotClose.bind(this, latLngs, zoomOnly), 10);
    var overlay = new google.maps.OverlayView();
    overlay.draw = function(){};
    overlay.setMap(map);
    var proj = overlay.getProjection();
    if(!proj)
        return setTimeout(panIfNotClose.bind(this, latLngs, zoomOnly), 10);

    var center = bounds.getCenter();
    var northEast = bounds.getNorthEast();
    var southWest = bounds.getSouthWest();
    var northEastPx = proj.fromLatLngToContainerPixel(northEast);
    var southWestPx = proj.fromLatLngToContainerPixel(southWest);
    var menuPadding = 0;
    if($navBar.css("display") !== "none")
        menuPadding = 400;
    var newNorthEastPx = new google.maps.Point(northEastPx.x - 100, northEastPx.y + 100);
    var newSouthWestPx = new google.maps.Point(southWestPx.x + (100 + menuPadding), southWestPx.y - 100);
    var newNorthEast = proj.fromContainerPixelToLatLng(newNorthEastPx);
    var newSouthWest = proj.fromContainerPixelToLatLng(newSouthWestPx);
    centerBounds = new google.maps.LatLngBounds(newSouthWest, newNorthEast);

    var shouldMove = false;
    var targetBounds = new google.maps.LatLngBounds();
    for(var i = 0; i < latLngs.length; i++){
        targetBounds.extend(latLngs[i]);
        if(!centerBounds.contains(latLngs[i]))
            shouldMove = true;
    }

    // If the marker isn't near the center of the map, pan to it
    if(latLngs.length === 1){
        if(!zoomOnly && (shouldMove || map.getZoom() !== 18))
            map.panTo(correctCenter(latLngs[0], proj));
        map.setZoom(18);
    }else{
        var targetZoom = Math.min(getBoundsZoomLevel(targetBounds), 18);
        if(!zoomOnly && (shouldMove || map.getZoom() !== targetZoom))
            map.panTo(correctCenter(targetBounds.getCenter(), proj));
        map.setZoom(targetZoom);
    }
}

function correctCenter(latLng, proj){
    // $navBar references a jQuery pointer to a DOM element
    if($navBar.css("display") === "none")
        return latLng;
    var latLngPx = proj.fromLatLngToContainerPixel(latLng);
    var newLatLngPx = new google.maps.Point(latLngPx.x - 200, latLngPx.y)
    return proj.fromContainerPixelToLatLng(newLatLngPx);
}

// Adapted from http://stackoverflow.com/a/13274361/802335
function getBoundsZoomLevel(bounds){
    // $mapCanvas is a jQuery reference to the div containing the Google Map
    var mapDim = {
        width: $mapCanvas.width() * .6,
        height: $mapCanvas.height() * .6
    };
    var ZOOM_MAX = 18;

    function latRad(lat){
        var sin = Math.sin(lat * Math.PI / 180);
        var radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
        return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;
    }

    function zoom(mapPx, fraction){
        return Math.floor(Math.log(mapPx / 256 / fraction) / Math.LN2);
    }

    var ne = bounds.getNorthEast();
    var sw = bounds.getSouthWest();

    var latFraction = (latRad(ne.lat()) - latRad(sw.lat())) / Math.PI;

    var lngDiff = ne.lng() - sw.lng();
    var lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;

    var latZoom = zoom(mapDim.height, latFraction);
    var lngZoom = zoom(mapDim.width, lngFraction);

    return Math.min(latZoom, lngZoom, ZOOM_MAX);
}
Dan Hlavenka
  • 3,697
  • 8
  • 42
  • 60