如何找到给定纬度/经度以北 x 公里的纬度/经度?

Posted

技术标签:

【中文标题】如何找到给定纬度/经度以北 x 公里的纬度/经度?【英文标题】:How do I find the lat/long that is x km north of a given lat/long? 【发布时间】:2010-11-10 15:24:02 【问题描述】:

我有一些生成谷歌地图的 C# 代码。此代码查看我需要在地图上绘制的所有点,然后计算出矩形的边界以包含这些点。然后它将此边界传递给 Google Maps API 以适当地设置缩放级别以显示地图上的所有点。

此代码运行良好,但我有一个新要求。

其中一个点可能具有与之相关的精度。如果是这种情况,那么我在该点周围画一个圆,半径设置为精度值。这再次工作正常,但是我的边界检查现在没有做我想要做的事情。我想让边界框包含完整的圆圈。

这需要一种算法来获取点 x 并计算点 y,该点位于 x 以北 z 米处和 x 以南 z 米处。

有没有人有这个算法,最好是在 C# 中。我确实找到了一个通用算法here,但我似乎没有正确实现这一点,因为我得到的答案是漂移了 1000 公里。

这是通用示例

Lat/lon given radial and distance

A point lat,lon is a distance d out on the tc radial from point 1 if:

     lat=asin(sin(lat1)*cos(d)+cos(lat1)*sin(d)*cos(tc))
     IF (cos(lat)=0)
        lon=lon1      // endpoint a pole
     ELSE
        lon=mod(lon1-asin(sin(tc)*sin(d)/cos(lat))+pi,2*pi)-pi
     ENDIF

这是我的 C# 翻译。

  // Extend a Point North/South by the specified distance
    public static Point ExtendPoint(Point _pt, int _distance, int _bearing )
    
        Decimal lat = 0.0;
        Decimal lng = 0.0;

        lat = Math.Asin(Math.Sin(_pt.Lat) * Math.Cos(_distance) + Math.Cos(_pt.Lat) * 
            Math.Sin(_distance) * Math.Cos(_bearing));

         if (Math.Cos(lat) == 0)
         
            lng = _pt.Lng;      // endpoint a pole
         
         else 
         
             lng = (
                 (_pt.Lng - Math.Asin(Math.Sin(_bearing) * Math.Sin(_distance) / Math.Cos(lat)) 
                 + Math.PI) % (2 * Math.PI)) - Math.PI;
         

         ret = new Point(lat,lng);
         return ret;
    

我调用这个函数时,方位角为 0 来计算新的北纬位置,值 180 来计算新的南纬位置。

谁能看到我做错了什么或者提供一个已知的工作算法?

【问题讨论】:

对于它的价值,我有一个 php 示例,它可以执行 OP 请求的操作。在我的示例中,它在起始纬度/经度坐标周围绘制了一个框,但代码可以很容易地用于获取单个点、X 公里或英里以外的点。 richardpeacock.com/blog/2011/11/… 【参考方案1】:

对于想要java版Eirch的代码的人

/**
 * move latlng point by rang and bearing
 *
 * @param latLng  point
 * @param range   range in meters
 * @param bearing bearing in degrees
 * @return new LatLng
 */
public static LatLng moveLatLng(LatLng latLng, double range, double bearing) 
    double EarthRadius = 6378137.0;
    double DegreesToRadians = Math.PI / 180.0;
    double RadiansToDegrees = 180.0 / Math.PI;

    final double latA = latLng.latitude * DegreesToRadians;
    final double lonA = latLng.longitude * DegreesToRadians;
    final double angularDistance = range / EarthRadius;
    final double trueCourse = bearing * DegreesToRadians;

    final double lat = Math.asin(
            Math.sin(latA) * Math.cos(angularDistance) +
                    Math.cos(latA) * Math.sin(angularDistance) * Math.cos(trueCourse));

    final double dlon = Math.atan2(
            Math.sin(trueCourse) * Math.sin(angularDistance) * Math.cos(latA),
            Math.cos(angularDistance) - Math.sin(latA) * Math.sin(lat));

    final double lon = ((lonA + dlon + Math.PI) % (Math.PI * 2)) - Math.PI;

    return new LatLng(lat * RadiansToDegrees, lon * RadiansToDegrees);

【讨论】:

【参考方案2】:

如果您有给定的纬度和经度,您可以像这样计算 x 公里纬度变化的正确纬度和经度:

new-lat = ((old-km-north + x-km-change)/40,075) * 360)
           ^ is the ratio of the                  ^ times the ratio of the circle
           earth the change                       by 360 to get the total ratio 
           covers.                                covered in degrees.

这同样适用于经度。如果你有总距离加上变化,你可以用类似的方式计算总度数。

new-long = ((old-km-east + x-km-change)/40,075) * 360)
           ^ is the ratio of the                  ^ times the ratio of the circle
           earth the change                       by 360 to get the total ratio 
           covers.                                covered in degrees.

同样,这些计算应该有效,但我在这里失去了纯粹的直觉,但逻辑似乎确实成立。

编辑:正如 Skizz 所指出的,需要使用 2.pi.r.cos(lat) 或 40074.cos(lat) 将 40,075 调整到任何给定纬度的地球圆周

【讨论】:

嗯,这看起来容易多了 :-) 谢谢。我现在试试。什么是 40.075 幻数。是 KM 覆盖的秒数(度)吗? 那是地球的圆周,尽管试一试。这不符合直觉,因此可能也需要一些调整。 KevDog 的意思是 40,075 随着纬度的增加而减少 - 极点以东 1 公里将增加赤道以东 1 公里以上的经度。因此,您需要将 40,075 替换为给定纬度的周长,即:2.pi.rs.cos(lat),其中 rs 是地球的半径,lat 是纬度。它确实假设地球是球形的。但是,如果您向东 1 公里然后向北 1 公里而不是向北 1 公里然后向东 1 公里,这种方法会产生不同的结果。 如果您只是在原始位置添加度数,为什么不保持经度不变(因为他只是向北,经度不应该改变)并且只需将 km-change/40,075 添加到 lat ?这个方程对我来说似乎很脆弱并且缺乏大量数据。当有非常详尽的方程式描述如何计算时,为什么还要凭直觉? 谁能告诉我old-km-north/ old-km-east指的是什么?【参考方案3】:

对于懒惰的人,(像我一样;))复制粘贴解决方案,Erich Mirabal 的版本,改动很小:

using System.Device.Location; // add reference to System.Device.dll
public static class GeoUtils

    /// <summary>
    /// Calculates the end-point from a given source at a given range (meters) and bearing (degrees).
    /// This methods uses simple geometry equations to calculate the end-point.
    /// </summary>
    /// <param name="source">Point of origin</param>
    /// <param name="range">Range in meters</param>
    /// <param name="bearing">Bearing in degrees</param>
    /// <returns>End-point from the source given the desired range and bearing.</returns>
    public static GeoCoordinate CalculateDerivedPosition(this GeoCoordinate source, double range, double bearing)
    
        var latA = source.Latitude * DegreesToRadians;
        var lonA = source.Longitude * DegreesToRadians;
        var angularDistance = range / EarthRadius;
        var trueCourse = bearing * DegreesToRadians;

        var lat = Math.Asin(
            Math.Sin(latA) * Math.Cos(angularDistance) +
            Math.Cos(latA) * Math.Sin(angularDistance) * Math.Cos(trueCourse));

        var dlon = Math.Atan2(
            Math.Sin(trueCourse) * Math.Sin(angularDistance) * Math.Cos(latA),
            Math.Cos(angularDistance) - Math.Sin(latA) * Math.Sin(lat));

        var lon = ((lonA + dlon + Math.PI) % (Math.PI*2)) - Math.PI;

        return new GeoCoordinate(
            lat * RadiansToDegrees,
            lon * RadiansToDegrees,
            source.Altitude);
    

    private const double DegreesToRadians = Math.PI/180.0;
    private const double RadiansToDegrees = 180.0/ Math.PI;
    private const double EarthRadius = 6378137.0;

用法:

[TestClass]
public class CalculateDerivedPositionUnitTest

    [TestMethod]
    public void OneDegreeSquareAtEquator()
    
        var center = new GeoCoordinate(0, 0);
        var radius = 111320;
        var southBound = center.CalculateDerivedPosition(radius, -180);
        var westBound = center.CalculateDerivedPosition(radius, -90);
        var eastBound = center.CalculateDerivedPosition(radius, 90);
        var northBound = center.CalculateDerivedPosition(radius, 0);

        Console.Write($"leftBottom: southBound.Latitude , westBound.Longitude rightTop: northBound.Latitude , eastBound.Longitude");
    

【讨论】:

【参考方案4】:

Ed William's rather awesome site 上的两个方程有问题...但我没有分析它们以了解原因。

I found here 的第三个等式似乎给出了正确的结果。

这是 php 中的测试用例...第三个等式是正确的,前两个给出的经度值非常不正确。

<?php
            $lon1 = -108.553412; $lat1 = 35.467155; $linDistance = .5; $bearing = 170;
            $lon1 = deg2rad($lon1); $lat1 = deg2rad($lat1);
            $distance = $linDistance/6371;  // convert dist to angular distance in radians
            $bearing = deg2rad($bearing);

            echo "lon1: " . rad2deg($lon1) . " lat1: " . rad2deg($lat1) . "<br>\n";

// doesn't work
            $lat2 = asin(sin($lat1) * cos($distance) + cos($lat1) * sin($distance) * cos($bearing) );
            $dlon = atan2(sin($bearing) * sin($distance) * cos($lat1), cos($distance) - sin($lat1) * sin($lat2));
            $lon2 = (($lon1 - $dlon + M_PI) % (2 * M_PI)) - M_PI;  // normalise to -180...+180

            echo "lon2: " . rad2deg($lon2) . " lat2: " . rad2deg($lat2) . "<br>\n";

// same results as above
            $lat3 = asin( (sin($lat1) * cos($distance)) + (cos($lat1) * sin($distance) * cos($bearing)));
            $lon3 = (($lon1 - (asin(sin($bearing) * sin($distance) / cos($lat3))) + M_PI) % (2 * M_PI)) - M_PI;

            echo "lon3: " . rad2deg($lon3) . " lat3: " . rad2deg($lat3) . "<br>\n";

// gives correct answer... go figure
            $lat4 = asin(sin($lat1) * cos($linDistance/6371) + cos($lat1) * sin($linDistance/6371) * cos($bearing) );
            $lon4 = $lon1 + atan2( (sin($bearing) * sin($linDistance/6371) * cos($lat1) ), (cos($linDistance/6371) - sin($lat1) * sin($lat2)));

            echo "lon4: " . rad2deg($lon4) . " lat4: " . rad2deg($lat4) . "<br>\n";
?>

注意,我收到了前两个方程的作者 (Ed Williams) 的电子邮件:

来自我的“实施笔记”:

注意 mod 功能。这似乎在 不同的语言,对符号是否有不同的约定 结果遵循除数或被除数的符号。 (我们想要标志 跟随除数或成为欧几里得。 C 的 fmod 和 Java 的 % 不起作用。) 在本文档中,Mod(y,x) 是 y 除以 x 的余数,并且始终 位于 0

如果您有一个 floor 函数(Excel 中的 int),则返回 floor(x)= “小于或等于 x 的最大整数”,例如地板(-2.3)=-3 和 地板(2.3) =2

mod(y,x) = y - x*floor(y/x)

以下应该在没有地板功能的情况下工作 - 不管 "int" 是否向下截断或舍入:

mod=y - x * int(y/x)
if ( mod < 0) mod = mod + x

php 就像 C 中的 fmod,但出于我的目的,它是“错误的”。

【讨论】:

【参考方案5】:

我不确定我是否在这里遗漏了什么,但我认为这个问题可以改写为,“我有一个纬度/经度点,我想找到北 x 米和南 x 米的点那个点。”

如果这是问题,那么您不需要找到新的经度(这使事情变得更简单),您只需要一个新的纬度。一个纬度大约是地球上任何地方的 60 海里长,而一海里是 1,852 米。所以,对于新的纬度,南北 x 米:

north_lat = lat + x / (1852 * 60)
north_lat = min(north_lat, 90)

south_lat = lat - x / (1852 * 60)
south_lat = max(south_lat, -90)

这并不完全准确,因为地球不是一个完美的球体,每个纬度之间正好有 60 海里。但是,其他答案假设纬度线是等距的,所以我假设您不关心这一点。如果您对可能引入多少错误感兴趣,***上有一个很好的表格,在此链接上显示了不同纬度的“纬度每 1° 变化的表面距离”:

http://en.wikipedia.org/wiki/Latitude#Degree_length

【讨论】:

【参考方案6】:

我有一段非常相似的代码。与另一个实现相比,它让我得到了非常接近的结果。

我认为你的问题是你使用“距离”作为米的线性距离,而不是弧度的角距离。

/// <summary>
/// Calculates the end-point from a given source at a given range (meters) and bearing (degrees).
/// This methods uses simple geometry equations to calculate the end-point.
/// </summary>
/// <param name="source">Point of origin</param>
/// <param name="range">Range in meters</param>
/// <param name="bearing">Bearing in degrees</param>
/// <returns>End-point from the source given the desired range and bearing.</returns>
public static LatLonAlt CalculateDerivedPosition(LatLonAlt source, double range, double bearing)

    double latA = source.Latitude * UnitConstants.DegreesToRadians;
    double lonA = source.Longitude * UnitConstants.DegreesToRadians;
    double angularDistance = range / GeospatialConstants.EarthRadius;
    double trueCourse = bearing * UnitConstants.DegreesToRadians;

    double lat = Math.Asin(
        Math.Sin(latA) * Math.Cos(angularDistance) + 
        Math.Cos(latA) * Math.Sin(angularDistance) * Math.Cos(trueCourse));

    double dlon = Math.Atan2(
        Math.Sin(trueCourse) * Math.Sin(angularDistance) * Math.Cos(latA), 
        Math.Cos(angularDistance) - Math.Sin(latA) * Math.Sin(lat));

    double lon = ((lonA + dlon + Math.PI) % UnitConstants.TwoPi) - Math.PI;

    return new LatLonAlt(
        lat * UnitConstants.RadiansToDegrees, 
        lon * UnitConstants.RadiansToDegrees, 
        source.Altitude);

在哪里

public const double EarthRadius = 6378137.0;   //  WGS-84 ellipsoid parameters

并且 LatLonAlt 以度/米为单位(转换发生在内部)。 根据需要进行调整。

我假设您可以弄清楚UnitConstants.DegreesToRadians 的值是什么:)

【讨论】:

我在 java 中尝试这种方法,但它不起作用。我得到 dlon = 0 的值。这是我的 dlon 代码:double dlon = Math.atan2(Math.sin(brng)*Math.sin(dist)*Math.cos(lat1), Math.cos(dist) -Math.sin(lat1)*Math.sin(lat2));我已经将纬度和经度的值转换为弧度。每个代码都按照您说的编写。 我无法计算出 UnitConstants.DegreesToRadians、GeospatialConstants.EarthRadius、UnitConstants.TwoPi 和 UnitConstants.RadiansToDegrees 是什么? :( 有人可以帮忙吗? EarthRadius = 6378137.0, TwoPi = Math.PI * 2, DegreesToRadians = 0.0174532925, RadiansToDegrees = 57.2957795。【参考方案7】:

如果先重投影到UTM再看距离会更准确。

希望对你有帮助

【讨论】:

以上是关于如何找到给定纬度/经度以北 x 公里的纬度/经度?的主要内容,如果未能解决你的问题,请参考以下文章

给定 GPS 的地理位置,如何在给定的 x 米半径内找到纬度和经度

给定初始纬度 lng,距离和方位角,在 php 中查找纬度经度点

给定经度和纬度,如何找到国家

找到给定半径内的附近位置

获取给定当前点、距离和方位的纬度/经度

如何计算每10公里2点之间的所有点(纬度和经度)?