如何在谷歌地图 api V3 中偏移中心点

Posted

技术标签:

【中文标题】如何在谷歌地图 api V3 中偏移中心点【英文标题】:How to offset the center point in Google maps api V3 【发布时间】:2012-05-26 06:29:57 【问题描述】:

我有一个谷歌地图,其中半透明面板覆盖了部分区域。我想调整地图的中心点以考虑地图中部分被遮挡的部分。见下图。理想情况下,放置十字准线和大头针的位置是地图的中心点。

我希望这是有道理的。

原因很简单:当您缩放时,它需要使地图居中于十字准线之上,而不是 50% 50%。此外,我将在地图上绘制标记并按顺序移动它们。当地图以它们为中心时,它们也需要处于偏移位置。

提前致谢!

【问题讨论】:

你必须澄清你的问题。 “考虑到地图部分被遮挡的部分”是什么意思? “而不是 50% 50%”是什么意思?想帮忙,但不知道你想达到什么目标。 嗨,您能否提供工作示例的 url,因为我需要一些帮助来创建移动策略线和路线线 【参考方案1】:

一旦找到the relevant previous answer,这并不是特别困难。

您需要将地图的中心转换为其世界坐标,找到地图需要居中的位置以将明显中心放置在您想要的位置,然后重新居中地图使用真正的中心。

API 将始终使地图居中于视口的中心,因此如果您使用map.getCenter(),则需要小心,因为它会返回真正的中心,而不是明显的中心。我想有可能重载 API,以便替换其 getCenter()setCenter() 方法,但我没有这样做。

代码如下。 Example online。在示例中,单击按钮会将地图中心(那里有一个路口)向下移动 100 像素,向左移动 200 像素。

function offsetCenter(latlng, offsetx, offsety) 

    // latlng is the apparent centre-point
    // offsetx is the distance you want that point to move to the right, in pixels
    // offsety is the distance you want that point to move upwards, in pixels
    // offset can be negative
    // offsetx and offsety are both optional

    var scale = Math.pow(2, map.getZoom());

    var worldCoordinateCenter = map.getProjection().fromLatLngToPoint(latlng);
    var pixelOffset = new google.maps.Point((offsetx/scale) || 0,(offsety/scale) ||0);

    var worldCoordinateNewCenter = new google.maps.Point(
        worldCoordinateCenter.x - pixelOffset.x,
        worldCoordinateCenter.y + pixelOffset.y
    );

    var newCenter = map.getProjection().fromPointToLatLng(worldCoordinateNewCenter);

    map.setCenter(newCenter);


【讨论】:

是否可以在页面加载时触发此代码,以便地图在首次查看时显示为偏移量? @DarrenCook:当然。只需使用它而不是map.setCenter()。这段代码所做的就是重新计算地图被告知要做的事情,然后它自己做map.setCenter() 感谢 Andrew,所以在我的代码中,我使用了 addMarkerFromAdress(latLng, title) 并替换为函数 offsetCenter(latlng,offsetx,offsety) 并且它仍然有效 - 但我如何获得 offsety 的值(243px) 进入函数? 更好用map.panBy(250, 0); 我发现此脚本从未使用过nw var,至少在我的用例中可以毫无问题地删除。我没有自己尝试这个,因为我有很多其他方法已经设置了边界等等。【参考方案2】:

还可以看看 maps 对象上的 panBy(x:number, y:number) 函数。

文档中提到了这个功能:

将地图的中心更改为给定距离(以像素为单位)。如果距离小于地图的宽度和高度,则过渡将平滑动画。请注意,地图坐标系从西向东(对于 x 值)和从北向南(对于 y 值)递增。

就这样使用吧:

mapsObject.panBy(200, 100)

【讨论】:

有谁知道总是禁用此方法的动画? 这一定是答案。它工作得很好,很容易。谢谢。【参考方案3】:

以下是使用地图 API 的 panBy() 方法解决问题的示例:http://jsfiddle.net/upsidown/2wej9smf/

【讨论】:

jsfiddle.net/upsidown/rat1fkkc 这是我的答案编辑类似于先生倒置【参考方案4】:

这里有一个更简单的方法,它可能在响应式设计中更有用,因为您可以使用百分比而不是像素。没有世界坐标,没有点的纬度!

var center;  // a latLng
var offsetX = 0.25; // move center one quarter map width left
var offsetY = 0.25; // move center one quarter map height down

var span = map.getBounds().toSpan(); // a latLng - # of deg map spans

var newCenter =  
    lat: center.lat() + span.lat()*offsetY,
    lng: center.lng() + span.lng()*offsetX
;

map.panTo(newCenter);  // or map.setCenter(newCenter);

【讨论】:

在 InfoBox 上试过这个,它可以工作! (虽然pixelOffset仍需修复) 解决方案的简单性和考虑到响应式设计的绝佳答案 我得到一个未捕获的类型错误:无法读取未定义的属性“toSpan” 我们可以得到 newCenter lat long 吗? 如果需要在同一操作中更改缩放,这将不起作用。此外,如果目的地(新)中心在地理位置上远离当前中心,它也不会准确工作。即使在相同的缩放级别下,屏幕宽度的 0.25 表示赤道处的距离与挪威的距离不同,例如,由于使用了墨卡托投影。投影最终坐标更加稳健。【参考方案5】:

刚刚找到另一个最简单的解决方案。 如果您使用 fitBounds 方法,则可以将可选的第二个参数传递给它。这个参数是填充,在拟合边界时会考虑。

// pass single side:
map.fitBounds(bounds,  left: 1000 )

// OR use Number:
map.fitBounds(bounds, 20)

延伸阅读:official docs.

【讨论】:

这太完美了!【参考方案6】:

安德鲁的就是答案。但是,就我而言, map.getBounds() 一直返回未定义。我修复了它等待 bounds_changed 事件并然后调用函数来偏移中心。像这样:

var center_moved = false;
google.maps.event.addListener(map, 'bounds_changed', function() 
  if(!center_moved)
    offsetCenter(map.getCenter(), 250, -200);
    center_moved = true; 
  
);

【讨论】:

这个答案与(放在之后)安德鲁的答案相结合是我的解决方案。谢谢你纳韦尔!【参考方案7】:

老问题,我知道。但是以 CSS 为中心的方式怎么样?

http://codepen.io/eddyblair/pen/VjpNQQ

我所做的是:

使用overflow: hidden将地图和叠加层包裹在一个容器中

position: absolute覆盖覆盖层

通过设置负值margin-left,将地图的表观宽度扩展为覆盖宽度(加上任何填充和偏移)。

然后为了遵守https://www.google.com/permissions/geoguidelines/attr-guide.html定位小部件和归属divs。

这样地图的中心与所需区域的中心一致。 js只是标准的地图js。

为街景重新定位图标是读者的练习:)


如果您想要左侧的叠加层,只需将行 24 margin-left 更改为 margin-right 并将第 32 行 right 更改为 left

【讨论】:

我真的很喜欢这种方法。保持 Angular/javascript 干净整洁。 谢谢 :) 当然,每当谷歌改变他们的风格时,这将需要更新。我将 cmets 放在移动属性和控件等的 CSS 之前。如果确实需要修改,这应该可以让我了解我是如何做到的。需要创建额外的 CSS 来整理街景。【参考方案8】:

经过大量搜索后,我找不到包括缩放的方法。谢天谢地,clever chap 已经弄清楚了。还有一个fiddle here

'use strict';

const TILE_SIZE = 
  height: 256,
  width: 256
; // google World tile size, as of v3.22
const ZOOM_MAX = 21; // max google maps zoom level, as of v3.22
const BUFFER = 15; // edge buffer for fitting markers within viewport bounds

const mapOptions = 
  zoom: 14,
  center: 
    lat: 34.075328,
    lng: -118.330432
  ,
  options: 
    mapTypeControl: false
  
;
const markers = [];
const mapDimensions = ;
const mapOffset = 
  x: 0,
  y: 0
;
const mapEl = document.getElementById('gmap');
const overlayEl = document.getElementById('overlay');
const gmap = new google.maps.Map(mapEl, mapOptions);

const updateMapDimensions = () => 
  mapDimensions.height = mapEl.offsetHeight;
  mapDimensions.width = mapEl.offsetWidth;
;

const getBoundsZoomLevel = (bounds, dimensions) => 
  const latRadian = lat => 
    let sin = Math.sin(lat * Math.PI / 180);
    let radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
    return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;
  ;
  const zoom = (mapPx, worldPx, fraction) => 
    return Math.floor(Math.log(mapPx / worldPx / fraction) / Math.LN2);
  ;
  const ne = bounds.getNorthEast();
  const sw = bounds.getSouthWest();
  const latFraction = (latRadian(ne.lat()) - latRadian(sw.lat())) / Math.PI;
  const lngDiff = ne.lng() - sw.lng();
  const lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;
  const latZoom = zoom(dimensions.height, TILE_SIZE.height, latFraction);
  const lngZoom = zoom(dimensions.width, TILE_SIZE.width, lngFraction);
  return Math.min(latZoom, lngZoom, ZOOM_MAX);
;

const getBounds = locations => 
  let northeastLat;
  let northeastLong;
  let southwestLat;
  let southwestLong;
  locations.forEach(function(location) 
    if (!northeastLat) 
      northeastLat = southwestLat = location.lat;
      southwestLong = northeastLong = location.lng;
      return;
    
    if (location.lat > northeastLat) northeastLat = location.lat;
    else if (location.lat < southwestLat) southwestLat = location.lat;
    if (location.lng < northeastLong) northeastLong = location.lng;
    else if (location.lng > southwestLong) southwestLong = location.lng;
  );
  const northeast = new google.maps.LatLng(northeastLat, northeastLong);
  const southwest = new google.maps.LatLng(southwestLat, southwestLong);
  const bounds = new google.maps.LatLngBounds();
  bounds.extend(northeast);
  bounds.extend(southwest);
  return bounds;
;

const zoomWithOffset = shouldZoom => 
  const currentzoom = gmap.getZoom();
  const newzoom = shouldZoom ? currentzoom + 1 : currentzoom - 1;
  const offset = 
    x: shouldZoom ? -mapOffset.x / 4 : mapOffset.x / 2,
    y: shouldZoom ? -mapOffset.y / 4 : mapOffset.y / 2
  ;
  const newCenter = offsetLatLng(gmap.getCenter(), offset.x, offset.y);
  if (shouldZoom) 
    gmap.setZoom(newzoom);
    gmap.panTo(newCenter);
   else 
    gmap.setCenter(newCenter);
    gmap.setZoom(newzoom);
  
;

const setMapBounds = locations => 
  updateMapDimensions();
  const bounds = getBounds(locations);
  const dimensions = 
    width: mapDimensions.width - mapOffset.x - BUFFER * 2,
    height: mapDimensions.height - mapOffset.y - BUFFER * 2
  ;
  const zoomLevel = getBoundsZoomLevel(bounds, dimensions);
  gmap.setZoom(zoomLevel);
  setOffsetCenter(bounds.getCenter());
;

const offsetLatLng = (latlng, offsetX, offsetY) => 
  offsetX = offsetX || 0;
  offsetY = offsetY || 0;
  const scale = Math.pow(2, gmap.getZoom());
  const point = gmap.getProjection().fromLatLngToPoint(latlng);
  const pixelOffset = new google.maps.Point((offsetX / scale), (offsetY / scale));
  const newPoint = new google.maps.Point(
    point.x - pixelOffset.x,
    point.y + pixelOffset.y
  );
  return gmap.getProjection().fromPointToLatLng(newPoint);
;

const setOffsetCenter = latlng => 
  const newCenterLatLng = offsetLatLng(latlng, mapOffset.x / 2, mapOffset.y / 2);
  gmap.panTo(newCenterLatLng);
;

const locations = [
  name: 'Wilshire Country Club',
  lat: 34.077796,
  lng: -118.331151
, 
  name: '301 N Rossmore Ave',
  lat: 34.077146,
  lng: -118.327805
, 
  name: '5920 Beverly Blvd',
  lat: 34.070281,
  lng: -118.331831
];

locations.forEach(function(location) 
  let marker = new google.maps.Marker(
    position: new google.maps.LatLng(location.lat, location.lng),
    title: location.name
  )
  marker.setMap(gmap);
  markers.push(marker);
);

mapOffset.x = overlayEl.offsetWidth;

document.zoom = bool => zoomWithOffset(bool);
document.setBounds = () => setMapBounds(locations);
section 
  height: 180px;
  margin-bottom: 15px;
  font-family: sans-serif;
  color: grey;

figure 
  position: relative;
  margin: 0;
  width: 100%;
  height: 100%;

figcaption 
  position: absolute;
  left: 15px;
  top: 15px;
  width: 120px;
  padding: 15px;
  background: white;
  box-shadow: 0 2px 5px rgba(0, 0, 0, .3);

gmap 
  display: block;
  height: 100%;
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js"></script>

<section>
  <figure>
    <gmap id="gmap"></gmap>
    <figcaption id="overlay">
      <h4>Tile Overlay</h4>
      <p>To be avoided by the map!</p>
    </figcaption>
  </figure>
</section>
<button onclick="zoom(true)">zoom in</button>
<button onclick="zoom(false)">zoom out</button>
<button onclick="setBounds()">set bounds</button>

【讨论】:

【参考方案9】:

可以在此处找到偏移路线或一组标记的另一种方法:

https://***.com/a/26192440/1238965

它仍然使用@Andrew Leach 答案中描述的fromLatLngToPoint() 方法。

【讨论】:

这比 IMO 接受的答案更复杂。【参考方案10】:

以下是 SIMPLE 用于:

的示例 对于 DESKTOP 版本,当 左侧OVERLAY 且宽度为 500 对于MOBILE版本,当页面底部OVERLAY且高度为250

当屏幕尺寸实时变化时,该解决方案也能顺利运行。

要求 querycss 中定义为 @media screen and (min-width: 1025px)@media screen and (max-width: 1024px)

代码:

function initMap() 
    map = new google.maps.Map(document.getElementById("map"), 
        zoom: 13
    );

    bounds = new google.maps.LatLngBounds();
    // add markers to bounds

    // Initial bounds
    defineBounds()

    // Trigger resize browser and set new bounds
    google.maps.event.addDomListener(window, 'resize', function() 
        defineBounds()
    );


function defineBounds() 
    if (window.matchMedia("(min-width:1025px)").matches) 
        map.fitBounds(bounds, 
            top: 100,
            right: 100,
            left: 600, // 500 + 100
            bottom: 100
        );
     else 
        map.fitBounds(bounds, 
            top: 80,
            right: 80,
            left: 80,
            bottom: 330 // 250 + 80
        );
    

可以为位于屏幕两侧的 OVERLAY 自定义解决方案

【讨论】:

以上是关于如何在谷歌地图 api V3 中偏移中心点的主要内容,如果未能解决你的问题,请参考以下文章

如何设置Map(),setVisible()4000/5000的标记而不会在谷歌地图api V3中失去性能?

在谷歌地图 api v3 上旋转 .gif 图像?

如何在谷歌地图中获取标记和中心之间的距离

在谷歌地图 api v3 中将地区、城市、州、国家/地区转换为纬度和经度?

谷歌地图 API V3 fitBounds() 不工作

如何在谷歌地图的屏幕中心设置标记