Вычислить центральную точку нескольких пар координат широты / долготы

учитывая набор точек широты и долготы, как я могу вычислить широту и долготу центральной точки этого набора (он же точка, которая будет центрировать представление по всем точкам)?

EDIT: решение Python, которое я использовал:

Convert lat/lon (must be in radians) to Cartesian coordinates for each location.
X = cos(lat) * cos(lon)
Y = cos(lat) * sin(lon)
Z = sin(lat)

Compute average x, y and z coordinates.
x = (x1 + x2 + ... + xn) / n
y = (y1 + y2 + ... + yn) / n
z = (z1 + z2 + ... + zn) / n

Convert average x, y, z coordinate to latitude and longitude.
Lon = atan2(y, x)
Hyp = sqrt(x * x + y * y)
Lat = atan2(z, hyp)

13 ответов


простой подход к их усреднению имеет странные крайние случаи с углами, когда они обертываются от 359 "назад до 0".

A гораздо раньше вопрос на SO спросил о поиске среднего из набора углов компаса.

расширение подхода, рекомендованного там для сферических координат, будет:

  • преобразуйте каждую пару lat/long в 3D-вектор единичной длины.
  • суммировать каждый из этих векторов
  • нормализуется результирующий вектор
  • преобразовать обратно в сферических координатах

спасибо! Вот версия C# решений OP с использованием степеней. Он использует


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

/**
 * Get a center latitude,longitude from an array of like geopoints
 *
 * @param array data 2 dimensional array of latitudes and longitudes
 * For Example:
 * $data = array
 * (
 *   0 = > array(45.849382, 76.322333),
 *   1 = > array(45.843543, 75.324143),
 *   2 = > array(45.765744, 76.543223),
 *   3 = > array(45.784234, 74.542335)
 * );
*/
function GetCenterFromDegrees($data)
{
    if (!is_array($data)) return FALSE;

    $num_coords = count($data);

    $X = 0.0;
    $Y = 0.0;
    $Z = 0.0;

    foreach ($data as $coord)
    {
        $lat = $coord[0] * pi() / 180;
        $lon = $coord[1] * pi() / 180;

        $a = cos($lat) * cos($lon);
        $b = cos($lat) * sin($lon);
        $c = sin($lat);

        $X += $a;
        $Y += $b;
        $Z += $c;
    }

    $X /= $num_coords;
    $Y /= $num_coords;
    $Z /= $num_coords;

    $lon = atan2($Y, $X);
    $hyp = sqrt($X * $X + $Y * $Y);
    $lat = atan2($Z, $hyp);

    return array($lat * 180 / pi(), $lon * 180 / pi());
}

очень полезный пост! Я реализовал это в JavaScript, таким образом, мой код. Я успешно использовал это.

function rad2degr(rad) { return rad * 180 / Math.PI; }
function degr2rad(degr) { return degr * Math.PI / 180; }

/**
 * @param latLngInDeg array of arrays with latitude and longtitude
 *   pairs in degrees. e.g. [[latitude1, longtitude1], [latitude2
 *   [longtitude2] ...]
 *
 * @return array with the center latitude longtitude pairs in 
 *   degrees.
 */
function getLatLngCenter(latLngInDegr) {
    var LATIDX = 0;
    var LNGIDX = 1;
    var sumX = 0;
    var sumY = 0;
    var sumZ = 0;

    for (var i=0; i<latLngInDegr.length; i++) {
        var lat = degr2rad(latLngInDegr[i][LATIDX]);
        var lng = degr2rad(latLngInDegr[i][LNGIDX]);
        // sum of cartesian coordinates
        sumX += Math.cos(lat) * Math.cos(lng);
        sumY += Math.cos(lat) * Math.sin(lng);
        sumZ += Math.sin(lat);
    }

    var avgX = sumX / latLngInDegr.length;
    var avgY = sumY / latLngInDegr.length;
    var avgZ = sumZ / latLngInDegr.length;

    // convert average x, y, z coordinate to latitude and longtitude
    var lng = Math.atan2(avgY, avgX);
    var hyp = Math.sqrt(avgX * avgX + avgY * avgY);
    var lat = Math.atan2(avgZ, hyp);

    return ([rad2degr(lat), rad2degr(lng)]);
}


в интересах возможного сохранения кого-то минуту или две, вот решение, которое было использовано в Objective-C вместо python. Эта версия принимает NSArray NSValues, которые содержат MKMapCoordinates, который был вызван в моей реализации:

#import <MapKit/MKGeometry.h>
+ (CLLocationCoordinate2D)centerCoordinateForCoordinates:(NSArray *)coordinateArray {
    double x = 0;
    double y = 0;
    double z = 0;

    for(NSValue *coordinateValue in coordinateArray) {
        CLLocationCoordinate2D coordinate = [coordinateValue MKCoordinateValue];

        double lat = GLKMathDegreesToRadians(coordinate.latitude);
        double lon = GLKMathDegreesToRadians(coordinate.longitude);
        x += cos(lat) * cos(lon);
        y += cos(lat) * sin(lon);
        z += sin(lat);
    }

    x = x / (double)coordinateArray.count;
    y = y / (double)coordinateArray.count;
    z = z / (double)coordinateArray.count;

    double resultLon = atan2(y, x);
    double resultHyp = sqrt(x * x + y * y);
    double resultLat = atan2(z, resultHyp);

    CLLocationCoordinate2D result = CLLocationCoordinate2DMake(GLKMathRadiansToDegrees(resultLat), GLKMathRadiansToDegrees(resultLon));
    return result;
}

Javascript версия исходной функции

/**
 * Get a center latitude,longitude from an array of like geopoints
 *
 * @param array data 2 dimensional array of latitudes and longitudes
 * For Example:
 * $data = array
 * (
 *   0 = > array(45.849382, 76.322333),
 *   1 = > array(45.843543, 75.324143),
 *   2 = > array(45.765744, 76.543223),
 *   3 = > array(45.784234, 74.542335)
 * );
*/
function GetCenterFromDegrees(data)
{       
    if (!(data.length > 0)){
        return false;
    } 

    var num_coords = data.length;

    var X = 0.0;
    var Y = 0.0;
    var Z = 0.0;

    for(i = 0; i < data.length; i++){
        var lat = data[i][0] * Math.PI / 180;
        var lon = data[i][1] * Math.PI / 180;

        var a = Math.cos(lat) * Math.cos(lon);
        var b = Math.cos(lat) * Math.sin(lon);
        var c = Math.sin(lat);

        X += a;
        Y += b;
        Z += c;
    }

    X /= num_coords;
    Y /= num_coords;
    Z /= num_coords;

    var lon = Math.atan2(Y, X);
    var hyp = Math.sqrt(X * X + Y * Y);
    var lat = Math.atan2(Z, hyp);

    var newX = (lat * 180 / Math.PI);
    var newY = (lon * 180 / Math.PI);

    return new Array(newX, newY);
}

Если вы заинтересованы в получении очень упрощенного "центра" точек (например, просто центрировать карту в центр вашего многоугольника gmaps), то вот базовый подход, который работал для меня.

public function center() {
    $minlat = false;
    $minlng = false;
    $maxlat = false;
    $maxlng = false;
    $data_array = json_decode($this->data, true);
    foreach ($data_array as $data_element) {
        $data_coords = explode(',',$data_element);
        if (isset($data_coords[1])) {
            if ($minlat === false) { $minlat = $data_coords[0]; } else { $minlat = ($data_coords[0] < $minlat) ? $data_coords[0] : $minlat; }
            if ($maxlat === false) { $maxlat = $data_coords[0]; } else { $maxlat = ($data_coords[0] > $maxlat) ? $data_coords[0] : $maxlat; }
            if ($minlng === false) { $minlng = $data_coords[1]; } else { $minlng = ($data_coords[1] < $minlng) ? $data_coords[1] : $minlng; }
            if ($maxlng === false) { $maxlng = $data_coords[1]; } else { $maxlng = ($data_coords[1] > $maxlng) ? $data_coords[1] : $maxlng; }
        }
    }
    $lat = $maxlat - (($maxlat - $minlat) / 2);
    $lng = $maxlng - (($maxlng - $minlng) / 2);
    return $lat.','.$lng;
}

Это возвращает среднюю координату lat/lng для центра многоугольника.


очень хорошие решения, как раз то, что мне нужно для моего проекта swift, так что вот swift порт. спасибо и тут же площадка проекта: https://github.com/ppoh71/playgounds/tree/master/centerLocationPoint.playground

/*
* calculate the center point of multiple latitude longitude coordinate-pairs
*/

import CoreLocation
import GLKit

var LocationPoints = [CLLocationCoordinate2D]()

//add some points to Location ne, nw, sw, se , it's a rectangle basicaly
LocationPoints.append(CLLocationCoordinate2D(latitude: 37.627512369999998, longitude: -122.38780611999999))
LocationPoints.append(CLLocationCoordinate2D(latitude: 37.627512369999998, longitude:  -122.43105867))
LocationPoints.append(CLLocationCoordinate2D(latitude: 37.56502528, longitude: -122.43105867))
LocationPoints.append(CLLocationCoordinate2D(latitude: 37.56502528, longitude: -122.38780611999999))

// center func
func getCenterCoord(LocationPoints: [CLLocationCoordinate2D]) -> CLLocationCoordinate2D{

    var x:Float = 0.0;
    var y:Float = 0.0;
    var z:Float = 0.0;

    for points in LocationPoints {

     let lat = GLKMathDegreesToRadians(Float(points.latitude));
     let long = GLKMathDegreesToRadians(Float(points.longitude));

        x += cos(lat) * cos(long);
        y += cos(lat) * sin(long);
        z += sin(lat);
    }

    x = x / Float(LocationPoints.count);
    y = y / Float(LocationPoints.count);
    z = z / Float(LocationPoints.count);

    let resultLong = atan2(y, x);
    let resultHyp = sqrt(x * x + y * y);
    let resultLat = atan2(z, resultHyp);



    let result = CLLocationCoordinate2D(latitude: CLLocationDegrees(GLKMathRadiansToDegrees(Float(resultLat))), longitude: CLLocationDegrees(GLKMathRadiansToDegrees(Float(resultLong))));

    return result;

}

//get the centerpoint
var centerPoint = getCenterCoord(LocationPoints)
print("Latitude: \(centerPoint.latitude) / Longitude: \(centerPoint.longitude)")

в Django это тривиально (и на самом деле работает, у меня были проблемы с рядом решений, неправильно возвращающих негативы для широты).

например, предположим, вы используете Джанго-geopostcodes (автором которого являюсь я).

from django.contrib.gis.geos import MultiPoint
from django.contrib.gis.db.models.functions import Distance
from django_geopostcodes.models import Locality

qs = Locality.objects.anything_icontains('New York')
points = [locality.point for locality in qs]
multipoint = MultiPoint(*points)
point = multipoint.centroid

point - это Джанго Point экземпляр, который затем может использоваться для таких вещей, как получение всех объектов, которые находятся в пределах 10 км от этой центральной точки;

Locality.objects.filter(point__distance_lte=(point, D(km=10)))\
    .annotate(distance=Distance('point', point))\
    .order_by('distance')

изменение этого на raw Python тривиально;

from django.contrib.gis.geos import Point, MultiPoint

points = [
    Point((145.137075, -37.639981)),
    Point((144.137075, -39.639981)),
]
multipoint = MultiPoint(*points)
point = multipoint.centroid

под капотом Django использует GEOS-подробнее в https://docs.djangoproject.com/en/1.10/ref/contrib/gis/geos/


Это то же самое, что и средневзвешенная задача, где все веса одинаковы, и есть два измерения.

найдите среднее значение всех широт для вашей центральной широты и среднее значение всех долгот для Центральной долготы.

предостережение Emptor: это близкое приближение расстояния, и ошибка станет непослушной, когда отклонения от среднего составляют более нескольких миль из-за кривизны Земли. Помните, что широты и долготы градусов (не сетки).


Если вы хотите принять во внимание используемый эллипсоид, вы можете найти формулы здесь http://www.ordnancesurvey.co.uk/oswebsite/gps/docs/A_Guide_to_Coordinate_Systems_in_Great_Britain.pdf

см. приложение B

документ содержит много других полезных вещей

B


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

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

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

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


из объекта в PHP. Заданный массив пар координат возвращает центр.

/**
 * Calculate center of given coordinates
 * @param  array    $coordinates    Each array of coordinate pairs
 * @return array                    Center of coordinates
 */
function getCoordsCenter($coordinates) {    
    $lats = $lons = array();
    foreach ($coordinates as $key => $value) {
        array_push($lats, $value[0]);
        array_push($lons, $value[1]);
    }
    $minlat = min($lats);
    $maxlat = max($lats);
    $minlon = min($lons);
    $maxlon = max($lons);
    $lat = $maxlat - (($maxlat - $minlat) / 2);
    $lng = $maxlon - (($maxlon - $minlon) / 2);
    return array("lat" => $lat, "lon" => $lng);
}

взятая идея из #4