Определение центра между координатами

Работа с координатами в Google Maps JavaScript API часто требует вычисления центра между несколькими географическими точками. Под центром может пониматься как простое среднее значение широты и долготы, так и геодезически корректная точка на поверхности сферы Земли. Выбор метода зависит от масштаба карты, распределения точек и требуемой точности отображения.

Самый прямолинейный способ вычисления центра между двумя точками — усреднение их широт и долгот:

[ lat_{center} = , lng_{center} = ]

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

Реализация на JavaScript

function getMidpoint(lat1, lng1, lat2, lng2) {
  return {
    lat: (lat1 + lat2) / 2,
    lng: (lng1 + lng2) / 2
  };
}

Использование в Google Maps JavaScript API:

const pointA = { lat: 55.7558, lng: 37.6173 };
const pointB = { lat: 59.9311, lng: 30.3609 };

const center = getMidpoint(pointA.lat, pointA.lng, pointB.lat, pointB.lng);

const map = new google.maps.Map(document.getElementById("map"), {
  center: center,
  zoom: 6
});

Ограничения арифметического метода

При работе с глобальными координатами возникают существенные ошибки:

  • игнорируется кривизна Земли;
  • долгота не является линейной величиной;
  • при пересечении 180-го меридиана результат становится некорректным;
  • для дальних точек центр может оказаться вне ожидаемой географической области.

Для повышения точности применяется сферическая модель.

Геодезический центр на сфере

Более корректный способ основан на переводе географических координат в трёхмерные декартовы координаты на единичной сфере.

Пусть широта и долгота переводятся в радианы:

[ lat = , lng = ]

Тогда:

[ x = ()(), y = ()(), z = ()]

После суммирования всех точек вычисляется среднее значение координат:

[ x_{avg} = , y_{avg} = , z_{avg} = ]

Затем результат нормализуется обратно на сферу и переводится в широту и долготу:

[ = (y, x), = (z, )]

JavaScript-реализация

function getGeographicCenter(points) {
  let x = 0, y = 0, z = 0;

  points.forEach(p => {
    const lat = p.lat * Math.PI / 180;
    const lng = p.lng * Math.PI / 180;

    x += Math.cos(lat) * Math.cos(lng);
    y += Math.cos(lat) * Math.sin(lng);
    z += Math.sin(lat);
  });

  const total = points.length;

  x /= total;
  y /= total;
  z /= total;

  const lng = Math.atan2(y, x);
  const hyp = Math.sqrt(x * x + y * y);
  const lat = Math.atan2(z, hyp);

  return {
    lat: lat * 180 / Math.PI,
    lng: lng * 180 / Math.PI
  };
}

Использование с набором маркеров

В контексте карты часто требуется вычислить центр группы маркеров:

const markers = [
  { lat: 55.7558, lng: 37.6173 },
  { lat: 59.9311, lng: 30.3609 },
  { lat: 56.8389, lng: 60.6057 }
];

const center = getGeographicCenter(markers);

const map = new google.maps.Map(document.getElementById("map"), {
  center,
  zoom: 5
});

Центр bounding box (ограничивающего прямоугольника)

Практически в Google Maps JavaScript API часто используется подход через границы области отображения. Он не требует сферических вычислений и хорошо подходит для визуального центрирования.

Алгоритм

  1. Найти минимальные и максимальные значения широты и долготы.
  2. Усреднить их.

[ lat_{center} = , lng_{center} = ]

Реализация

function getBoundsCenter(points) {
  let minLat = Infinity;
  let maxLat = -Infinity;
  let minLng = Infinity;
  let maxLng = -Infinity;

  points.forEach(p => {
    if (p.lat < minLat) minLat = p.lat;
    if (p.lat > maxLat) maxLat = p.lat;
    if (p.lng < minLng) minLng = p.lng;
    if (p.lng > maxLng) maxLng = p.lng;
  });

  return {
    lat: (minLat + maxLat) / 2,
    lng: (minLng + maxLng) / 2
  };
}

Интеграция с LatLngBounds

Google Maps API предоставляет встроенный класс для работы с границами:

const bounds = new google.maps.LatLngBounds();

points.forEach(p => {
  bounds.extend(new google.maps.LatLng(p.lat, p.lng));
});

const center = bounds.getCenter();

const map = new google.maps.Map(document.getElementById("map"), {
  center: center.toJSON(),
  zoom: 5
});

Сравнение методов вычисления центра

Арифметический метод:

  • минимальная вычислительная сложность;
  • подходит для локальных координат;
  • некорректен на глобальных расстояниях.

Сферический метод:

  • высокая точность;
  • устойчив к глобальным распределениям точек;
  • требует тригонометрических вычислений.

Bounding box:

  • оптимален для визуального центрирования;
  • игнорирует геодезию;
  • широко используется в интерфейсах карт.

Применение в динамическом центрировании карты

При обновлении набора маркеров центр часто пересчитывается автоматически:

function updateMapCenter(map, points) {
  const center = getGeographicCenter(points);

  map.setCenter(center);
}

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

Особенности работы с антиподами и 180-м меридианом

При наличии точек по разные стороны 180-го меридиана стандартные методы bounding box дают ошибку, расширяя область на всю карту. В таких случаях используется нормализация долгот:

  • приведение значений к диапазону [-180, 180];
  • использование круговой арифметики для долготы;
  • предпочтение сферического центра.

Центрирование с учётом веса точек

В некоторых сценариях каждая точка имеет значимость:

function getWeightedCenter(points) {
  let x = 0, y = 0, z = 0, weightSum = 0;

  points.forEach(p => {
    const lat = p.lat * Math.PI / 180;
    const lng = p.lng * Math.PI / 180;
    const w = p.weight || 1;

    x += Math.cos(lat) * Math.cos(lng) * w;
    y += Math.cos(lat) * Math.sin(lng) * w;
    z += Math.sin(lat) * w;

    weightSum += w;
  });

  x /= weightSum;
  y /= weightSum;
  z /= weightSum;

  const lng = Math.atan2(y, x);
  const hyp = Math.sqrt(x * x + y * y);
  const lat = Math.atan2(z, hyp);

  return {
    lat: lat * 180 / Math.PI,
    lng: lng * 180 / Math.PI
  };
}