如何在谷歌地图 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定位小部件和归属div
s。
这样地图的中心与所需区域的中心一致。 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
当屏幕尺寸实时变化时,该解决方案也能顺利运行。
要求 query 在 css 中定义为 @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中失去性能?