Как перевести широту или долготу в метры?

Если у меня есть значения широты или долготы в стандартном формате NMEA, есть ли простой способ / формула для преобразования этого значения в метры, которое я затем смогу реализовать в Java (J9)?

Edit: Ok, кажется, то, что я хочу сделать, не легко, однако то, что я действительно хочу сделать, это:

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

20 ответов

Решение

Вот функция JavaScript:

function measure(lat1, lon1, lat2, lon2){  // generally used geo measurement function
    var R = 6378.137; // Radius of earth in KM
    var dLat = lat2 * Math.PI / 180 - lat1 * Math.PI / 180;
    var dLon = lon2 * Math.PI / 180 - lon1 * Math.PI / 180;
    var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
    Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
    Math.sin(dLon/2) * Math.sin(dLon/2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    var d = R * c;
    return d * 1000; // meters
}

Объяснение: https://en.wikipedia.org/wiki/Haversine_formula

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

Учитывая, что вы ищете простую формулу, это, вероятно, самый простой способ сделать это, предполагая, что Земля является сферой периметра 40075 км.

Длина в метрах 1° широты = всегда 111,32 км

Длина в метрах 1° долготы = 40075 км * cos(широта) / 360

Для аппроксимации коротких расстояний между двумя координатами я использовал формулы из http://en.wikipedia.org/wiki/Lat-lon:

m_per_deg_lat = 111132.954 - 559.822 * cos( 2 * latMid ) + 1.175 * cos( 4 * latMid);
m_per_deg_lon = 111132.954 * cos ( latMid );

,

В приведенном ниже коде я оставил необработанные числа, чтобы показать их связь с формулой из Википедии.

double latMid, m_per_deg_lat, m_per_deg_lon, deltaLat, deltaLon,dist_m;

latMid = (Lat1+Lat2 )/2.0;  // or just use Lat1 for slightly less accurate estimate


m_per_deg_lat = 111132.954 - 559.822 * cos( 2.0 * latMid ) + 1.175 * cos( 4.0 * latMid);
m_per_deg_lon = (3.14159265359/180 ) * 6367449 * cos ( latMid );

deltaLat = fabs(Lat1 - Lat2);
deltaLon = fabs(Lon1 - Lon2);

dist_m = sqrt (  pow( deltaLat * m_per_deg_lat,2) + pow( deltaLon * m_per_deg_lon , 2) );

Запись в Википедии гласит, что дистанционные калибры находятся в пределах 0,6 м для 100 км в продольном направлении и 1 см для 100 км в продольном направлении, но я не проверял это, так как где-то рядом эта точность подходит для моего использования.

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

Вот R-версия функции bh-s, на всякий случай:

measure <- function(lon1,lat1,lon2,lat2) {
    R <- 6378.137                                # radius of earth in Km
    dLat <- (lat2-lat1)*pi/180
    dLon <- (lon2-lon1)*pi/180
    a <- sin((dLat/2))^2 + cos(lat1*pi/180)*cos(lat2*pi/180)*(sin(dLon/2))^2
    c <- 2 * atan2(sqrt(a), sqrt(1-a))
    d <- R * c
    return (d * 1000)                            # distance in meters
}

Земля раздражающе неровная поверхность, поэтому нет простой формулы, чтобы сделать это точно. Вы должны жить с приблизительной моделью Земли и проецировать на нее свои координаты. Для этого я обычно использую модель WGS 84. Это то, что устройства GPS обычно используют для решения точно такой же проблемы.

У NOAA есть программное обеспечение, которое вы можете скачать, чтобы помочь с этим на их веб-сайте.

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

Однако сделать это не обязательно сложно. Похоже, вы используете Java, поэтому я бы порекомендовал посмотреть что-то вроде GDAL. Он предоставляет Java-обертки для своих подпрограмм, и у них есть все инструменты, необходимые для преобразования из широты / долготы (географические координаты) в UTM (проецируемая система координат) или в какую-либо другую разумную проекцию карты.

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

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

попробуйте http://www.movable-type.co.uk/scripts/latlong.html немного методов и кода на разных языках.

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

Оригинальный плакат спрашивал: «Если у меня есть показания широты или долготы в стандартном формате NMEA, есть ли простой способ / формула для преобразования этого показания в метры»

Я давно не использовал Java, поэтому я нашел решение здесь, в «PARI». Просто подставьте широту и долготу вашей точки в приведенные ниже уравнения, чтобы получить точную длину дуги и масштабы в метрах в (секунду долготы) и метрах в (секунду широты). Я написал эти уравнения для бесплатной математической программы PARI с открытым исходным кодом для Mac и ПК. Вы можете просто вставить в него следующее, и я покажу, как применить их к двум составленным точкам:

      \\=======Arc lengths along Latitude and Longitude and the respective scales:
\p300
default(format,"g.42")
dms(u)=[truncate(u),truncate((u-truncate(u))*60),((u-truncate(u))*60-truncate((u-truncate(u))*60))*60];
SpinEarthRadiansPerSec=7.292115e-5;\
GMearth=3986005e8;\
J2earth=108263e-8;\
re=6378137;\
ecc=solve(ecc=.0001,.9999,eccp=ecc/sqrt(1-ecc^2);qecc=(1+3/eccp^2)*atan(eccp)-3/eccp;ecc^2-(3*J2earth+4/15*SpinEarthRadiansPerSec^2*re^3/GMearth*ecc^3/qecc));\
e2=ecc^2;\
b2=1-e2;\
b=sqrt(b2);\
fl=1-b;\
rfl=1/fl;\
U0=GMearth/ecc/re*atan(eccp)+1/3*SpinEarthRadiansPerSec^2*re^2;\
HeightAboveEllipsoid=0;\
reh=re+HeightAboveEllipsoid;\
longscale(lat)=reh*Pi/648000/sqrt(1+b2*(tan(lat))^2);
latscale(lat)=reh*b*Pi/648000/(1-e2*(sin(lat))^2)^(3/2);
longarc(lat,long1,long2)=longscale(lat)*648000/Pi*(long2-long1);
latarc(lat1,lat2)=(intnum(th=lat1,lat2,sqrt(1-e2*(sin(th))^2))+e2/2*sin(2*lat1)/sqrt(1-e2*(sin(lat1))^2)-e2/2*sin(2*lat2)/sqrt(1-e2*(sin(lat2))^2))*reh;
\\=======

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

[Широта, Долгота]=[+30, 30]

а другой в

[Широта, Долгота]=[+30:00:16,237796,30:00:18,655502].

Чтобы преобразовать эти точки в метры в двух координатах:

Я могу установить систему координат в метрах с первой точкой в ​​начале координат: [0,0] метров. Затем я могу определить координатную ось x как строго с востока на запад, а ось y как строго с севера на юг.

Тогда координаты второй точки:

      ? [longarc(30*Pi/180,30*Pi/180,((18.655502/60+0)/60+30)*Pi/180),latarc(30*Pi/180,((16.237796/60+0)/60+30)*Pi/180)]
%9 = [499.999998389040060103621525561027349597207, 499.999990137812119668486524932382720606325]

Предупреждение о точности: обратите внимание, однако: поскольку поверхность Земли искривлена, полученные на ней двумерные координаты не могут точно следовать тем же правилам, что и декартовы координаты, такие как теорема Пифагора. Также линии, указывающие строго с севера на юг, сходятся в северном полушарии. На Северном полюсе становится очевидным, что линии север-юг не годятся для линий, параллельных оси Y на карте.

На 30 градусах широты и длине 500 метров координата x изменяется на 1,0228 дюйма, если масштаб установлен на [0,+500] вместо [0,0]:

      ? [longarc(((18.655502/60+0)/60+30)*Pi/180,30*Pi/180,((18.655502/60+0)/60+30)*Pi/180),latarc(30*Pi/180,((16.237796/60+0)/60+30)*Pi/180)]
%10 = [499.974018595036400823218815901067566617826, 499.999990137812119668486524932382720606325]
? (%10[1]-%9[1])*1000/25.4
%12 = -1.02282653557713702372872677007019603860352
? 

Погрешность в 500 м/1 дюйм составляет всего около 1/20000, что достаточно для большинства диаграмм, но можно уменьшить погрешность в 1 дюйм. Для совершенно общего способа преобразования широты, долготы в ортогональные координаты x, y для любой точки земного шара я бы предпочел отказаться от выравнивания координатных линий с восток-запад и север-юг, за исключением того, что центральная ось Y указывает должным образом Север. Например, вы можете вращать земной шар вокруг полюсов (вокруг трехмерной оси Z).
поэтому центральная точка на вашей карте находится на нулевой долготе. Затем наклоните земной шар (вокруг трехмерной оси Y), чтобы центральная точка оказалась на широте, долготе = [0,0]. На земном шаре точки с широтой, долготой = [0,0] находятся дальше всего от полюсов и имеют вокруг них сетку широты, долготы, которая является наиболее ортогональной, поэтому вы можете использовать эти новые линии «Север-Юг», «Восток-Запад». как координатные линии x,y, не подвергаясь растяжению, которое произошло бы при этом до поворота центральной точки от полюса. Показ явного примера этого занял бы гораздо больше места.

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

    'below is from
'http://www.zipcodeworld.com/samples/distance.vbnet.html
Public Function distance(ByVal lat1 As Double, ByVal lon1 As Double, _
                         ByVal lat2 As Double, ByVal lon2 As Double, _
                         Optional ByVal unit As Char = "M"c) As Double
    Dim theta As Double = lon1 - lon2
    Dim dist As Double = Math.Sin(deg2rad(lat1)) * Math.Sin(deg2rad(lat2)) + _
                            Math.Cos(deg2rad(lat1)) * Math.Cos(deg2rad(lat2)) * _
                            Math.Cos(deg2rad(theta))
    dist = Math.Acos(dist)
    dist = rad2deg(dist)
    dist = dist * 60 * 1.1515
    If unit = "K" Then
        dist = dist * 1.609344
    ElseIf unit = "N" Then
        dist = dist * 0.8684
    End If
    Return dist
End Function
Public Function Haversine(ByVal lat1 As Double, ByVal lon1 As Double, _
                         ByVal lat2 As Double, ByVal lon2 As Double, _
                         Optional ByVal unit As Char = "M"c) As Double
    Dim R As Double = 6371 'earth radius in km
    Dim dLat As Double
    Dim dLon As Double
    Dim a As Double
    Dim c As Double
    Dim d As Double
    dLat = deg2rad(lat2 - lat1)
    dLon = deg2rad((lon2 - lon1))
    a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) + Math.Cos(deg2rad(lat1)) * _
            Math.Cos(deg2rad(lat2)) * Math.Sin(dLon / 2) * Math.Sin(dLon / 2)
    c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a))
    d = R * c
    Select Case unit.ToString.ToUpper
        Case "M"c
            d = d * 0.62137119
        Case "N"c
            d = d * 0.5399568
    End Select
    Return d
End Function
Private Function deg2rad(ByVal deg As Double) As Double
    Return (deg * Math.PI / 180.0)
End Function
Private Function rad2deg(ByVal rad As Double) As Double
    Return rad / Math.PI * 180.0
End Function

Вот функция MySQL:

      SET @radius_of_earth = 6378.137; -- In kilometers

DROP FUNCTION IF EXISTS Measure;
DELIMITER //
CREATE FUNCTION Measure (lat1 REAL, lon1 REAL, lat2 REAL, lon2 REAL) RETURNS REAL
BEGIN
-- Multiply by 1000 to convert millimeters to meters
RETURN 2 * @radius_of_earth * 1000 * ASIN(SQRT(
    POW(SIN((lat2 - lat1) / 2 * PI() / 180), 2) +
    COS(lat1 * PI() / 180) *
    COS(lat2 * PI() / 180) *
    POW(SIN((lon2 - lon1) / 2 * PI() / 180), 2)
));
END; //
DELIMITER ;

Зачем ограничиваться одной степенью?

Формула основана на пропорции:

      distance[m] : distance[deg] = max circumference[m] : 360[deg]

Допустим, вам дан угол для широты и один для долготы в градусах:(longitude[deg], latitude[deg])

Что касается широты , то всегда проходит за полюса. В сферической модели с радиусом R (в метрах) максимальная длина окружности равна2 * pi * Rи пропорции решают:

      latitude[m] = ( 2 * pi * R[m] * latitude[deg] ) / 360[deg]

(обратите внимание, что градус и градус упрощается, а то, что остается, это метры с обеих сторон).

Для долготы _max circumferenceпропорциональна косинусу широты (как вы можете себе представить, бег по кругу вокруг северного полюса короче, чем бег по кругу вокруг экватора), поэтому2 * pi * R * cos(latitude[rad]). Поэтому

      longitude distance[m] = ( 2 * pi * R[m] * cos(latitude[rad]) * longitude[deg] ) / 360[deg]

Обратите внимание, что вам нужно будет преобразовать широту из градусов в рад перед вычислением cos.


Опуская подробности для тех, кто просто ищет формулу:

      lat_in_m = 111132.954 * lat_in_degree / 360
lon_in_m = 111132.954 * cos(lat_in_radians) * lon_in_deg ) / 360

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

Чтобы напрямую преобразовать расстояние между двумя широтами/долготами в xy-метры между ними, вы можете использовать уравнения радиуса Земли из Википедии. В C это:

      N = a/sqrt(1-e2)*(sin(Lat1)*sin(Lat1))));
latlen = ((1-e2)/(a*a))*N*N*N*(Lat1-Lat2)*PI/180;
lonlen = (N*cos(Lat1))*(Lon1-Lon2)*PI/180;

Где a — радиус Земли по экватору, а e2 — квадрат эксцентриситета Земли. Уравнения получают радиусы между двумя точками, а PI/180 — длины окружностей этих радиусов.

Вот версия в Swift:

      func toDegreeAt(point: CLLocationCoordinate2D) -> CLLocationDegrees {
    let latitude = point.latitude  
    let earthRadiusInMetersAtSeaLevel = 6378137.0
    let earthRadiusInMetersAtPole = 6356752.314
    
    let r1 = earthRadiusInMetersAtSeaLevel
    let r2 = earthRadiusInMetersAtPole
    let beta = latitude

    let earthRadiuseAtGivenLatitude = (
      ( pow(pow(r1, 2) * cos(beta), 2) + pow(pow(r2, 2) * sin(beta), 2) ) /
      ( pow(r1 * cos(beta), 2) + pow(r2 * sin(beta), 2) )
    )
    .squareRoot()
      
    let metersInOneDegree = (2 * Double.pi * earthRadiuseAtGivenLatitude * 1.0) / 360.0
    let value: CLLocationDegrees = self / metersInOneDegree
    return value
  }

На основании среднего расстояния для разложения по Земле.

1 ° = 111 км;

Пересчитав это в радианах и разделив на метры, возьмите магическое число для RAD в метрах: 0,000008998719243599958;

затем:

const RAD = 0.000008998719243599958;
Math.sqrt(Math.pow(lat1 - lat2, 2) + Math.pow(long1 - long2, 2)) / RAD;

Если вы хотите простое решение, используйте формулу Хаверсайна, как указано в других комментариях. Если у вас есть приложение, чувствительное к точности, имейте в виду, что формула Хаверсайна не гарантирует точность лучше 0,5%, поскольку предполагается, что Земля - ​​это круг. Чтобы считать Землю сплюснутым сфероидом, рассмотрим использование формул Винсенти. Кроме того, я не уверен, какой радиус мы должны использовать с формулой Хаверсайна: {Экватор: 6 378,137 км, Полярный: 6 356 752 км, Объемный: 6 371,0088 км}.

Вам нужно преобразовать координаты в радианы, чтобы сделать сферическую геометрию. После преобразования вы можете рассчитать расстояние между двумя точками. Расстояние может быть преобразовано в любую меру, которую вы хотите.

Другие вопросы по тегам