0
votes

I have a need to display a couple hundred to perhaps one thousand high resolution aerial photographs over the standard satellite imagery provided by Google maps. The images are geographically dispersed, so I decided to implement a tile server as a generic asp.net handler (*.ashx file). I will be basing my issue descriptions on the map shown on Google's developer site at the following URL:

https://developers.google.com/maps/documentation/javascript/examples/maptype-overlay

Everything is more-or-less working, but I am having the following two issues:

1) After selecting the "Satellite" map type, hovering over that button produces a dropdown with a checkbox called "Labels". How can I add another checkbox to that dropdown titled "Aerial Photographs" that will toggle my overlay on/off? Will I have to hard-code JQuery hacks that utilize Google Maps implementation details, or can I accomplish this through the API?

2) My *.ashx handler returns either the image, or a status 204 (no content) if the specified tile does not exist. The issue is that the 204 results are not cached, so every time I zoom out and back in to the same location, my server gets re-hit for all the tiles that the client should already know don't exist. I failed to see it documented what a tile server should return for such an "empty" tile, so the client can cache the result. What should I return if there is no map tile for a specific location?

Thanks.

1
My question is not necessarily about hooking up the event handlers from the button. It is more how to add a button in the Satellite dropdown. Here is an example where a custom control is added as an entirely new button: developers.google.com/maps/documentation/javascript/examples/…. How can I add such a button in the Satellite dropdown?Jeff G
How about creating a custom registry with your overlay and include that as a maptype..Default UI should give u the button and/or the label to show/hide it.. developers.google.com/maps/documentation/javascript/…Kay_N
Adding a custom type registry does add a button to the control area, but there are two issues. The first is that selecting that button ONLY shows my map tiles, and I need them to be overlayed over either the satellite or hybrid map types (since my map tile server only serves a very limited portion of the globe). The second problem (assuming I could figure out how to make the button a check box to enable/disable my layer) is that there is no visual correlation between my overlay and the satellite map, which is the desired result.Jeff G

1 Answers

1
votes

Given the lack of response to this question, it is clear that sparse tile servers are an uncommon practice. The following is the solution (however hacky it may be) for both problems:

1) How do I add a checkbox to the "Satellite" dropdown to toggle my map layer? Unfortunately, there is no supported way to do this, so I came up with the following INCREDIBLY hacky code:

// Create a function to select the "Labels" checkbox
var getLabelButton = function() {
    return $('.gm-style-mtc:last > div:last > div:last');
};

// Clone the "Labels" checkbox used to show/hide the hybrid map overlay
var labelBtn   = getLabelButton();
var labelClone = labelBtn.clone();

// Change the display and hover text for the new button
labelClone.prop('title', Localizer.GetString('CustomImagery.Description'));
labelClone.find('label').html(Localizer.GetString('CustomImagery.Name'));

// Highlight the button when the client hovers the mouse over it
var checkbox        = labelClone.children('span');
var prevBackColor   = labelClone.css('background-color');
var prevBorderColor = checkbox  .css('border-color');
labelClone.hover(function() {
    labelClone.css('background-color', '#EBEBEB');
    checkbox  .css('border-color'    , '#666');
}, function() {
    labelClone.css('background-color', prevBackColor);
    checkbox  .css('border-color'    , prevBorderColor);
});

// Set the checkmark image source to be the correct value, instead of transparent
var checkmark    = checkbox .children('div');
var checkmarkImg = checkmark.children('img');
checkmarkImg.attr('src', 'https://maps.gstatic.com/mapfiles/mv/imgs8.png');

// Attach the new checkbox after the Labels checkbox
labelBtn.after(labelClone);

// Create a method to determine if the selected map type supports custom imagery
var mapTypesSupportingCustomImagery = [
    google.maps.MapTypeId.SATELLITE,
    google.maps.MapTypeId.HYBRID
];
var isImagerySupportedOnSelectedMapType = function() {
    var mapTypeId = googleMap.getMapTypeId();
    return (0 <= mapTypesSupportingCustomImagery.indexOf(mapTypeId));
};

// Show the checkmark and imagery if the initial map type supports it
if (isImagerySupportedOnSelectedMapType()) {
    checkmark.css('display', '');
    googleMap.overlayMapTypes.push(tileServer);
}

// Show/hide the checkmark and imagery when the user clicks on the checkbox
labelClone.on('click', function() {
    var showImagery = (checkmark.css('display') === 'none');
    if (showImagery) {
        checkmark.css('display', '');
        googleMap.overlayMapTypes.push(tileServer);
    } else {
        checkmark.css('display', 'none');
        var tileServerIndex = googleMap.overlayMapTypes.indexOf(tileServer);
        googleMap.overlayMapTypes.removeAt(tileServerIndex);
    }
});

// Create a function that returns whether the custom imagery should be displayed
var displayCustomImagery = function() {
    return (isImagerySupportedOnSelectedMapType() && checkmark.css('display') != 'none');
};

// Add an event listener to add the tile server when displaying satellite view
google.maps.event.addListener(googleMap, 'maptypeid_changed', function() {
    var tileServerIndex = googleMap.overlayMapTypes.indexOf(tileServer);
    if (displayCustomImagery()) {
        if (tileServerIndex < 0) {
            googleMap.overlayMapTypes.push(tileServer);
        }
    } else if (0 <= tileServerIndex) {
        googleMap.overlayMapTypes.removeAt(tileServerIndex);
    }
});

In the above code, googleMap is the map object, and tileServer is my implementation of a google.maps.ImageMapType object.

2) What should I return to represent an empty tile?

My solution to this question was quite a bit cleaner. I simply list the file names of all the tiles on the server, which are a base-4 encoding of the Morton number for the tile being requested. Then I send this list to the client as a dictionary from string to bool (always true). The client simply checks to see if the server contains a map tile before making the request, so the server doesn't have to worry about what to return (I left it as returning a 204 error if an invalid request is made). The javascript to get the tile name within the getTileUrl method is as follows:

function(coord, zoom) {
    // Return null if the zoom level is not supported
    // NOTE: This should not be necessary, but minZoom and
    //       maxZoom parameters are ignored for image map types
    if (zoom < minZoom || maxZoom < zoom) {
        return null;
    }

    // Get the name of the map tile being requested
    var tileName = '';
    var y = coord.y << 1;
    for (var shift = zoom - 1; 0 <= shift; --shift) {
        var digit = (coord.x >>> shift) & 1;
        digit    |= (      y >>> shift) & 2;
        tileName += digit;
    }

    // Return if the map tile being requested does not exist
    if (!mapTiles[tileName]) {
        return null;
    }

    // Return the url to the tile server
    ...
}