Как правильно получить ограничивающий прямоугольник, используя LocationRect.fromLocations (), когда местоположения охватывают 180-й меридиан? - PullRequest
5 голосов
/ 02 марта 2012

Я использую V7 Bing Maps "control" (не знаю, почему он называется "control" ...). Я звоню Microsoft.Maps.Map.setView({bounds: bounds}), и это не работает, как я ожидал или хотел.

У меня есть набор полигонов с точками, которые охватывают 180-й меридиан. Примером является граница островов Новой Зеландии - некоторые из них находятся к западу от 180-го меридиана, некоторые части (острова Чатем) - восточнее.

Когда я создаю полигон с этими границами и вызываю setView(), карта увеличивает waaaaaay out.

enter image description here

Почему? и как этого избежать?


На этой странице приведена демонстрация проблемы.

Вот код.

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 ]

11 голосов
/ 02 марта 2012

Я посмотрел на это.

В частности, я посмотрел в 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, напримерэто:

<script charset="UTF-8" type="text/javascript"
        src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0">
</script>

Я обнаружил две разные проблемы.

• Функция MapMath.locationRectToMercatorZoom (внутренняя функция, не предназначенная для непосредственного использования приложениями) всегда возвращает масштаб 1, когда ограничивающий прямоугольник LocationRect охватывает 180-й меридиан.Это неверноЭта функция используется функцией 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 голосов
/ 27 марта 2012

Возможно, гораздо более простой подход.

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

LocationRect.fromLocations принимает список местоположений / массив

Ваш полигон, который вы обернули вокруг Австралии, содержит функцию для возврата массива местоположений с именем 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);

Тогда в вашем двойном клике:

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});
}
1 голос
/ 03 июля 2013

это исправлено в veapicore.js как минимум v7.0 / 7.0.20130619132259.11

...