Як компенсувати центральну точку в Google Maps API V3


78

У мене є карта Google із напівпрозорою панеллю, що покриває частину площі. Я хотів би скоригувати центральну точку карти, враховуючи частину карти, яка частково затемнена. Дивіться зображення нижче. В ідеалі, там, де розміщені перехрестя та шпилька, буде центральною точкою карти.

Сподіваюся, це має сенс.

Причина проста: при збільшенні масштабу потрібно відцентрувати карту над перехрестям, а не на 50% 50%. Крім того, я буду будувати маркери на карті та рухатись по них послідовно. Коли карта центрирується на них, вони також повинні знаходитись у позиції зміщення.

Спасибі заздалегідь!

Макет карти, яку я будую


Вам доведеться прояснити своє питання. Що означає "взяти до уваги частину карти, яка частково затемнена"? І що означає "а не на 50% 50%"? Хочете допомогти, але не можете зрозуміти, чого ви намагаєтесь досягти.
Шон Міккі

Привіт, чи можете ви, будь ласка, навести URL-адресу робочого прикладу, тому що мені потрібна допомога для створення рухомої лінії виверту разом з лінією маршруту
SP Singh,

Відповіді:


84

Це не особливо складно, як тільки ви знайдете відповідну попередню відповідь .

Вам потрібно перетворити центр карти на її світові координати, знайти, де карту потрібно відцентрувати, щоб помітити видимий центр там, де ви цього хочете, і перецентрувати карту за допомогою реального центру.

API завжди буде центрувати карту в центрі області перегляду, тому вам слід бути обережним, якщо ви використовуєте, map.getCenter()оскільки це поверне справжній центр, а не видимий центр. Думаю, можна було б перевантажити API, щоб його getCenter()та setCenter()методи замінити, але я цього не робив.

Код нижче. Приклад в Інтернеті . У прикладі, натискання кнопки зміщує центр карти (там є розв'язка дороги) вниз на 100 пікселів і наліво на 200 пікселів.

function offsetCenter(latlng, offsetx, offsety) {

    // latlng is the apparent centre-point
    // offsetx is the distance you want that point to move to the right, in pixels
    // offsety is the distance you want that point to move upwards, in pixels
    // offset can be negative
    // offsetx and offsety are both optional

    var scale = Math.pow(2, map.getZoom());

    var worldCoordinateCenter = map.getProjection().fromLatLngToPoint(latlng);
    var pixelOffset = new google.maps.Point((offsetx/scale) || 0,(offsety/scale) ||0);

    var worldCoordinateNewCenter = new google.maps.Point(
        worldCoordinateCenter.x - pixelOffset.x,
        worldCoordinateCenter.y + pixelOffset.y
    );

    var newCenter = map.getProjection().fromPointToLatLng(worldCoordinateNewCenter);

    map.setCenter(newCenter);

}

Чи можна змусити цей код запускатись при завантаженні сторінки, щоб при першому перегляді карта відображалася як зміщена?
Даррен Кук,

@DarrenCook: Звичайно. Просто використовуйте це замість map.setCenter(). Все, що робить цей код, - це перерахувати те, що робиться на карті, і потім робить своє map.setCenter().
Ендрю Ліч

Дякую, Ендрю, тому в своєму коді я взяв addMarkerFromAdress (latLng, title) і замінив на функцію offsetCenter (latlng, offsetx, offsety), і все це все ще працює - але як я можу отримати значення для offsety (243px) у функцію?
Даррен Кук,

27
Краще використовуватиmap.panBy(250, 0);
Олександр Швецов

2
Я виявив, що nwvar ніколи не використовується цим сценарієм, і принаймні в моєму випадку використання може бути видалений без будь-яких проблем. Я не пробував цього сам по собі, хоча, оскільки у мене є купа інших методів, які вже встановлюють межі, і все таке.
hellatan

70

Також подивіться на функцію panBy (x: number, y: number) на об'єкті Maps.

У документації згадується про функцію:

Змінює центр карти на задану відстань у пікселях. Якщо відстань менше ширини та висоти карти, перехід буде плавно анімований. Зверніть увагу, що система координат карти збільшується із заходу на схід (для значень x) та півночі на південь (для значень y).

Просто використовуйте його так:

mapsObject.panBy(200, 100)

7
Хтось знає про спосіб вимкнення анімації для цього методу завжди?
Ash

Це має бути відповідь. Це працювало ідеально і так просто. Дякую.
Розробник107

10

Ось більш простий метод, який може бути кориснішим у адаптивному дизайні, оскільки ви можете використовувати відсотки замість пікселів. Немає світових координат, немає LatLngs до балів!

var center;  // a latLng
var offsetX = 0.25; // move center one quarter map width left
var offsetY = 0.25; // move center one quarter map height down

var span = map.getBounds().toSpan(); // a latLng - # of deg map spans

var newCenter = { 
    lat: center.lat() + span.lat()*offsetY,
    lng: center.lng() + span.lng()*offsetX
};

map.panTo(newCenter);  // or map.setCenter(newCenter);

Спробував це на InfoBox, і це працює! (хоча pixelOffset все ще потрібно виправити)
початок

1
Чудова відповідь як на простоту рішення, так і на врахування
чуйного

1
Я отримую Uncaught TypeError: Не вдається прочитати властивість 'toSpan' з undefined
Mike Kormendy

ми можемо отримати newCenter lat довго?
Містер Томар,

1
Це не спрацювало б, якщо потрібно змінити масштаб у тій самій операції. Крім того, це не буде працювати точно, якщо пункт призначення (новий) центр буде розташований географічно далеко від поточного центру. Навіть на тому самому рівні масштабування 0,25 ширини екрану представляє іншу відстань на екваторі, ніж у Норвегії, наприклад, через використовувану проекцію Меркатора. Проектування кінцевих координат є більш надійним.
pscl


8

Щойно знайшов ще одне найпростіше рішення. Якщо ви використовуєте метод fitBounds, ви можете передати йому необов’язковий другий аргумент. Цей аргумент є доповненням, яке буде враховано під час встановлення меж.

// pass single side:
map.fitBounds(bounds, { left: 1000 })

// OR use Number:
map.fitBounds(bounds, 20)

Подальше читання: офіційні документи .


це ідеально!
monchisan

6

Відповідь Андрія. Однак у моєму випадку map.getBounds () продовжував повертатися невизначеним. Я виправив це, чекаючи події bounds_changed, а потім викликав функцію для зміщення центру. Подобається так:

var center_moved = false;
google.maps.event.addListener(map, 'bounds_changed', function() {
  if(!center_moved){
    offsetCenter(map.getCenter(), 250, -200);
    center_moved = true; 
  }
});

1
Ця відповідь у поєднанні з (розміщеною після) відповіддю Андрія стала для мене рішенням. Дякую Нахуель!
Mike Kormendy

5

Старе питання, я знаю. Але як щодо більш орієнтованого на CSS способу?

http://codepen.io/eddyblair/pen/VjpNQQ

Що я зробив:

  • Оберніть карту та накладіть її в контейнер overflow: hidden

  • Наклав накладку на position: absolute

  • Розширив видиму ширину карти на ширину накладання (плюс будь-яке заповнення та зміщення), встановивши мінус margin-left.

  • Потім, щоб відповідати https://www.google.com/permissions/geoguidelines/attr-guide.html, розмістили віджети та атрибуції div.

Таким чином центр карти лежить на одній лінії з центром бажаної області. Js - це просто стандартна карта js.

Перестановка піктограм для перегляду вулиць - це вправа для читача :)


Якщо ви хочете накласти зліва, просто змініть рядок 24 margin-leftна margin-rightта рядок 32 rightна left.


Мені дуже подобається такий підхід. Зберігає Angular / javascript приємним та чистим.
Різний художник

Дякую :) Це, звичайно, потребуватиме оновлення щоразу, коли Google змінює свій стиль. Я розміщую коментарі перед CSS, який переміщує атрибуції та елементи керування тощо. Це повинно дати уявлення про те, як я це зробив, якщо це потребує модифікації. Для сортування перегляду вулиць потрібно буде створити додатковий CSS.
Mardoxx

3

Після довгих пошуків я не зміг знайти спосіб зробити це, що включало б масштабування. На щастя, розумний хлопець це зрозумів. Тут теж є скрипка

'use strict';

const TILE_SIZE = {
  height: 256,
  width: 256
}; // google World tile size, as of v3.22
const ZOOM_MAX = 21; // max google maps zoom level, as of v3.22
const BUFFER = 15; // edge buffer for fitting markers within viewport bounds

const mapOptions = {
  zoom: 14,
  center: {
    lat: 34.075328,
    lng: -118.330432
  },
  options: {
    mapTypeControl: false
  }
};
const markers = [];
const mapDimensions = {};
const mapOffset = {
  x: 0,
  y: 0
};
const mapEl = document.getElementById('gmap');
const overlayEl = document.getElementById('overlay');
const gmap = new google.maps.Map(mapEl, mapOptions);

const updateMapDimensions = () => {
  mapDimensions.height = mapEl.offsetHeight;
  mapDimensions.width = mapEl.offsetWidth;
};

const getBoundsZoomLevel = (bounds, dimensions) => {
  const latRadian = lat => {
    let sin = Math.sin(lat * Math.PI / 180);
    let radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
    return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;
  };
  const zoom = (mapPx, worldPx, fraction) => {
    return Math.floor(Math.log(mapPx / worldPx / fraction) / Math.LN2);
  };
  const ne = bounds.getNorthEast();
  const sw = bounds.getSouthWest();
  const latFraction = (latRadian(ne.lat()) - latRadian(sw.lat())) / Math.PI;
  const lngDiff = ne.lng() - sw.lng();
  const lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;
  const latZoom = zoom(dimensions.height, TILE_SIZE.height, latFraction);
  const lngZoom = zoom(dimensions.width, TILE_SIZE.width, lngFraction);
  return Math.min(latZoom, lngZoom, ZOOM_MAX);
};

const getBounds = locations => {
  let northeastLat;
  let northeastLong;
  let southwestLat;
  let southwestLong;
  locations.forEach(function(location) {
    if (!northeastLat) {
      northeastLat = southwestLat = location.lat;
      southwestLong = northeastLong = location.lng;
      return;
    }
    if (location.lat > northeastLat) northeastLat = location.lat;
    else if (location.lat < southwestLat) southwestLat = location.lat;
    if (location.lng < northeastLong) northeastLong = location.lng;
    else if (location.lng > southwestLong) southwestLong = location.lng;
  });
  const northeast = new google.maps.LatLng(northeastLat, northeastLong);
  const southwest = new google.maps.LatLng(southwestLat, southwestLong);
  const bounds = new google.maps.LatLngBounds();
  bounds.extend(northeast);
  bounds.extend(southwest);
  return bounds;
};

const zoomWithOffset = shouldZoom => {
  const currentzoom = gmap.getZoom();
  const newzoom = shouldZoom ? currentzoom + 1 : currentzoom - 1;
  const offset = {
    x: shouldZoom ? -mapOffset.x / 4 : mapOffset.x / 2,
    y: shouldZoom ? -mapOffset.y / 4 : mapOffset.y / 2
  };
  const newCenter = offsetLatLng(gmap.getCenter(), offset.x, offset.y);
  if (shouldZoom) {
    gmap.setZoom(newzoom);
    gmap.panTo(newCenter);
  } else {
    gmap.setCenter(newCenter);
    gmap.setZoom(newzoom);
  }
};

const setMapBounds = locations => {
  updateMapDimensions();
  const bounds = getBounds(locations);
  const dimensions = {
    width: mapDimensions.width - mapOffset.x - BUFFER * 2,
    height: mapDimensions.height - mapOffset.y - BUFFER * 2
  };
  const zoomLevel = getBoundsZoomLevel(bounds, dimensions);
  gmap.setZoom(zoomLevel);
  setOffsetCenter(bounds.getCenter());
};

const offsetLatLng = (latlng, offsetX, offsetY) => {
  offsetX = offsetX || 0;
  offsetY = offsetY || 0;
  const scale = Math.pow(2, gmap.getZoom());
  const point = gmap.getProjection().fromLatLngToPoint(latlng);
  const pixelOffset = new google.maps.Point((offsetX / scale), (offsetY / scale));
  const newPoint = new google.maps.Point(
    point.x - pixelOffset.x,
    point.y + pixelOffset.y
  );
  return gmap.getProjection().fromPointToLatLng(newPoint);
};

const setOffsetCenter = latlng => {
  const newCenterLatLng = offsetLatLng(latlng, mapOffset.x / 2, mapOffset.y / 2);
  gmap.panTo(newCenterLatLng);
};

const locations = [{
  name: 'Wilshire Country Club',
  lat: 34.077796,
  lng: -118.331151
}, {
  name: '301 N Rossmore Ave',
  lat: 34.077146,
  lng: -118.327805
}, {
  name: '5920 Beverly Blvd',
  lat: 34.070281,
  lng: -118.331831
}];

locations.forEach(function(location) {
  let marker = new google.maps.Marker({
    position: new google.maps.LatLng(location.lat, location.lng),
    title: location.name
  })
  marker.setMap(gmap);
  markers.push(marker);
});

mapOffset.x = overlayEl.offsetWidth;

document.zoom = bool => zoomWithOffset(bool);
document.setBounds = () => setMapBounds(locations);
section {
  height: 180px;
  margin-bottom: 15px;
  font-family: sans-serif;
  color: grey;
}
figure {
  position: relative;
  margin: 0;
  width: 100%;
  height: 100%;
}
figcaption {
  position: absolute;
  left: 15px;
  top: 15px;
  width: 120px;
  padding: 15px;
  background: white;
  box-shadow: 0 2px 5px rgba(0, 0, 0, .3);
}
gmap {
  display: block;
  height: 100%;
}
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js"></script>

<section>
  <figure>
    <gmap id="gmap"></gmap>
    <figcaption id="overlay">
      <h4>Tile Overlay</h4>
      <p>To be avoided by the map!</p>
    </figcaption>
  </figure>
</section>
<button onclick="zoom(true)">zoom in</button>
<button onclick="zoom(false)">zoom out</button>
<button onclick="setBounds()">set bounds</button>


1

Інший підхід щодо зміщення маршруту або групи маркерів можна знайти тут:

https://stackoverflow.com/a/26192440/1238965

Він все ще використовує fromLatLngToPoint()метод, описаний у відповіді @Andrew Leach.


1
Це складніше, ніж прийнята відповідь IMO.
Ян Ланн,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.