Google Maps API 3 fitBounds 填充 - 确保标记不会被覆盖控件遮挡

Posted

技术标签:

【中文标题】Google Maps API 3 fitBounds 填充 - 确保标记不会被覆盖控件遮挡【英文标题】:Google Maps API 3 fitBounds padding - ensure markers are not obscured by overlaid controls 【发布时间】:2012-05-07 12:52:49 【问题描述】:

我希望能够在调用map.fitBounds() 后向地图视图添加填充,这样所有标记都可以看到,而不管地图控件或打开时会覆盖标记的滑动面板之类的东西。 Leaftlet 有一个option to add padding to fitBounds,但谷歌地图没有。

有时最北端的标记会部分隐藏在视口上方。最西端的标记通常也位于缩放滑块下方。使用 API 2,可以通过减少地图视口中的给定填充来形成虚拟视口,然后调用方法showBounds() 来计算并基于该虚拟视口执行缩放和居中:

map.showBounds(bounds, top:30,right:10,left:50);

可以在 showBounds() 示例链接下的 here 找到 API 2 的一个工作示例。

我在 API V3 中找不到类似的功能,但希望有另一种方法可以实现。也许我可以抓住东北和西南的点,然后在包含它们之后添加假坐标以进一步扩展边界?

更新

(Codepen以防下面的代码不起作用)

function initMap() 
  var map = new google.maps.Map(document.getElementById('map'), 
    draggable: true,
    streetViewControl: false,
    zoomControl: false
  );

  var marker1 = new google.maps.Marker(
    position: lat: 37, lng: -121,
    map: map,
  );

  var marker2 = new google.maps.Marker(
    position: lat: 39.3, lng: -122,
    map: map,
  );
 
  var bounds = new google.maps.LatLngBounds();
  bounds.extend(marker1.position);
  bounds.extend(marker2.position);
  map.fitBounds(bounds);
#map 
  height: 640px;
  width: 360px;

#overlays 
  position: absolute;
  height: 50px;
  width: 340px;
  background: white;
  margin: -80px 10px;
  text-align: center;
  line-height: 50px;

/* Optional: Makes the sample page fill the window. */
html, body 
  height: 100%;
  margin: 0;
  padding: 0;
<html>
  <head>
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
    <meta charset="utf-8">
    <title>Simple markers</title>

  </head>
  <body>
    <div id="map"></div>
    <div id="overlays">Controls / Order pizza / ETA / etc.</div>
  
    <script async defer
    src="https://maps.googleapis.com/maps/api/js?&callback=initMap">
    </script>
  </body>
</html>

问题是这样的:

我已尝试添加Custom controls 中记录的控件,但地图并不完全了解它 - 请参阅从Maps custom control example 分叉的this fiddle。其中一个标记仍被控件遮挡。

【问题讨论】:

【参考方案1】:

这是一种 hack-ish 解决方案,但在 fitBounds 之后,您可以缩小一个级别,以便为标记获得足够的填充。

假设map 变量是您对地图对象的引用;

map.setZoom(map.getZoom() - 1);

【讨论】:

便宜、快速和肮脏。我喜欢! 这在这里不起作用,很奇怪!它正在输出缩放级别和缩放级别 - 1,但不缩放 map.fitBounds(bounds); console.log(map.getZoom()); map.setZoom(map.getZoom() - 5); console.log(map.getZoom()); 也适用于 rails 中的 gmaps4rails。为了找到缩放级别,我使用了 var zoom_level = handler.getMap().getZoom() 并在 fitMapToBounds 调用之后使用 handler.getMap().setZoom(zoom_level - 1) 设置缩放级别。 因为 fitBounds() 是异步的,要使其正常工作,您可能需要按照***.com/a/11976581/286486 中所述向您的代码添加一个侦听器【参考方案2】:

截至 2017 年 6 月,Maps javascript API 支持 fitBounds() 方法中的填充参数。

fitBounds(bounds:LatLngBounds|LatLngBoundsLiteral, padding?:number)

详情请参阅文档

https://developers.google.com/maps/documentation/javascript/reference#Map

【讨论】:

很高兴看到这一点,但对于许多用例来说仍然是一个不充分的解决方案,因为通常只有某些区域需要填充,并且(记录不充分的)功能似乎会向所有边添加填充。 在我看来,填充是添加到地图的各个方面。 “请参阅文档以获取更多详细信息” - 这很好,但没有详细信息。只提到了一个名为padding 的参数,它接受一个数字。没有关于其语义或默认值的详细信息。虽然 Google Maps API 的指南很好,但缺少参考文档,本例也不例外。 伙计们,如果您认为文档不清楚,请在 Google issue tracker 中提交文档错误。我理解填充参数表示所有边缘的像素填充。 developers.google.com/maps/documentation/javascript/reference/… 您可以添加一个数字或 left:50, top:200 // 右、下等。【参考方案3】:

我通过扩展地图边界以包含一个足以将标记推入视野的纬度解决了这个问题。

首先你需要创建一个覆盖视图

var overlayHelper = new google.maps.OverlayView();
overlayHelper.onAdd = function() ;
overlayHelper.onRemove = function() ;
overlayHelper.draw = function() ;
overlayHelper.setMap(map);

一旦你有了一个覆盖帮助器,你就需要获取地图投影并基于它执行计算。

请注意,我在地图上的控件是地图最右侧的一个 420 像素宽、100% 高度的 div。您显然需要更改代码以适应您的控件。

var mapCanvas = $("#map_canvas"),
    controlLeft = mapCanvas.width() - 420, // canvas width minus width of the overlayed control
    projection = overlayHelper.getProjection(),
    widestPoint = 0, 
    latlng, 
    point;

// the markers were created elsewhere and already extended the bounds on creation
map.fitBounds(mapBounds);

// check if any markers are hidden behind the overlayed control
for (var m in markers) 
    point = projection.fromLatLngToContainerPixel(markers[m].getPosition());
    if (point.x > controlLeft && point.x > widestPoint) 
        widestPoint = point.x;
    


if (widestPoint > 0) 
    point = new google.maps.Point(
                mapCanvas.width() + (widestPoint - controlLeft), 
                mapCanvas.height() / 2); // middle of map height, since we only want to reposition bounds to the left and not up and down

    latlng = projection.fromContainerPixelToLatLng(point);
    mapBounds.extend(latlng);
    map.fitBounds(mapBounds);

如果您在地图首次加载时执行此操作,则需要将其包装在地图事件中以等待空闲。这允许覆盖视图初始化。不要在事件回调中包含叠加层帮助器的创建。

google.maps.event.addListenerOnce(map, 'idle', function()  <above code> );

【讨论】:

【参考方案4】:

更新

Google Maps API 现在支持 fitBounds 方法中的原生“填充”参数(从版本 3.32 开始,如果更早,请纠正我)。

我还没有机会对其进行测试,但如果您能够升级 - 我会建议使用原生方式。如果您使用的是版本


我将working solution 替换为erzzo 并对其进行了一些改进。 示例

fitBoundsWithPadding(googleMapInstance, PolygonLatLngBounds, left:250, bottom:10);

参数说明:

    gMap - 谷歌地图实例 边界 - 谷歌地图 LatLngBounds 对象以适应 paddingXY - 对象文字:2 种可能的格式: x, y - 用于水平和垂直填充(x=left=right, y=top=bottom) 左、右、上、下

要复制的函数列表

function fitBoundsWithPadding(gMap, bounds, paddingXY) 
        var projection = gMap.getProjection();
        if (projection) 
            if (!$.isPlainObject(paddingXY))
                paddingXY = x: 0, y: 0;

            var paddings = 
                top: 0,
                right: 0,
                bottom: 0,
                left: 0
            ;

            if (paddingXY.left)
                paddings.left = paddingXY.left;
             else if (paddingXY.x) 
                paddings.left = paddingXY.x;
                paddings.right = paddingXY.x;
            

            if (paddingXY.right)
                paddings.right = paddingXY.right;
            

            if (paddingXY.top)
                paddings.top = paddingXY.top;
             else if (paddingXY.y) 
                paddings.top = paddingXY.y;
                paddings.bottom = paddingXY.y;
            

            if (paddingXY.bottom)
                paddings.bottom = paddingXY.bottom;
            

            // copying the bounds object, since we will extend it
            bounds = new google.maps.LatLngBounds(bounds.getSouthWest(), bounds.getNorthEast());

            // SW
            var point1 = projection.fromLatLngToPoint(bounds.getSouthWest());


            // we must call fitBounds 2 times - first is necessary to set up a projection with initial (actual) bounds
            // and then calculate new bounds by adding our pixel-sized paddings to the resulting viewport
            gMap.fitBounds(bounds);

            var point2 = new google.maps.Point(
                ( (typeof(paddings.left) == 'number' ? paddings.left : 0) / Math.pow(2, gMap.getZoom()) ) || 0,
                ( (typeof(paddings.bottom) == 'number' ? paddings.bottom : 0) / Math.pow(2, gMap.getZoom()) ) || 0
            );

            var newPoint = projection.fromPointToLatLng(new google.maps.Point(
                point1.x - point2.x,
                point1.y + point2.y
            ));

            bounds.extend(newPoint);

            // NE
            point1 = projection.fromLatLngToPoint(bounds.getNorthEast());
            point2 = new google.maps.Point(
                ( (typeof(paddings.right) == 'number' ? paddings.right : 0) / Math.pow(2, gMap.getZoom()) ) || 0,
                ( (typeof(paddings.top) == 'number' ? paddings.top : 0) / Math.pow(2, gMap.getZoom()) ) || 0
            );
            newPoint = projection.fromPointToLatLng(new google.maps.Point(
                point1.x + point2.x,
                point1.y - point2.y
            ));

            bounds.extend(newPoint);

            gMap.fitBounds(bounds);
        
    

【讨论】:

完美解决方案。谢谢! 感谢您提供此解决方案。我将如何调整它以包含 maxZoom 参数?我希望能够限制 fitBoundsWithPadding 可以放大的距离。 嗨@julius!我很高兴它有帮助 =) 作为一个快速的解决方案,我可以建议在 fitBounds 之后检查缩放级别。像这样: if (gMap.getZoom() > maxZoom) gMap.setZoom(maxZoom);此外,还有一个地图选项“maxZoom”,您可以为您的地图全局设置 我实际上在任何 fitBounds 之前使用 map.setOptions( maxZoom: maxZoom ); 解决了它,然后再取消它。 @enorl76 感谢您提出一个好问题!甚至让我有些怀疑。答案是:首先 fitBounds 是设置具有初始(实际)边界的投影所必需的,然后通过将像素大小的填充添加到结果视口来计算新边界。我更新了答案。再次感谢【参考方案5】:

您可以将map.fitBounds() 与API V3 一起使用,其填充语法与您在map.showBounds() 中提到的相同。

只需使用map.fitBounds(bounds, top:30,right:10,left:50); 对我有用。

(这可能是对 xomena 或 Roman86 帖子的评论,我没有足够的声誉来评论)

【讨论】:

【参考方案6】:

我将为这个问题提供一个更通用的解决方案。 如果我们有一个职位,例如marker.getPosition(),我们可以使用这个函数找到另一个距离它(x, y)像素的位置。

function extendedLocation(position, x, y) 
    var projection = map.getProjection();
    var newMarkerX = projection.fromLatLngToPoint(position).x + x/(Math.pow(2, map.getZoom()))
    var newMarkerY = projection.fromLatLngToPoint(position).y + y/(Math.pow(2, map.getZoom()))
    var newMarkerPoint = new google.maps.Point(newMarkerX, newMarkerY);
    return projection.fromPointToLatLng(newMarkerPoint)

注意:x 为正,y 为右,y 为下。因此,在一般情况下,要考虑标记,我们需要传递 y 的负值,例如extendedLocation(marker.getPosition(), 4, -18)

如果您在假设 30 px 高度的顶部有一个持久滑块或任何此类元素,只需在函数中使用 y 参数作为 -30

可以创建一个更通用的函数,它返回 4 个点的数组,每个 (x, y) 像素远离给定的向上、向下、向右和向左方向。

function getAllExtendedPosition(position, x, y)
   var positionArray = [];
   postitionArray.push(extendedLocation(position, x, y);
   postitionArray.push(extendedLocation(position, -x, -y);
   postitionArray.push(extendedLocation(position, -x, y);
   postitionArray.push(extendedLocation(position, x, -y);
   return positionArray

【讨论】:

【参考方案7】:

我做的方式看起来很干净。例如。在地图的每一侧使用像素坐标基础应用 10% 的填充。这是 Maps API v3.44:

map.fitBounds(bounds)
let mapDiv = map.getDiv()

let padding = 
  bottom: mapDiv.offsetHeight * 0.1,
  left: mapDiv.offsetWidth * 0.1,
  right: mapDiv.offsetWidth * 0.1,
  top: mapDiv.offsetHeight * 0.1,

map.fitBounds(bounds, padding);

【讨论】:

【参考方案8】:

执行此操作的另一种方法是在计算出的距离之外使用一个额外的 LatLong 点来扩展您的边界。 google.maps.LatLngBounds() 对象具有获取边界框的 SouthWest 和 NorthEast 点的函数,可用于计算北、南、东或西 X 英里的距离。

例如,如果您尝试将标记向右推以考虑地图左侧的叠加元素,您可以尝试以下操作:

// create your LatLngBounds object, like you're already probably doing
var bounds = new google.maps.LatLngBounds();

// for each marker call bounds.extend(pos) to extend the base boundaries

// once your loop is complete
// *** add a calculated LatLng point to the west of your boundary ***
bounds.extend(new google.maps.LatLng(bounds.getSouthWest().lat(), bounds.getSouthWest().lng() - .9));

// finally, center your map on the modified boundaries
map.fitBounds(bounds);

在上面的示例中,通过从最西点的经度中减去 0.9 将 LatLong 点添加到边界会使边界向西移动约 52 英里。

一个整点 (pos.lng() - 1.0) 大约是 58 英里,因此在确定需要哪种填充时,您可以猜测一个合适的距离或使用其他方法来计算纵向偏移量。

【讨论】:

添加一个绝对距离的点是不好的设计,值会根据缩放而改变。要正确添加这一点,您必须自己计算边界并缩放...... 在某种程度上暗示了考虑缩放和/或画布大小 - 因此最后一句话建议使用“在确定需要什么样的填充时使用其他方法来计算纵向偏移”。跨度>

以上是关于Google Maps API 3 fitBounds 填充 - 确保标记不会被覆盖控件遮挡的主要内容,如果未能解决你的问题,请参考以下文章

Google Maps API 3 referrerNotAllowedMapError

Google Maps API 3 - 根据缩放级别显示/隐藏标记

在 maps.google.com 上的缩放比在 Google Maps API v3 上更流畅

Google Maps API 3 - 限制平移/地图边界

Google Maps API 3. 叠加层中的文本选择

Google Maps v3 - 防止 API 加载 Roboto 字体