Вычислите центральную точку нескольких пар координат широта / долгота
учитывая набор точек широты и долготы, как я могу рассчитать широту и долготу центральной точки этого набора (ака точка, которая будет центрировать вид на все точки)?
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 гораздо раньше вопрос о так просил о том, чтобы найти среднее арифметическое набора углов компаса.
расширение подхода, рекомендованного там для сферических координат, будет:
- преобразуйте каждую пару lat / long в трехмерный вектор единичной длины.
- суммируйте каждый из этих векторов
- нормализуется результирующий вектор
- преобразовать обратно в сферических координатах
Я нашел этот пост очень полезным, так что вот решение в PHP. Я использую это успешно и просто хотел сэкономить еще один dev некоторое время.
/** * 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 это тривиально (и на самом деле работает, у меня были проблемы с рядом решений, не правильно возвращающих негативы для широты).
например, допустим, вы используете 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/
Это то же самое, что и средневзвешенная задача, где все веса одинаковы, и есть два измерения.
найдите среднее значение всех широт для вашей центральной широты и среднее значение всех долгот для Центральной долготы.
Caveat 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