谷歌地图API 3 fitBounds填充-确保标记不被覆盖叠加的控件遮挡

35
我希望在调用 map.fitBounds() 后,能够为地图视图添加填充,以便所有标记点可见,不受地图控件或滑动面板等遮盖的影响。Leaflet 有一个选项,可以添加填充来适应 fitBounds,但是 Google Maps 没有实现这个功能。
有时最北端的标记点会部分隐藏在视口之上。西侧的标记点也经常位于缩放滑块下面。使用 API 2,可以通过从地图视口中减去指定的填充(padding),形成虚拟视口(viewport),然后调用 showBounds() 方法来基于该虚拟视口计算和执行缩放和居中操作。
map.showBounds(bounds, {top:30,right:10,left:50});

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

我无法在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>

问题在于:

enter image description here

我尝试按照自定义控件文档的说明添加了一个控件,但是地图并没有完全意识到它 - 请参见从地图自定义控件示例派生的此fiddle。其中一个标记仍然被该控件遮挡。

8个回答

27

这是一种有些hack的解决方案,但在使用fitBounds之后,您可以缩小一个级别,以便为标记留出足够的填充空间。

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

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

4
便宜、快速和简单粗暴,我喜欢! - shababhsiddique
这里不起作用,很奇怪!它输出了缩放级别和缩放级别-1,但没有缩放。map.fitBounds(bounds); console.log(map.getZoom()); map.setZoom(map.getZoom() - 5); console.log(map.getZoom()); - Miguel Stevens
同样适用于Rails中的gmaps4rails。要找到缩放级别,我使用了 var zoom_level = handler.getMap().getZoom() 并在fitMapToBounds调用后使用 handler.getMap().setZoom(zoom_level - 1) 来设置缩放级别。 - Adam Cooper
3
由于fitBounds()是异步的,为了使其正常工作,您可能需要像https://dev59.com/dG855IYBdhLWcg3wIAga#11976581中所述添加一个监听器到您的代码。 - Jeff Steil

18

2
看到这很不错,但对于许多用例来说仍然是一个不充分的解决方案,因为通常只有一些区域需要填充,而那个(文档不好的)功能似乎会在所有边缘添加填充。 - El Yobo
4
“请参考文档以获取更多详细信息。” - 这很好,但是没有详细信息。只有提到一个名为padding的参数,它需要一个数字。完全没有关于其语义或默认值的详细信息。尽管谷歌地图API的指南很好,但参考文档却不足,这种情况也不例外。 - Edward Brey
太有趣了。我在搜索“fitBounds padding”时来到这里,因为文档中没有关于填充的更多细节,并且我被引用到文档以获取更多细节。 - Parker Ault
4
您可以添加一个数字或对象{left:50, top:200} // right, bottom等等,用于指定在地图边界周围要放置的填充区域。有关详细信息,请参见链接中提供的文档。 - yairniz
显示剩余3条评论

12

您可以在API V3中使用map.fitBounds(),并且可以像在map.showBounds()中提到的一样使用相同的填充语法。

对我而言,只需简单地使用map.fitBounds(bounds, {top:30,right:10,left:50});即可。

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


10
我通过扩展地图边界以包含一个充分推动标记进入视图的latlng来解决了这个问题。 首先,您需要创建一个覆盖视图。
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> });

9

更新

Google Maps API现在支持在 fitBounds 方法中本地使用 "padding" 参数(从版本3.32开始,如果早期请纠正我)。

我还没有测试过它,但如果您能够升级 - 我建议使用本地方法。 如果您使用的是版本<3.32并且无法升级 - 我的解决方案适用于您。


我采用了erzzo提供的可行解决方案并对其进行了改进。
示例

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

参数说明:

  1. gMap - 谷歌地图实例
  2. bounds - 谷歌地图的LatLngBounds对象,用于适应
  3. paddingXY - 对象文字:有两种可能的格式:
    • {x,y} - 用于水平和垂直填充(x =左=右,y =上=下)
    • {left,right,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可以缩放的最小值。 - juliusbangert
嗨@julius!我很高兴它有帮助=)作为一个快速解决方案,我可以建议在fitBounds之后检查缩放级别。像这样: if (gMap.getZoom() > maxZoom) { gMap.setZoom(maxZoom); } 此外,还有一个地图选项“maxZoom”,您可以全局设置您的地图。 - Roman86
我实际上是通过在任何fitBounds之前使用**map.setOptions({ maxZoom: maxZoom });**,然后在之后取消设置来解决它的。 - juliusbangert
1
@enorl76 谢谢你提出这个好问题!它甚至让我有些怀疑。这是答案:首先,fitBounds 是必要的,以设置具有初始(实际)边界的投影,然后通过将我们的像素大小的填充添加到结果视口来计算新的边界。我已经更新了答案。再次感谢。 - Roman86

1
我将为此问题提供更通用的解决方案。 如果我们有一个位置,例如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的负值,例如extendedLocation(marker.getPosition(), 4, -18) 如果在顶部有一个持久的滑块或任何30像素高度的元素,请将函数中的y参数设置为-30 可以创建一个更通用的函数,返回距离给定位置向上、向下、向右和向左方向每个(x,y)像素的4个点的数组。
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
}

0
我所做的方式看起来相当干净。例如,在地图的每一侧使用基于像素坐标的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);

-1

另一种方法是通过添加一个距离计算出的附加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英里,因此您可以猜测一个好的距离或使用其他方法计算纵向偏移量,以便在确定所需填充类型时。

使用绝对距离添加点是不好的设计,因为值会随着缩放而改变。要正确添加此点,您必须自己计算范围和缩放... - Kustolovic
考虑到缩放和/或画布大小是隐含的,因此最后一句建议在计算所需填充时使用“其他方法来计算纵向偏移量”。 - mcnejef

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接