我正在寻找一种使用Google Maps V3 API计算给定边界的缩放级别的方法,类似于V2 API中的getBoundsZoomLevel()
。
这是我想要做的:
// These are exact bounds previously captured from the map object
var sw = new google.maps.LatLng(42.763479, -84.338918);
var ne = new google.maps.LatLng(42.679488, -84.524313);
var bounds = new google.maps.LatLngBounds(sw, ne);
var zoom = // do some magic to calculate the zoom level
// Set the map to these exact bounds
map.setCenter(bounds.getCenter());
map.setZoom(zoom);
// NOTE: fitBounds() will not work
不幸的是,我不能将fitBounds()
方法用于我的特定用例。它适用于在地图上拟合标记,但它不适用于设置精确边界。以下是我无法使用fitBounds()
方法的示例。
map.fitBounds(map.getBounds()); // not what you expect
答案 0 :(得分:247)
感谢Giles Gardam的回答,但它只涉及经度,而不是纬度。一个完整的解决方案应该计算纬度所需的缩放级别和经度所需的缩放级别,然后取两者中较小的(更远的)。
这是一个同时使用纬度和经度的函数:
function getBoundsZoomLevel(bounds, mapDim) {
var WORLD_DIM = { height: 256, width: 256 };
var ZOOM_MAX = 21;
function latRad(lat) {
var sin = Math.sin(lat * Math.PI / 180);
var radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;
}
function zoom(mapPx, worldPx, fraction) {
return Math.floor(Math.log(mapPx / worldPx / fraction) / Math.LN2);
}
var ne = bounds.getNorthEast();
var sw = bounds.getSouthWest();
var latFraction = (latRad(ne.lat()) - latRad(sw.lat())) / Math.PI;
var lngDiff = ne.lng() - sw.lng();
var lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;
var latZoom = zoom(mapDim.height, WORLD_DIM.height, latFraction);
var lngZoom = zoom(mapDim.width, WORLD_DIM.width, lngFraction);
return Math.min(latZoom, lngZoom, ZOOM_MAX);
}
参数:
“bounds”参数值应为google.maps.LatLngBounds
对象。
“mapDim”参数值应该是具有“height”和“width”属性的对象,这些属性表示显示地图的DOM元素的高度和宽度。如果要确保填充,可能需要减少这些值。也就是说,您可能不希望边界内的地图标记太靠近地图边缘。
如果您使用的是jQuery库,则可以按如下方式获取mapDim
值:
var $mapDiv = $('#mapElementId');
var mapDim = { height: $mapDiv.height(), width: $mapDiv.width() };
如果您使用的是Prototype库,则可以按如下方式获取mapDim值:
var mapDim = $('mapElementId').getDimensions();
返回值:
返回值是仍将显示整个边界的最大缩放级别。此值介于0
和最大缩放级别之间,包括在内。
最大缩放级别为21.(我相信Google Maps API v2只有19级。)
说明:
Google地图使用墨卡托投影。在墨卡托投影中,经度线的间距相等,但纬度线不是。纬度线之间的距离随着它们从赤道到极点的增加而增加。实际上,当距离到达极点时,距离趋于无穷大。但是,谷歌地图地图并未显示北纬约85度以上或约低于-85度的纬度。 (reference)(我计算实际截止值为+/- 85.05112877980658度。)
这使得边界的分数的计算对于纬度而言比对于经度更复杂。我使用formula from Wikipedia来计算纬度分数。我假设这与Google地图使用的投影相匹配。毕竟,我链接到上面的Google Maps文档页面包含指向同一维基百科页面的链接。
其他注释:
答案 1 :(得分:101)
Google小组也提出了类似的问题:http://groups.google.com/group/google-maps-js-api-v3/browse_thread/thread/e6448fc197c3c892
缩放级别是离散的,每个步骤的比例加倍。所以一般来说你不能完全符合你想要的界限(除非你对特定的地图大小非常幸运)。
另一个问题是边长之间的比例,例如:你不能将边界精确地拟合到方形图中的细长矩形。
对于如何拟合精确边界没有简单的答案,因为即使你愿意改变地图div的大小,你也必须选择你改变的大小和相应的缩放级别(粗略地说,你做到了吗?比现在更大还是更小?)。
如果你真的需要计算缩放,而不是存储它,这应该可以解决问题:
墨卡托投影会扭曲纬度,但经度上的任何差异始终代表地图宽度的相同部分(角度差以度/ 360为单位)。在缩放零点处,整个世界地图是256x256像素,并且每个级别的缩放都是宽度和高度的两倍。因此,在我们知道地图的宽度(以像素为单位)之后,我们可以在一个小代数之后计算缩放,如下所示。请注意,因为经度缠绕,我们必须确保角度为正。
var GLOBE_WIDTH = 256; // a constant in Google's map projection
var west = sw.lng();
var east = ne.lng();
var angle = east - west;
if (angle < 0) {
angle += 360;
}
var zoom = Math.round(Math.log(pixelWidth * 360 / angle / GLOBE_WIDTH) / Math.LN2);
答案 2 :(得分:34)
对于API的第3版,这很简单且有效:
var latlngList = [];
latlngList.push(new google.maps.LatLng(lat, lng));
var bounds = new google.maps.LatLngBounds();
latlngList.each(function(n) {
bounds.extend(n);
});
map.setCenter(bounds.getCenter()); //or use custom center
map.fitBounds(bounds);
和一些可选的技巧:
//remove one zoom level to ensure no marker is on the edge.
map.setZoom(map.getZoom() - 1);
// set a minimum zoom
// if you got only 1 marker or all markers are on the same address map will be zoomed too much.
if(map.getZoom() > 15){
map.setZoom(15);
}
答案 3 :(得分:4)
此功能的Kotlin版本:
fun getBoundsZoomLevel(bounds: LatLngBounds, mapDim: Size): Double {
val WORLD_DIM = Size(256, 256)
val ZOOM_MAX = 21.toDouble();
fun latRad(lat: Double): Double {
val sin = Math.sin(lat * Math.PI / 180);
val radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
return max(min(radX2, Math.PI), -Math.PI) /2
}
fun zoom(mapPx: Int, worldPx: Int, fraction: Double): Double {
return floor(Math.log(mapPx / worldPx / fraction) / Math.log(2.0))
}
val ne = bounds.northeast;
val sw = bounds.southwest;
val latFraction = (latRad(ne.latitude) - latRad(sw.latitude)) / Math.PI;
val lngDiff = ne.longitude - sw.longitude;
val lngFraction = if (lngDiff < 0) { (lngDiff + 360) } else { (lngDiff / 360) }
val latZoom = zoom(mapDim.height, WORLD_DIM.height, latFraction);
val lngZoom = zoom(mapDim.width, WORLD_DIM.width, lngFraction);
return minOf(latZoom, lngZoom, ZOOM_MAX)
}
答案 4 :(得分:1)
谢谢,这对我找到最合适的缩放系数以正确显示折线有很大帮助。 我找到了我必须跟踪的点之间的最大和最小坐标,如果路径非常“垂直”,我只添加了几行代码:
var GLOBE_WIDTH = 256; // a constant in Google's map projection
var west = <?php echo $minLng; ?>;
var east = <?php echo $maxLng; ?>;
*var north = <?php echo $maxLat; ?>;*
*var south = <?php echo $minLat; ?>;*
var angle = east - west;
if (angle < 0) {
angle += 360;
}
*var angle2 = north - south;*
*if (angle2 > angle) angle = angle2;*
var zoomfactor = Math.round(Math.log(960 * 360 / angle / GLOBE_WIDTH) / Math.LN2);
实际上,理想的缩放系数是zoomfactor-1。
答案 5 :(得分:1)
由于所有其他答案似乎对我有一个或另一组情况(地图宽度/高度,边界宽度/高度等)有问题,我想我会把我的答案放在这里......
这里有一个非常有用的javascript文件:http://www.polyarc.us/adjust.js
我用它作为基础:
var com = com || {};
com.local = com.local || {};
com.local.gmaps3 = com.local.gmaps3 || {};
com.local.gmaps3.CoordinateUtils = new function() {
var OFFSET = 268435456;
var RADIUS = OFFSET / Math.PI;
/**
* Gets the minimum zoom level that entirely contains the Lat/Lon bounding rectangle given.
*
* @param {google.maps.LatLngBounds} boundary the Lat/Lon bounding rectangle to be contained
* @param {number} mapWidth the width of the map in pixels
* @param {number} mapHeight the height of the map in pixels
* @return {number} the minimum zoom level that entirely contains the given Lat/Lon rectangle boundary
*/
this.getMinimumZoomLevelContainingBounds = function ( boundary, mapWidth, mapHeight ) {
var zoomIndependentSouthWestPoint = latLonToZoomLevelIndependentPoint( boundary.getSouthWest() );
var zoomIndependentNorthEastPoint = latLonToZoomLevelIndependentPoint( boundary.getNorthEast() );
var zoomIndependentNorthWestPoint = { x: zoomIndependentSouthWestPoint.x, y: zoomIndependentNorthEastPoint.y };
var zoomIndependentSouthEastPoint = { x: zoomIndependentNorthEastPoint.x, y: zoomIndependentSouthWestPoint.y };
var zoomLevelDependentSouthEast, zoomLevelDependentNorthWest, zoomLevelWidth, zoomLevelHeight;
for( var zoom = 21; zoom >= 0; --zoom ) {
zoomLevelDependentSouthEast = zoomLevelIndependentPointToMapCanvasPoint( zoomIndependentSouthEastPoint, zoom );
zoomLevelDependentNorthWest = zoomLevelIndependentPointToMapCanvasPoint( zoomIndependentNorthWestPoint, zoom );
zoomLevelWidth = zoomLevelDependentSouthEast.x - zoomLevelDependentNorthWest.x;
zoomLevelHeight = zoomLevelDependentSouthEast.y - zoomLevelDependentNorthWest.y;
if( zoomLevelWidth <= mapWidth && zoomLevelHeight <= mapHeight )
return zoom;
}
return 0;
};
function latLonToZoomLevelIndependentPoint ( latLon ) {
return { x: lonToX( latLon.lng() ), y: latToY( latLon.lat() ) };
}
function zoomLevelIndependentPointToMapCanvasPoint ( point, zoomLevel ) {
return {
x: zoomLevelIndependentCoordinateToMapCanvasCoordinate( point.x, zoomLevel ),
y: zoomLevelIndependentCoordinateToMapCanvasCoordinate( point.y, zoomLevel )
};
}
function zoomLevelIndependentCoordinateToMapCanvasCoordinate ( coordinate, zoomLevel ) {
return coordinate >> ( 21 - zoomLevel );
}
function latToY ( lat ) {
return OFFSET - RADIUS * Math.log( ( 1 + Math.sin( lat * Math.PI / 180 ) ) / ( 1 - Math.sin( lat * Math.PI / 180 ) ) ) / 2;
}
function lonToX ( lon ) {
return OFFSET + RADIUS * lon * Math.PI / 180;
}
};
如果需要,你当然可以清理它或缩小它,但我保留变量名长,以便更容易理解。
如果你想知道OFFSET的来源,显然268435456是缩放级别21的地球周长的一半(根据http://www.appelsiini.net/2008/11/introduction-to-marker-clustering-with-google-maps)。
答案 6 :(得分:0)
Valerio的解决方案几乎是正确的,但存在一些逻辑错误。
你必须首先检查角度2是否大于角度,然后在负数下加上360。
否则你总是有一个比角度更大的值
所以正确的解决方案是:
var west = calculateMin(data.longitudes);
var east = calculateMax(data.longitudes);
var angle = east - west;
var north = calculateMax(data.latitudes);
var south = calculateMin(data.latitudes);
var angle2 = north - south;
var zoomfactor;
var delta = 0;
var horizontal = false;
if(angle2 > angle) {
angle = angle2;
delta = 3;
}
if (angle < 0) {
angle += 360;
}
zoomfactor = Math.floor(Math.log(960 * 360 / angle / GLOBE_WIDTH) / Math.LN2) - 2 - delta;
Delta就在那里,因为我的宽度大于高度。
答案 7 :(得分:0)
map.getBounds()
不是瞬间操作,所以我在类似的情况下使用事件处理程序。这是我在Coffeescript中的例子
@map.fitBounds(@bounds)
google.maps.event.addListenerOnce @map, 'bounds_changed', =>
@map.setZoom(12) if @map.getZoom() > 12
答案 8 :(得分:0)
使用ES6
上的react-google-maps查找平均默认中心的工作示例:
const bounds = new google.maps.LatLngBounds();
paths.map((latLng) => bounds.extend(new google.maps.LatLng(latLng)));
const defaultCenter = bounds.getCenter();
<GoogleMap
defaultZoom={paths.length ? 12 : 4}
defaultCenter={defaultCenter}
>
<Marker position={{ lat, lng }} />
</GoogleMap>
答案 9 :(得分:0)
Giles Gardam经度的缩放级别的计算对我来说很好。 如果您想计算纬度的缩放系数,这是一个很好的简单解决方案:
double minLat = ...;
double maxLat = ...;
double midAngle = (maxLat+minLat)/2;
//alpha is the non-negative angle distance of alpha and beta to midangle
double alpha = maxLat-midAngle;
//Projection screen is orthogonal to vector with angle midAngle
//portion of horizontal scale:
double yPortion = Math.sin(alpha*Math.pi/180) / 2;
double latZoom = Math.log(mapSize.height / GLOBE_WIDTH / yPortion) / Math.ln2;
//return min (max zoom) of both zoom levels
double zoom = Math.min(lngZoom, latZoom);
答案 10 :(得分:0)
快速版
func getBoundsZoomLevel(bounds: GMSCoordinateBounds, mapDim: CGSize) -> Double {
var bounds = bounds
let WORLD_DIM = CGSize(width: 256, height: 256)
let ZOOM_MAX: Double = 21.0
func latRad(_ lat: Double) -> Double {
let sin2 = sin(lat * .pi / 180)
let radX2 = log10((1 + sin2) / (1 - sin2)) / 2
return max(min(radX2, .pi), -.pi) / 2
}
func zoom(_ mapPx: CGFloat,_ worldPx: CGFloat,_ fraction: Double) -> Double {
return floor(log10(Double(mapPx) / Double(worldPx) / fraction / log10(2.0)))
}
let ne = bounds.northEast
let sw = bounds.southWest
let latFraction = (latRad(ne.latitude) - latRad(sw.latitude)) / .pi
let lngDiff = ne.longitude - sw.longitude
let lngFraction = lngDiff < 0 ? (lngDiff + 360) : (lngDiff / 360)
let latZoom = zoom(mapDim.height, WORLD_DIM.height, latFraction);
let lngZoom = zoom(mapDim.width, WORLD_DIM.width, lngFraction);
return min(latZoom, lngZoom, ZOOM_MAX)
}
答案 11 :(得分:0)
Dart版本:
double latRad(double lat) {
final double sin = math.sin(lat * math.pi / 180);
final double radX2 = math.log((1 + sin) / (1 - sin)) / 2;
return math.max(math.min(radX2, math.pi), -math.pi) / 2;
}
double getMapBoundZoom(LatLngBounds bounds, double mapWidth, double mapHeight) {
final LatLng northEast = bounds.northEast;
final LatLng southWest = bounds.southWest;
final double latFraction = (latRad(northEast.latitude) - latRad(southWest.latitude)) / math.pi;
final double lngDiff = northEast.longitude - southWest.longitude;
final double lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;
final double latZoom = (math.log(mapHeight / 256 / latFraction) / math.ln2).floorToDouble();
final double lngZoom = (math.log(mapWidth / 256 / lngFraction) / math.ln2).floorToDouble();
return math.min(latZoom, lngZoom);
}
答案 12 :(得分:0)
没有一个被高度评价的答案对我有用。他们抛出了各种不确定的错误,最终计算出角度的inf / nan。我怀疑LatLngBounds的行为可能随着时间而改变。无论如何,我发现这段代码可以满足我的需求,也许可以帮助某人:
function latRad(lat) {
var sin = Math.sin(lat * Math.PI / 180);
var radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;
}
function getZoom(lat_a, lng_a, lat_b, lng_b) {
let latDif = Math.abs(latRad(lat_a) - latRad(lat_b))
let lngDif = Math.abs(lng_a - lng_b)
let latFrac = latDif / Math.PI
let lngFrac = lngDif / 360
let lngZoom = Math.log(1/latFrac) / Math.log(2)
let latZoom = Math.log(1/lngFrac) / Math.log(2)
return Math.min(lngZoom, latZoom)
}