当位置跨越180度子午线时,如何使用LocationRect.fromLocations()正确获取边界框?

Che*_*eso 5 javascript bing-maps

我正在使用v7 Bing Maps Javascript"控件"(我不知道为什么它被称为"控件"......).我正在打电话Microsoft.Maps.Map.setView({bounds: bounds}),它没有按照我的期望或愿望工作.

我有一组多边形,其点数跨越第180个子午线.一个例子是新西兰岛屿的边界 - 其中一些位于第180个子午线以西,一些部分(查塔姆岛)位于东部.

当我创建一个带有这些边界和调用的多边形时setView(),地图会放大waaaaaay.

在此输入图像描述

为什么?以及如何避免它?


此页面提供了该问题的演示.

这是代码.

var map, MM = Microsoft.Maps;

function showMap(m) {
  var options = {
    mapTypeId: MM.MapTypeId.road // aerial,
    // center will be recalculated
    // zoom will be recalculated
  },
  map1 = new MM.Map(m, options);
  return map1;
}

function doubleclickCallback(e) {
  e.handled = true;
  var bounds = map.getBounds();
  map.setView({ bounds: bounds });
}

function init() {
  var mapDiv = document.getElementById("map1");
    map = showMap(mapDiv);

  MM.Events.addHandler(map, "dblclick", doubleclickCallback); 
}
Run Code Online (Sandbox Code Playgroud)

如果双击地图中没有第180个子午线的地图,则不会发生任何事情.如果在地图显示第180个子午线时双击,则地图将重置为缩放级别1.

Che*_*eso 11

我调查了这个.

特别是我查看了veapicore.js,版本7.0.20120123200232.91,可在

http://ecn.dev.virtualearth.net/mapcontrol/v7.0/js/bin/7.0.20120123200232.91/en-us/veapicore.js

当您包含bing maps控件时,将下载此模块,如下所示:

<script charset="UTF-8" type="text/javascript"
        src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0">
</script>
Run Code Online (Sandbox Code Playgroud)

我发现我认为是两个截然不同的问题.

•当LocationRect的边界框跨越第180个子午线时,MapMath.locationRectToMercatorZoom函数(内部函数,不适合应用程序直接使用)始终返回1的缩放.这是不正确的.Map.setView()函数使用此函数自动设置缩放,这会导致缩放等待现象.

•LocationRect.fromLocations()使用简单的方法来确定一组位置的边界框.实际上,它不能保证是"最小边界框"或"最小边界矩形".据我所知,从该方法返回的框永远不会跨越第180个子午线.例如,为一组代表新西兰岛屿边界的位置返回的LocationRect将从-176纬度开始向东伸展,一直到+ 165th子午线.这是错的.

我通过修补veapicore.js中的代码修复了这些问题.

function monkeyPatchMapMath() {
  Microsoft.Maps.InternalNamespaceForDelay.MapMath.
    locationRectToMercatorZoom = function (windowDimensions, bounds) {
      var ins = Microsoft.Maps.InternalNamespaceForDelay,
        d = windowDimensions,
        g = Microsoft.Maps.Globals,
        n = bounds.getNorth(),
        s = bounds.getSouth(),
        e = bounds.getEast(),
        w = bounds.getWest(),
        f = ((e+360 - w) % 360)/360,
        //f = Math.abs(w - e) / 360,
        u = Math.abs(ins.MercatorCube.latitudeToY(n) -
                     ins.MercatorCube.latitudeToY(s)),
        r = Math.min(d.width / (g.zoomOriginWidth * f),
                     d.height / (g.zoomOriginWidth * u));
      return ins.VectorMath.log2(r);
    };
}



function monkeyPatchFromLocations() {
  Microsoft.Maps.LocationRect.fromLocations = function () {
    var com = Microsoft.Maps.InternalNamespaceForDelay.Common,
      o = com.isArray(arguments[0]) ? arguments[0] : arguments,
      latMax, latMin, lngMin1, lngMin2, lngMax1, lngMax2, c,
      lngMin, lngMax, LL, dx1, dx2,
      pt = Microsoft.Maps.AltitudeReference,
      s, e, n, f = o.length;

    while (f--)
      n = o[f],
    isFinite(n.latitude) && isFinite(n.longitude) &&
      (latMax = latMax === c ? n.latitude : Math.max(latMax, n.latitude),
       latMin = latMin === c ? n.latitude : Math.min(latMin, n.latitude),
       lngMax1 = lngMax1 === c ? n.longitude : Math.max(lngMax1, n.longitude),
       lngMin1 = lngMin1 === c ? n.longitude : Math.min(lngMin1, n.longitude),
       LL = n.longitude,
       (LL < 0) && (LL += 360),
       lngMax2 = lngMax2 === c ? LL : Math.max(lngMax2, LL),
       lngMin2 = lngMin2 === c ? LL : Math.min(lngMin2, LL),
       isFinite(n.altitude) && pt.isValid(n.altitudeReference) &&
       (e = n.altitude, s = n.altitudeReference));

    dx1 = lngMax1 - lngMin1,
    dx2 = lngMax2 - lngMin2,
    lngMax = (dx1 > dx2) ? lngMax2 : lngMax1,
    lngMin = (dx1 > dx2) ? lngMin2 : lngMin1;

    return Microsoft.Maps.LocationRect.fromEdges(latMax, lngMin, latMin, lngMax, e, s);
  };
}
Run Code Online (Sandbox Code Playgroud)

这些函数需要在使用前调用一次,但在加载后.我认为第一个是延迟加载的,所以你不能在文件准备好的情况下进行猴子修补; 你需要等到你创建了一个Microsoft.Maps.Map.

给定一个LocationRect,第一个做正确的事情.在矩形跨越第180个子午线的情况下,原始方法翻转东西边缘.

第二个函数修复了该fromLocations方法.原始实现遍历所有位置,并将最小经度设为"左",将最大经度设为"右".当最小经度位于第180个子午线的东边(例如,-178)时,这会失败,并且最大经度值恰好位于同一直线的西边(例如,+ 165).得到的边界框应该跨越第180个子午线,但实际上使用这种天真的方法计算出来的值很长.

更正的实现计算该框,并计算第二个边界框.对于第二个,当经度为负时,它使用经度值或经度+ 360,而不是使用经度值.生成的变换将经度从-180到180的值更改为范围从0到360的值.然后该函数计算该新值集的最大值和最小值.

结果是两个边界框:一个具有从-180到+180的经度,另一个具有从0到360的经度.这些框将具有不同的宽度.

固定实现选择宽度较窄的框,猜测较小的框是正确的答案.如果您尝试计算一组大于地球一半的点的边界框,则此启发式算法将会中断.

示例用法可能如下所示:

monkeyPatchFromLocations();
bounds = Microsoft.Maps.LocationRect.fromLocations(allPoints);
monkeyPatchMapMath();
map1.setView({bounds:bounds});
Run Code Online (Sandbox Code Playgroud)

该页面演示了:http://jsbin.com/emobav/4

双击地图永远不会导致缩放效果,如http://jsbin.com/emobav/2中所示