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
...
}