【问题标题】:Custom Map Tile Overlay Issues自定义地图图块叠加问题
【发布时间】:2015-03-21 21:17:28
【问题描述】:

我需要在谷歌地图提供的标准卫星图像上显示几百到一千张高分辨率航空照片。这些图像在地理上是分散的,所以我决定将一个 tile 服务器实现为一个通用的 asp.net 处理程序(*.ashx 文件)。我将根据 Google 开发者网站上显示的地图来描述问题,网址如下:

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

一切都或多或少的工作,但我有以下两个问题:

1) 选择“卫星”地图类型后,将鼠标悬停在该按钮上会生成一个下拉菜单,其中包含一个名为“标签”的复选框。如何在标题为“航空照片”的下拉菜单中添加另一个复选框,以打开/关闭我的叠加层?我是否必须对利用 Google 地图实现细节的 JQuery hack 进行硬编码,或者我可以通过 API 完成此操作吗?

2) 如果指定的图块不存在,我的 *.ashx 处理程序将返回图像或状态 204(无内容)。问题是 204 结果没有被缓存,所以每次我缩小并回到同一个位置时,我的服务器都会重新访问客户端应该已经知道不存在的所有图块。我没有看到它记录了瓦片服务器应该为这样的“空”瓦片返回什么,以便客户端可以缓存结果。如果没有特定位置的地图图块,我应该返回什么?

谢谢。

【问题讨论】:

  • 我的问题不一定是关于从按钮连接事件处理程序。更多的是如何在卫星下拉列表中添加按钮。这是一个将自定义控件添加为全新按钮的示例:developers.google.com/maps/documentation/javascript/examples/…。如何在 Satellite 下拉菜单中添加这样的按钮?
  • 如何使用覆盖创建自定义注册表并将其作为地图类型包含在内。默认用户界面应该为您提供按钮和/或标签以显示/隐藏它。developers.google.com/maps/documentation/javascript/…
  • 添加自定义类型注册表确实会向控制区域添加一个按钮,但有两个问题。首先是选择该按钮仅显示我的地图图块,并且我需要将它们覆盖在卫星或混合地图类型上(因为我的地图图块服务器仅服务于全球非常有限的部分)。第二个问题(假设我可以弄清楚如何使按钮成为启用/禁用我的图层的复选框)是我的叠加层和卫星地图之间没有视觉关联,这是预期的结果。

标签: javascript asp.net google-maps google-maps-api-3


【解决方案1】:

鉴于对这个问题缺乏回应,很明显稀疏的切片服务器是一种不常见的做法。以下是这两个问题的解决方案(尽管它可能很老套):

1) 如何在“卫星”下拉菜单中添加一个复选框来切换我的地图图层?不幸的是,没有支持的方法可以做到这一点,所以我想出了以下令人难以置信的 hacky 代码:

// 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);
    }
});

在上面的代码中,googleMap 是地图对象,tileServer 是我对 google.maps.ImageMapType 对象的实现。

2) 我应该返回什么来代表一个空图块?

我对这个问题的解决方案相当干净。我只是列出了服务器上所有瓦片的文件名,它们是被请求瓦片的莫顿编号的 base-4 编码。然后我将此列表作为从字符串到布尔的字典发送给客户端(始终为真)。客户端在发出请求之前只是检查服务器是否包含地图图块,因此服务器不必担心返回什么(如果发出无效请求,我将其保留为返回 204 错误)。在getTileUrl方法中获取瓦片名称的javascript如下:

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

【讨论】:

  • @KayAnn 不幸的是,我希望有一些更易于维护的东西,但这是我能想到的最好的。感谢您的所有建议!
  • 其他人都知道,如果您的地图指定了 google.maps.MapTypeControlStyle.HORIZONTAL_BAR 或 google.maps.MapTypeControlStyle.DROPDOWN_MENU 之一,则此解决方案有效(至少在今天),但如果您使用默认(或不指定任何内容)。这是因为默认会在两种布局之间切换,并且在它们之间切换时添加的任何 DOM 元素都会被移除。
  • 完全没问题。希望我可以做更多但令人印象深刻的黑客攻击。 N如果我遇到比这更稳定的东西,我会在这里发帖。干杯!
猜你喜欢
  • 1970-01-01
  • 2019-06-22
  • 2022-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-06
  • 1970-01-01
相关资源
最近更新 更多