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

时间:2012-03-02 05:54:02

标签: javascript bing-maps

我正在使用v7 Bing Maps Javascript“控件”(我不知道为什么它被称为“控件”......)。我正在打电话给Microsoft.Maps.Map.setView({bounds: bounds}),但它并没有像我期望的那样工作。

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

当我使用这些边界创建一个多边形并调用setView()时,地图会缩小 waaaaaay

enter image description here

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


This page提供了问题的演示。

这是代码。

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

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

3 个答案:

答案 0 :(得分: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>

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

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

这些功能在使用前需要被称为一次,但是在加载之后。我认为第一个是延迟加载的,所以你不能在文件准备好的情况下进行猴子修补;您需要等到创建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});

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

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

答案 1 :(得分:2)

也许是一种更简单的方法。

bounds = Microsoft.Maps.LocationRect.fromLocations(allPoints);

LocationRect.fromLocations accepts a list of locations / array

你在澳大利亚缠绕的多边形包含一个函数来返回一个名为getLocations()的位置数组。

似乎可以像这样调用它(双重检查语法);

var viewBoundaries = Microsoft.Maps.LocationRect.fromLocations(polygon.getLocations());

                      map.setView({ bounds: viewBoundaries });
                      map.setView({ zoom: 10 });

跨越180时这不起作用吗?我不明白为什么它不会,因为它只是使用多边形中的点。如果是这样,以下对您来说可能是一个非常简单的解决方案。

你说每当你双击地图时,就会平移地图。这很有意义,因为您只向地图添加了一个事件处理程序,并在以下代码中将边界设置为地图的边界:

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

MM.Events.addHandler(map, "dblclick", doubleclickCallback); 

我认为你需要将你的点击处理程序添加到多边形,以便将视图跨越到指定的多边形。

Microsoft.Maps.Events.addHandler(polygon, 'click', doubleclickCallback);

然后在你的doubleclickCallback中:

function doubleclickCallback(e) {
  // Now we are getting the boundaries of our polygon
  var bounds = e.target.getLocations();
  map.setView({ bounds: bounds });
  map.setView({ zoom: 9});
}

答案 2 :(得分:1)

至少在v7.0 / 7.0.20130619132259.11中,这似乎在veapicore.js中修复了