Заполнение fitBounds в Google Maps API 3 — следите за тем, чтобы маркеры не закрывались наложенными элементами управления.

Я хочу иметь возможность добавлять отступы к представлению карты после вызова map.fitBounds() , поэтому все маркеры могут быть видны независимо от элементов управления картой или таких вещей, как скользящие панели, закрывающие маркеры при открытии. В Leaftlet есть возможность добавления отступов к fitBounds, а в Google Maps — нет.

Иногда самые северные маркеры частично скрываются над окном просмотра. Крайние западные маркеры также часто лежат под ползунком масштабирования. С API 2 можно было сформировать виртуальную область просмотра, уменьшив заданные отступы от области просмотра карты, а затем вызвать метод showBounds() для расчета и выполнения масштабирования и центрирования на основе этой виртуальной области просмотра:

map.showBounds(bounds, {top:30,right:10,left:50});

Рабочий пример этого для API 2 можно найти здесь в разделе showBounds() ссылка пример.

Я не могу найти аналогичную функциональность в API V3, но, надеюсь, это можно сделать другим способом. Может быть, я мог бы взять северо-восточную и юго-западную точки, а затем добавить поддельные координаты, чтобы расширить границы после их включения?

ОБНОВИТЬ

(Codepen на случай, если приведенный ниже код не работает)

function initMap() {
  var map = new google.maps.Map(document.getElementById('map'), {
    draggable: true,
    streetViewControl: false,
    zoomControl: false
  });

  var marker1 = new google.maps.Marker({
    position: {lat: 37, lng: -121},
    map: map,
  });

  var marker2 = new google.maps.Marker({
    position: {lat: 39.3, lng: -122},
    map: map,
  });
 
  var bounds = new google.maps.LatLngBounds();
  bounds.extend(marker1.position);
  bounds.extend(marker2.position);
  map.fitBounds(bounds);
}
#map {
  height: 640px;
  width: 360px;
}
#overlays {
  position: absolute;
  height: 50px;
  width: 340px;
  background: white;
  margin: -80px 10px;
  text-align: center;
  line-height: 50px;
}
/* Optional: Makes the sample page fill the window. */
html, body {
  height: 100%;
  margin: 0;
  padding: 0;
}
<html>
  <head>
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
    <meta charset="utf-8">
    <title>Simple markers</title>

  </head>
  <body>
    <div id="map"></div>
    <div id="overlays">Controls / Order pizza / ETA / etc.</div>
  
    <script async defer
    src="https://maps.googleapis.com/maps/api/js?&callback=initMap">
    </script>
  </body>
</html>

Проблема в следующем:

введите описание изображения здесь

Я попытался добавить элемент управления, как описано в разделе Пользовательские элементы управления, но map точно не знает об этом - см. эту скрипту, разветвленную из Пример пользовательского элемента управления Maps. Один из маркеров все еще закрыт элементом управления.


person jsurf    schedule 26.04.2012    source источник


Ответы (8)


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

Предположим, что переменная map является вашей ссылкой на объект карты;

map.setZoom(map.getZoom() - 1);
person keune    schedule 26.04.2012
comment
Дешево, быстро и грязно. мне это нравится! - person shababhsiddique; 21.06.2014
comment
Здесь это не работает, странно! Выводит уровень масштабирования, а уровень масштабирования - 1, но не масштабирует map.fitBounds(bounds); console.log(map.getZoom()); map.setZoom(map.getZoom() - 5); console.log(map.getZoom()); - person Miguel Stevens; 20.08.2014
comment
Также отлично работает для gmaps4rails в рельсах. Чтобы найти уровень масштабирования, я использовал var zoom_level = handler.getMap().getZoom() и установил уровень масштабирования с помощью handler.getMap().setZoom(zoom_level - 1) после вызова fitMapToBounds. - person Adam Cooper; 13.05.2015
comment
Поскольку fitBounds() является асинхронным, чтобы заставить его работать, вам может потребоваться добавить прослушиватель в ваш код, как описано в stackoverflow.com/a. /11976581/286486 - person Jeff Steil; 29.02.2016

По состоянию на июнь 2017 года API JavaScript для Карт поддерживает параметр padding в методе fitBounds().

fitBounds(bounds:LatLngBounds|LatLngBoundsLiteral, padding?:number)

Пожалуйста, обратитесь к документации для получения дополнительной информации

https://developers.google.com/maps/documentation/javascript/reference#Map

person xomena    schedule 06.07.2017
comment
Это приятно видеть, но все же это неадекватное решение для многих случаев использования, поскольку часто только некоторые области нуждаются в отступах, и эта (плохо документированная) функция, по-видимому, добавляет отступы со всех сторон. - person El Yobo; 13.07.2017
comment
Мне кажется, что отступы добавляются со всех сторон карты. - person exoslav; 01.09.2017
comment
обратитесь к документации для получения дополнительной информации. Было бы неплохо, но подробностей нет. Есть только упоминание о параметре padding, который принимает число. Никаких подробностей о его семантике или значении по умолчанию. Хотя руководства по Google Maps API хороши, справочных документов не хватает, и этот случай не является исключением. - person Edward Brey; 20.02.2018
comment
Веселый. Я попал сюда в поисках дополнения fitBounds, потому что документация не содержит дополнительных сведений о дополнении, и я обратился к документации за дополнительными подробностями. - person Parker Ault; 21.02.2018
comment
developers.google.com/maps/documentation/javascript/reference/ Вы можете добавить номер или {слева: 50, сверху: 200} // справа, снизу и т. д.. - person yairniz; 06.03.2019
comment
для меня уменьшение масштаба не остановилось с заполнением нулями. Вместо этого мне пришлось использовать отрицательное дополнение, которое, к счастью, не увеличивало, но и не уменьшало масштаб, что сработало для меня. - person Valentin Rapp; 14.03.2019
comment
Заполнение может быть числом или объектом Padding. developers.google.com/maps/documentation/javascript/reference/, который допускает различные отступы для верхнего, правого, нижнего и левого полей. - person gunwin; 25.04.2019

Я решил эту проблему, расширив границы карты, включив в них широту, которая достаточно выдвинула маркеры в поле зрения.

Во-первых, вам нужно создать оверлейный вид

var overlayHelper = new google.maps.OverlayView();
overlayHelper.onAdd = function() {};
overlayHelper.onRemove = function() {};
overlayHelper.draw = function() {};
overlayHelper.setMap(map);

Когда у вас есть помощник по наложению, вам нужно получить проекцию карты и выполнить расчеты на ее основе.

Обратите внимание, что элемент управления, который у меня есть на моей карте, представляет собой div шириной 420 пикселей и высотой 100% в крайнем правом углу карты. Вам, очевидно, потребуется изменить код, чтобы приспособить элементы управления.

var mapCanvas = $("#map_canvas"),
    controlLeft = mapCanvas.width() - 420, // canvas width minus width of the overlayed control
    projection = overlayHelper.getProjection(),
    widestPoint = 0, 
    latlng, 
    point;

// the markers were created elsewhere and already extended the bounds on creation
map.fitBounds(mapBounds);

// check if any markers are hidden behind the overlayed control
for (var m in markers) {
    point = projection.fromLatLngToContainerPixel(markers[m].getPosition());
    if (point.x > controlLeft && point.x > widestPoint) {
        widestPoint = point.x;
    }
}

if (widestPoint > 0) {
    point = new google.maps.Point(
                mapCanvas.width() + (widestPoint - controlLeft), 
                mapCanvas.height() / 2); // middle of map height, since we only want to reposition bounds to the left and not up and down

    latlng = projection.fromContainerPixelToLatLng(point);
    mapBounds.extend(latlng);
    map.fitBounds(mapBounds);
}

Если вы делаете это, когда карта загружается в первый раз, вам нужно будет обернуть это в событие карты, чтобы дождаться простоя. Это позволяет инициализировать вид наложения. Не включайте создание помощника оверлея в обратный вызов события.

google.maps.event.addListenerOnce(map, 'idle', function() { <above code> });
person Seain Malkin    schedule 29.12.2012

Обновлено

Google Maps API теперь поддерживает собственный параметр padding в fitBounds (начиная с версии 3.32, поправьте меня, если раньше).

У меня еще не было возможности протестировать его, но если у вас есть возможность обновиться - я бы порекомендовал использовать нативный способ. Если вы используете версию ‹ 3.32 и не можете обновиться - мое решение для вас.


Я взял рабочее решение от erzzo и немного улучшил его.
Пример

fitBoundsWithPadding(googleMapInstance, PolygonLatLngBounds, {left:250, bottom:10});

Описание аргументов:

  1. gMap — экземпляр карты Google
  2. bounds — Google отображает объект LatLngBounds, чтобы он подходил
  3. paddingXY - Object Literal: 2 possible formats:
    • {x, y} - for horizontal and vertical paddings (x=left=right, y=top=bottom)
    • {слева, справа, сверху, снизу}

список функций для копирования

function fitBoundsWithPadding(gMap, bounds, paddingXY) {
        var projection = gMap.getProjection();
        if (projection) {
            if (!$.isPlainObject(paddingXY))
                paddingXY = {x: 0, y: 0};

            var paddings = {
                top: 0,
                right: 0,
                bottom: 0,
                left: 0
            };

            if (paddingXY.left){
                paddings.left = paddingXY.left;
            } else if (paddingXY.x) {
                paddings.left = paddingXY.x;
                paddings.right = paddingXY.x;
            }

            if (paddingXY.right){
                paddings.right = paddingXY.right;
            }

            if (paddingXY.top){
                paddings.top = paddingXY.top;
            } else if (paddingXY.y) {
                paddings.top = paddingXY.y;
                paddings.bottom = paddingXY.y;
            }

            if (paddingXY.bottom){
                paddings.bottom = paddingXY.bottom;
            }

            // copying the bounds object, since we will extend it
            bounds = new google.maps.LatLngBounds(bounds.getSouthWest(), bounds.getNorthEast());

            // SW
            var point1 = projection.fromLatLngToPoint(bounds.getSouthWest());


            // we must call fitBounds 2 times - first is necessary to set up a projection with initial (actual) bounds
            // and then calculate new bounds by adding our pixel-sized paddings to the resulting viewport
            gMap.fitBounds(bounds);

            var point2 = new google.maps.Point(
                ( (typeof(paddings.left) == 'number' ? paddings.left : 0) / Math.pow(2, gMap.getZoom()) ) || 0,
                ( (typeof(paddings.bottom) == 'number' ? paddings.bottom : 0) / Math.pow(2, gMap.getZoom()) ) || 0
            );

            var newPoint = projection.fromPointToLatLng(new google.maps.Point(
                point1.x - point2.x,
                point1.y + point2.y
            ));

            bounds.extend(newPoint);

            // NE
            point1 = projection.fromLatLngToPoint(bounds.getNorthEast());
            point2 = new google.maps.Point(
                ( (typeof(paddings.right) == 'number' ? paddings.right : 0) / Math.pow(2, gMap.getZoom()) ) || 0,
                ( (typeof(paddings.top) == 'number' ? paddings.top : 0) / Math.pow(2, gMap.getZoom()) ) || 0
            );
            newPoint = projection.fromPointToLatLng(new google.maps.Point(
                point1.x + point2.x,
                point1.y - point2.y
            ));

            bounds.extend(newPoint);

            gMap.fitBounds(bounds);
        }
    }
person Roman86    schedule 03.05.2017
comment
Идеальное решение. Спасибо! - person Алексей Матвеев; 17.11.2017
comment
Спасибо за это решение. Как бы я адаптировал это, чтобы включить параметр maxZoom? Я хочу иметь возможность ограничить масштаб приближения fitBoundsWithPadding. - person juliusbangert; 10.04.2018
comment
привет @julius! Я рад, что это помогло =) В качестве быстрого решения могу предложить проверить уровень масштабирования после fitBounds. Вот так: if (gMap.getZoom() › maxZoom) { gMap.setZoom(maxZoom); } Кроме того, есть параметр карты maxZoom, который вы можете установить глобально для своей карты. - person Roman86; 11.04.2018
comment
На самом деле я решил это, используя map.setOptions({ maxZoom: maxZoom }); до любых fitBounds, а затем отключив его. - person juliusbangert; 11.04.2018
comment
@ enorl76 спасибо за хороший вопрос! Это даже заставило меня немного усомниться. Вот ответ: сначала fitBounds необходим для настройки проекции с начальными (фактических) границами, а затем вычисляет новые границы, добавляя наши отступы размером с пиксель в результирующем окне просмотра. Я обновил ответ. Еще раз спасибо - person Roman86; 09.07.2018

Вы можете использовать map.fitBounds() с API V3 с тем же синтаксисом заполнения, который вы упомянули с map.showBounds().

Простое использование map.fitBounds(bounds, {top:30,right:10,left:50}); сработало для меня.

(это может быть комментарий к сообщению xomena или Roman86, у меня недостаточно репутации, чтобы комментировать)

person Milan Švehla    schedule 30.11.2018

Я предоставлю более общее решение этой проблемы. Если у нас есть позиция, например. marker.getPosition(), с помощью этой функции мы можем найти другую позицию на расстоянии (x, y) пикселя от нее.

function extendedLocation(position, x, y) {
    var projection = map.getProjection();
    var newMarkerX = projection.fromLatLngToPoint(position).x + x/(Math.pow(2, map.getZoom()))
    var newMarkerY = projection.fromLatLngToPoint(position).y + y/(Math.pow(2, map.getZoom()))
    var newMarkerPoint = new google.maps.Point(newMarkerX, newMarkerY);
    return projection.fromPointToLatLng(newMarkerPoint)
}

Примечание: положительное значение x соответствует правому направлению, а положительное значение y — направлению вниз. Итак, в общем случае, чтобы вывести маркер в поле зрения, нам нужно передать отрицательное значение y, например. extendedLocation(marker.getPosition(), 4, -18)

Если у вас есть постоянный ползунок или любой подобный элемент вверху, допустим, высотой 30 пикселей, просто используйте параметр y в функции как -30.

Можно создать более обобщенную функцию, которая возвращает массив из 4 точек, каждая из которых находится на расстоянии (x, y) пикселей от заданного направления вверх, вниз, вправо и влево.

function getAllExtendedPosition(position, x, y){
   var positionArray = [];
   postitionArray.push(extendedLocation(position, x, y);
   postitionArray.push(extendedLocation(position, -x, -y);
   postitionArray.push(extendedLocation(position, -x, y);
   postitionArray.push(extendedLocation(position, x, -y);
   return positionArray
}
person Gaurav Mukherjee    schedule 11.02.2016

То, как я это сделал, кажется довольно чистым. Например. Примените отступ 10%, используя координаты пикселей на каждой стороне карты. Это с Maps API v3.44:

map.fitBounds(bounds)
let mapDiv = map.getDiv()

let padding = {
  bottom: mapDiv.offsetHeight * 0.1,
  left: mapDiv.offsetWidth * 0.1,
  right: mapDiv.offsetWidth * 0.1,
  top: mapDiv.offsetHeight * 0.1,
}
map.fitBounds(bounds, padding);
person errolflynn    schedule 25.02.2021

Другой способ сделать это — расширить ваши границы с помощью дополнительной точки широты и долготы на расчетном расстоянии. Объект google.maps.LatLngBounds() имеет функции для получения юго-западной и северо-восточной точек ограничивающей рамки, которые можно использовать для расчета расстояния X миль на север, юг, восток или запад.

Например, если вы пытаетесь сдвинуть маркеры вправо, чтобы учесть элементы наложения в левой части карты, вы можете попробовать следующее:

// create your LatLngBounds object, like you're already probably doing
var bounds = new google.maps.LatLngBounds();

// for each marker call bounds.extend(pos) to extend the base boundaries

// once your loop is complete
// *** add a calculated LatLng point to the west of your boundary ***
bounds.extend(new google.maps.LatLng(bounds.getSouthWest().lat(), bounds.getSouthWest().lng() - .9));

// finally, center your map on the modified boundaries
map.fitBounds(bounds);

В приведенном выше примере добавление точки LatLong к границам путем вычитания 0,9 из долготы самой западной точки перемещает границу примерно на 52 мили дальше на запад.

Целая точка (pos.lng() - 1.0) составляет около 58 миль, поэтому вы можете либо угадать хорошее расстояние, либо использовать какой-либо другой метод для расчета этого продольного смещения при выяснении того, какой вид набивки вам нужен.

person mcnejef    schedule 22.09.2016
comment
Добавление точки с абсолютным расстоянием - плохой дизайн, значение будет меняться в зависимости от масштаба. Чтобы правильно добавить эту точку, вы должны сами рассчитать границы и масштабировать… - person Kustolovic; 05.12.2016
comment
учет масштабирования и / или размера холста как бы подразумевался - отсюда и последнее предложение, в котором предлагается использовать какой-либо другой метод для вычисления продольного смещения при выяснении того, какой вид отступов вам нужен. - person mcnejef; 05.12.2016