谷歌地图 V3-如何计算给定边界的缩放水平

我正在寻找一种使用 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
176533 次浏览

类似的问题也出现在了谷歌的小组 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);

谢谢,这帮助我找到最合适的缩放因子,以正确显示折线很多。 我找到了需要跟踪的点之间的最大和最小坐标,并且,如果路径非常“垂直”,我只是添加了几行代码:

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);

实际上,理想的缩放因子是缩放因子 -1。

瓦莱里奥的解决方案几乎是正确的,但存在一些逻辑错误。

你必须首先检查角度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 在那里,因为我的宽度比高度大。

map.getBounds()不是瞬间操作,所以我在类似的事件处理中使用它。以下是我在 Coffeescript 的例子

@map.fitBounds(@bounds)
google.maps.event.addListenerOnce @map, 'bounds_changed', =>
@map.setZoom(12) if @map.getZoom() > 12

感谢贾尔斯 · 加达姆的回答,但它只涉及经度,而不涉及纬度。一个完整的解决方案应该计算纬度所需的缩放级别和经度所需的缩放级别,然后从两者中取出较小的(进一步取出)。

下面是一个同时使用两个经纬度的函数:

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);
}

Demo on jsfiddle

参数:

“ bound”参数值应该是一个 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。(我相信在谷歌地图 API v2中只有19。)


说明:

谷歌地图使用麦卡托投影法。在麦卡托投影法中,经线之间的距离相等,而纬线之间的距离相等。纬线之间的距离随着它们从赤道到两极而增加。事实上,当它到达两极时,距离趋向于无穷远。然而,谷歌地图并没有显示北纬约85度以上或南纬约 -85度以下的纬度。(参考文献)(我计算的实际截止温度是 +/-85.05112877980658度。)

这使得在纬度范围内计算分数比在经度范围内计算分数更加复杂。我用 来自维基百科的公式来计算纬度分数。我假设这与谷歌地图使用的投影相匹配。毕竟,我上面链接到的谷歌地图文档页面包含到同一维基百科页面的链接。

其他注释:

  1. 缩放等级范围从0到最大缩放等级。缩放等级0是指地图被完全缩小。更高的级别放大地图进一步。(参考文献)
  2. 在缩放级别为0时,整个世界可以显示在一个256x256像素的区域内
  3. 对于每个较高的缩放级别,显示相同区域所需的像素数在宽度和高度上都加倍。(参考文献)
  4. 地图是纵向包裹的,但不是纬向包裹的。

由于所有其他的答案似乎对我有这样或那样的情况(贴图宽度/高度,边界宽度/高度等)的问题,我想我应该把我的答案放在这里..。

这里有一个非常有用的 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)。

对于 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);
}

工作示例查找平均默认中心与反应谷歌地图在 ES6:

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>

贾尔斯加达姆经度变焦水平的计算对我来说很好。 如果你想计算纬度的缩放因子,这是一个简单的解决方案,工作正常:

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);

下面是 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) / 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)
}

快速版

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)
}

飞镖版本:

  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);
}

所有被高度赞同的答案对我都不起作用。他们抛出各种未定义的错误,最终计算 inf/nan 的角度。我怀疑 LatLngBound 的行为随着时间的推移而改变。无论如何,我发现这个代码可以满足我的需要,也许它可以帮助某人:

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)


}

计算缩放级别以显示包括该区域的两个交叉角的地图,并在屏幕具有特定高度的部分上显示该地图。

两个坐标 Max lat/long 最少/最长

以像素为单位的显示区域 身高

      double getZoomLevelNew(context,
double maxLat, double maxLong,
double minLat, double minLong,
double height){
try {
double _zoom;
MediaQueryData queryData2;
queryData2 = MediaQuery.of(context);
double _zLat =
Math.log(
(globals.factor(height) / queryData2.devicePixelRatio / 256.0) *
180 / (maxLat - minLat).abs()) / Math.log(2);
double _zLong =
Math.log((globals.factor(MediaQuery
.of(context)
.size
.width) / queryData2.devicePixelRatio / 256.0) * 360 /
(maxLong - minLong).abs()) / Math.log(2);
_zoom = Math.min(_zLat, _zLong)*globals.zoomFactorNew;
if (_zoom < 0) {
_zoom = 0;
}
return _zoom;
} catch(e){
print("getZoomLevelNew - excep - " + e.toString());
}