blog

Software screen capture

Custom Google Info Windows: Updated, Live

by

April 30, 2014 at 3:22 am
Remy says:
Do you have a webpage with the full code (html, js, css) to test how it works?
Tia

My dear… Remy? Tia? Both?

Whoever you are, your wish is granted: CGWin Demo

Not content to merely offer a live example of our Custom Google Info Windows, we’ve also updated our implementation with panToView capability – that is, when you click on a marker and the resulting info window is outside the current map bounds, the map will now pan the minimum amount necessary to show the full info window.

It’s not as easy as you might expect! Let’s take a look at what we have to do to make this happen (and then note the demo page again, just for good measure).

So, let’s start off with a big ol’ block of code:

/**
 * If the custom window is not already entirely within the map view, pan the map the minimum amount
 * necessary to bring the custom info window fully into view.
 */
CustomWindow.prototype.panToView = function(){
    var position = this.position,
        latlng = this.marker.getPosition(),
        top = parseInt(this.container.style.top, 10),
        cHeight = position.y - top,
        cWidth = this.container.offsetWidth / 2,
        map = this.getMap(),
        center = map.getCenter(),
        bounds = map.getBounds(),
        degPerPixel = (function(){
            var degs = {},
                div = map.getDiv(),
                span = bounds.toSpan();
        degs.x = span.lng() / div.offsetWidth;
        degs.y = span.lat() / div.offsetHeight;
        return degs;
    })(),
    infoBounds = (function(){
        var infoBounds = {};
        infoBounds.north = latlng.lat() + cHeight * degPerPixel.y;
        infoBounds.south = latlng.lat();
        infoBounds.west = latlng.lng() - cWidth * degPerPixel.x;
        infoBounds.east = latlng.lng() + cWidth * degPerPixel.x;
        return infoBounds;
    })(),
    newCenter = (function(){
        var ne = bounds.getNorthEast(),
            sw = bounds.getSouthWest(),
            north = ne.lat(),
            east = ne.lng(),
            south = sw.lat(),
            west = sw.lng(),
            x = center.lng(),
            y = center.lat(),
            shiftLng = ((infoBounds.west < west) ? west - infoBounds.west : 0) +
                ((infoBounds.east > east) ? east - infoBounds.east : 0),
            shiftLat = ((infoBounds.north > north) ? north - infoBounds.north : 0) +
                ((infoBounds.south < south) ? south - infoBounds.south : 0);
        return (shiftLng || shiftLat) ? new google.maps.LatLng(y - shiftLat, x - shiftLng) : void 0;
    })();
if (newCenter){
    map.panTo(newCenter);
    }
};

You’ll note almost all of the work happens in the long, run-on var statement. At first blush, this looks like a mere effort at character saving (all those extra vars would be pretty messy) – but thanks to Javascript var hoisting its actually a better representation of what the function block looks like to the runtime. But I digress.

var position = this.position,
    latlng = this.marker.getPosition(),
    top = parseInt(this.container.style.top, 10),
    cHeight = position.y - top,
    cWidth = this.container.offsetWidth / 2,
    map = this.getMap(),
    center = map.getCenter(),
    bounds = map.getBounds(),

We start off by getting the locations and bounds of a few things – namely, the position of the custom window itself, the position of its marker, the dimensions of the container, the map and its center and bounds, all for use in later computations.

degPerPixel = (function(){
    var degs = {},
        div = map.getDiv(),
        span = bounds.toSpan();
degs.x = span.lng() / div.offsetWidth;
degs.y = span.lat() / div.offsetHeight;
return degs;
})(),

Here, we create a closure to return an object whose values are the calculated number of degrees per pixel in our map view on the x and y axes. We need this in order to calculate the difference between the current bounds and position of the infoWindow versus the bounds of the map.

infoBounds = (function(){
    var infoBounds = {};
infoBounds.north = latlng.lat() + cHeight * degPerPixel.y;
infoBounds.south = latlng.lat();
infoBounds.west = latlng.lng() - cWidth * degPerPixel.x;
infoBounds.east = latlng.lng() + cWidth * degPerPixel.x;
return infoBounds;
})(),

Similarly, here we return an object containing the calculating bounds of our info window in degrees, with the base of the marker being considered the southern boundary, so that both the marker and info window will be in view when we’re done.

newCenter = (function(){
    var ne = bounds.getNorthEast(),
        sw = bounds.getSouthWest(),
        north = ne.lat(),
        east = ne.lng(),
        south = sw.lat(),
        west = sw.lng(),
        x = center.lng(),
        y = center.lat(),
        shiftLng = ((infoBounds.west < west) ? west - infoBounds.west : 0) +
            ((infoBounds.east > east) ? east - infoBounds.east : 0),
        shiftLat = ((infoBounds.north > north) ? north - infoBounds.north : 0) +
            ((infoBounds.south < south) ? south - infoBounds.south : 0);
        return (shiftLng || shiftLat) ? new google.maps.LatLng(y - shiftLat, x - shiftLng) : void 0;
})();
if (newCenter){
    map.panTo(newCenter);
}

Finally, we calculate what should be the new center of the map based on the minimum shift of the center necessary in order to ensure the full bounds of our info window, as previously calculated, will be in view, considering the bounds of the map. If no shift is required, we return undefined and skip setting a new center.

Want to see it in action? CGWin Demo
Want to see the code? CGWin Github repo

+ more

Accurate Timing

Accurate Timing

In many tasks we need to do something at given intervals of time. The most obvious ways may not give you the best results. Time? Meh. The most basic tasks that don't have what you might call CPU-scale time requirements can be handled with the usual language and...

read more