Google Maps V3 - Как рассчитать уровень масштабирования для заданных границ - PullRequest
128 голосов
/ 18 мая 2011

Я ищу способ расчета уровня масштабирования для заданных границ с помощью API Карт Google V3, аналогично getBoundsZoomLevel() в API V2.

Вот что я хочу сделать:

// 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

Ответы [ 8 ]

252 голосов
/ 07 ноября 2012

Спасибо Giles Gardam за его ответ, но он касается только долготы, а не широты. Полное решение должно рассчитать уровень масштабирования, необходимый для широты, и уровень масштабирования, необходимый для долготы, а затем взять меньшее (дальше) из двух.

Вот функция, которая использует широту и долготу:

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

Демонстрация на jsfiddle

Параметры:

Значением параметра "bounds" должен быть объект 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. (Я полагаю, что это был только 19 для Google Maps API v2.)


Пояснение:

Карты Google используют проекцию Меркатора. В проекции Меркатора линии долготы расположены на одинаковом расстоянии, а линии широты - нет. Расстояние между линиями широты увеличивается по мере их продвижения от экватора к полюсам. На самом деле расстояние стремится к бесконечности, когда достигает полюсов. Карта Google Карты, однако, не показывает широты выше приблизительно 85 градусов северной широты или ниже -85 градусов южной широты. ( ссылка ) (Я рассчитываю фактическую отсечку при +/- 85.05112877980658 градусах.)

Это усложняет вычисление дробей для границ по широте, а не по долготе. Я использовал формулу из Википедии для вычисления доли широты. Я предполагаю, что это соответствует проекции, используемой Google Maps. В конце концов, страница документации Google Карт, на которую я ссылаюсь выше, содержит ссылку на ту же страницу Википедии.

Другие примечания:

  1. Уровни масштабирования варьируются от 0 до максимального уровня масштабирования. Уровень масштабирования 0 - карта полностью уменьшена. Более высокие уровни увеличивают карту дальше. ( ссылка * +1050 *) * * тысяча пятьдесят-один При уровне масштабирования 0 весь мир может отображаться в области размером 256 x 256 пикселей. ( ссылка * 1 054 *)
  2. Для каждого более высокого уровня масштабирования количество пикселей, необходимое для отображения одной и той же области, удваивается как по ширине, так и по высоте. (* * Ссылка тысяча пятьдесят семь * * тысяча пятьдесят-восемь)
  3. Карты переносятся в продольном направлении, но не в широтном направлении.
102 голосов
/ 19 мая 2011

Аналогичный вопрос был задан в группе Google: 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);
34 голосов
/ 03 января 2014

Для версии 3 API это просто и работает:

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);
}
1 голос
/ 14 марта 2013

Поскольку у всех остальных ответов, похоже, есть проблемы с тем или иным набором обстоятельств (ширина / высота карты, ширина / высота границ и т. Д.), Я решил поместить свой ответ здесь ...

Здесь был очень полезный файл 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;
   }

};

Вы, конечно, можете это очистить или уменьшить, еслинеобходимо, но я долго держал имена переменных в попытке облегчить их понимание.

Если вам интересно, откуда взялся СМЕЩЕНИЕ, очевидно, что 268435456 - это половина окружности Земли в пикселях при уровне масштабирования 21 (согласноhttp://www.appelsiini.net/2008/11/introduction-to-marker-clustering-with-google-maps).

1 голос
/ 17 мая 2012

Спасибо, это мне очень помогло в поиске наиболее подходящего коэффициента масштабирования для правильного отображения полилинии.Я нахожу максимальные и минимальные координаты среди точек, которые я должен отслеживать, и, если путь очень «вертикальный», я просто добавил несколько строк кода:

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

На самом деле, идеальный коэффициент увеличенияzoomfactor-1.

0 голосов
/ 09 апреля 2018

Пример работы, чтобы найти средний центр по умолчанию с response-google-maps на 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>
0 голосов
/ 14 октября 2012

map.getBounds() не является мгновенной операцией, поэтому я использую в аналогичном случае обработчик событий. Вот мой пример в Coffeescript

@map.fitBounds(@bounds)
google.maps.event.addListenerOnce @map, 'bounds_changed', =>
  @map.setZoom(12) if @map.getZoom() > 12
0 голосов
/ 31 августа 2012

Валерио почти прав в своем решении, но есть некоторая логическая ошибка.

Вы должны сначала проверить, больше ли угол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;

Дельта есть, потому что у меня ширина больше, чем высота.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...