【问题标题】:How to correctly get the bounding box using LocationRect.fromLocations() when locations span 180th meridian?当位置跨越第 180 个子午线时,如何使用 LocationRect.fromLocations() 正确获取边界框?
【发布时间】:2012-03-02 05:54:02
【问题描述】:

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

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

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

为什么?以及如何避免?


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。

【问题讨论】:

    标签: javascript bing-maps


    【解决方案1】:

    我对此进行了调查。

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

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

    当你包含必应地图控件时,会下载此模块,如下所示:

    <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 度开始向东延伸,一直到 +165 度子午线。这是错误的。

    我通过猴子修补 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

    双击地图不会导致缩小效果,如http://jsbin.com/emobav/2

    【讨论】:

      【解决方案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});
      }
      

      【讨论】:

      • 这在跨越 180 时不起作用吗?我不明白为什么它不会...克里斯,它不起作用,因为 Microsoft 地图库中有一个错误。阅读我在回答中写的内容。通过传递跨越 180 的任何点集很容易演示该错误。他们用来计算边界框或中心的算法是幼稚的。阅读我的回答,我解释了这一切。
      • @Cheeso 第二个函数修复了 fromLocations 方法。 抱歉,第一次阅读时我没有发现 fromLocations 也有问题。太糟糕了,因为那会使 getLocations() 成为一个很好的候选人!太棒了,你找到并修复奶酪,好一个!
      【解决方案3】:

      从 v7.0/7.0.20130619132259.11 开始,这似乎已在 veapicore.js 中修复

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-03-19
        • 2021-09-28
        • 2015-03-26
        • 1970-01-01
        • 2021-04-16
        • 1970-01-01
        相关资源
        最近更新 更多