计算对象的纬度/经度/长度值,当前纬度/长度/空格+在x,y,z上移动

时间:2012-12-04 19:54:05

标签: algorithm geometry coordinates

我认为在Google上搜索时会有很多结果,但事实证明,在大多数情况下,就像在stackoverflow上一样,问题非常具体,包括Google地图,某些GIS或现实世界椭圆形。

就我而言,

  • 我有一个lat,long和alt值的对象,其中lat和long是以度为单位的浮点数,alt是一个浮点数作为十进制值,表示距离球体中心的距离。所以没有分钟(10°30'是10,5)。
  • 对象在所有3轴x,y和z 上移动,其中移动相对于其当前位置
  • 需要计算新的lat,long和alt值

我没有考虑过这个问题,我首先像这样实现它:

刚刚将z运动添加到alt,然后计算虚拟球体的周长以获得每单位度数(米)。有了这个,我先计算了新的lat,然后又计算了新的lat。我知道计算一个接一个的值是错误的,但只要我的“对象世界”中的所有计算都以相同的方式完成,整体行为就可以了。我想了一些事情,比如当物体围绕整个球体时,长值不会改变,并且围绕球体的一半在x轴上比在y轴上不同(x轴:-180到180,y-轴-90到90)以及类似的东西,它起作用。

但后来我意识到我没有考虑到我计算了赤道上每米的度数并且没有考虑其他纬度值。那时我知道事情变得更复杂,我开始在网上搜索。但我没有找到符合我需求的算法。现在我确定这已经很多次了,所以我问这里以防万一有人之前处理过这个话题,并且可以指出一个很好的实现:)。

我发现的是:

  • 将纬度/经度转换为墨卡托投影的算法
  • 用于计算两个纬度/长值对的距离的Haversine公式
  • 其他用途的其他配方奶粉

这对我没有帮助。

对我来说最有帮助的是:Calculate latitude and longitude having meters distance from another latitude/longitude point

但我认为我还没有完全理解它。

  • 弧度课程是什么? (关于我有x和y的动作)
  • 外推方法不考虑海拔高度/地球半径?

如果有人可以帮助我,那真是太棒了!

(旁注:我在Erlang中实现了这一点,但这无关紧要,任何类型的算法都会有所帮助)


更新

我实现了上面提到的功能和下面答案中的功能。在测试时我得到了错误的值,可能是因为我在实现中犯了错误或者计算了错误的测试数据。我们来看看:

实施1:

% My own implementation that only works on the equator so far. Simple calculations as mentioned above.

测试(在赤道上)确定。

实施2:

% @doc calculates new lat+long+alt values for old values + movement vector
% https://stackoverflow.com/questions/5857523/calculate-latitude-and-longitude-having-meters-distance-from-another-latitude-lo
calc_position2(LastCurrLat, LastCurrLong, LastCurrAlt, MoveX, MoveY, MoveZ) ->
    % first the new altitude
    NewCurrAlt = LastCurrAlt + MoveZ,

    % original algorithm: http://williams.best.vwh.net/avform.htm#LL
    % lat=asin(sin(lat1)*cos(d)+cos(lat1)*sin(d)*cos(tc))
    % dlon=atan2(sin(tc)*sin(d)*cos(lat1),cos(d)-sin(lat1)*sin(lat))
    % lon=mod(lon1+dlon +pi,2*pi)-pi
    % where:
    % lat1, lon1    - start point in radians
    % d             - distance in radians
    % tc            - course in radians

    % -> for the found implementation to work some value conversions are needed
    CourseDeg = calc_course(MoveX, MoveY),
    CourseRad = deg_to_rad(CourseDeg), % todo: cleanup: in course the calculated values are rad anyway, converting to deg is just an extra calculation
    Distance = calc_distance(MoveX, MoveY),
    DistanceDeg = calc_degrees_per_meter_at_equator(NewCurrAlt) * Distance,
    DistanceRad = deg_to_rad(DistanceDeg),
    Lat1Rad = deg_to_rad(LastCurrLat),
    Lon1Rad = deg_to_rad(LastCurrLong),

    LatRad = math:asin(math:sin(Lat1Rad) * math:cos(DistanceRad) + math:cos(Lat1Rad) * math:sin(DistanceRad) * math:cos(CourseRad)),
    Dlon = math:atan2(math:sin(CourseRad) * math:sin(DistanceRad) * math:cos(Lat1Rad), math:cos(DistanceRad) - math:sin(Lat1Rad) * math:sin(LatRad)),
    LonRad = remainder((Lon1Rad + Dlon + math:pi()), (2 * math:pi())) - math:pi(),

    NewCurrLat = rad_to_deg(LatRad),
    NewCurrLong = rad_to_deg(LonRad),

    {NewCurrLat, NewCurrLong, NewCurrAlt}.

% some trigonometry
% returns angle between adjacent and hypotenuse, with MoveX as adjacent and MoveY as opposite
calc_course(MoveX, MoveY) ->
    case MoveX > 0 of
        true ->
            case MoveY > 0 of
                true ->
                    % tan(alpha) = opposite / adjacent
                    % arc tan to get the alpha
                    % erlang returns radians -> convert to degrees
                    Deg = rad_to_deg(math:atan(MoveY / MoveX));
                false ->
                    Temp = 360 - rad_to_deg(math:atan((MoveY * -1) / MoveX)),
                    case Temp == 360 of
                        true ->
                            Deg = 0.0;
                        false ->
                            Deg = Temp
                    end
            end;
        false ->
            % attention! MoveX not > 0 -> can be 0 -> div by zero
            case MoveX == 0 of
                true ->
                    case MoveY > 0 of
                        true ->
                            Deg = 90.0;
                        false ->
                            case MoveY == 0 of
                                true ->
                                    Deg = 0.0;
                                false ->
                                    Deg = 270.0
                            end
                    end;
                false -> % MoveX < 0
                    case MoveY > 0 of
                        true ->
                            Deg = 180 - rad_to_deg(math:atan(MoveY / (MoveX * -1)));
                        false ->
                            Deg = 180 + rad_to_deg(math:atan((MoveY * -1) / (MoveX * -1)))
                    end
            end
    end,
    Deg.

rad_to_deg(X) ->
    X * 180 / math:pi().
deg_to_rad(X) ->
    X * math:pi() / 180.

% distance = hypetenuse in Pythagorean theorem
calc_distance(MoveX, MoveY) ->
    math:sqrt(math:pow(MoveX,2) + math:pow(MoveY,2)).

calc_degrees_per_meter_at_equator(Alt) ->
    Circumference = 2 * math:pi() * Alt,
    360 / Circumference.

% erlang rem only operates with integers
% https://stackoverflow.com/questions/9297424/stdremainder-in-erlang
remainder(A, B) ->
    A_div_B = A / B,
    N = round(A_div_B),

    case (abs(N - A_div_B) == 0.5) of
    true ->
        A_div_B_Trunc = trunc(A_div_B),

        New_N = case ((abs(A_div_B_Trunc) rem 2) == 0) of
        true -> A_div_B_Trunc;
        false ->
            case (A_div_B >= 0) of
            true -> A_div_B_Trunc + 1;
            false -> A_div_B_Trunc - 1
            end
        end,
        A - New_N * B;
    false ->
        A - N * B
    end.

试验: 对象位于纬度/经度/高度(10°,10°,6371000m)。没有移动(0m,0m,0m)。预期:

{1.000000e+001,1.000000e+001,6.371000e+006}

但回归:

{1.000000e+001,-3.500000e+002,6.371000e+006}

比预期低360度......

物体在(0°,10°,6371000m),移动(10m,0m,0m)。预期:

{0.000000e+000,1.000009e+001,6.371000e+006}

不确定是否有些数字没有显示。作为经度应该像10.000089932160591。无论如何 - 返回:

{8.993216e-005,-3.500000e+002,6.371000e+006}

虽然我们现在正在移动,但经度值相同?虽然我们没有在Y轴上移动,但是改变了纬度值?

同样的位置怎么样,现在向东移动5,000,000米?

{0.000000e+000,5.496608e+001,6.371000e+006} % expected
{4.496608e+001,-3.500000e+002,6.371000e+006} % returned

实施3:

calc_position3(LastCurrLat, LastCurrLong, LastCurrAlt, MoveX, MoveY, MoveZ) ->
    {CurrX, CurrY, CurrZ} = spherical_to_cartesian(LastCurrLat, LastCurrLong, LastCurrAlt),
    NewX = CurrX + MoveX,
    NewY = CurrY + MoveY,
    NewZ = CurrZ + MoveZ,
    {NewCurrLat, NewCurrLong, NewCurrAlt} = cartesian_to_spherical(NewX, NewY, NewZ),
    {NewCurrLat, NewCurrLong, NewCurrAlt}.

spherical_to_cartesian(Lat, Lon, Alt) ->
    X = Alt * math:cos(Lat) * math:cos(Lon),
    Y = Alt * math:cos(Lat) * math:sin(Lon),
    Z = Alt * math:sin(Lat),
    {X, Y, Z}.

cartesian_to_spherical(X, Y, Z) ->
    R = math:sqrt(math:pow(X,2) + math:pow(Y,2)),
    Alt = math:sqrt(math:pow(X,2) + math:pow(Y,2) + math:pow(Z,2)),
    Lat = math:asin(Z / Alt),
    case R > 0 of
        true ->
            Lon = math:acos(X / R);
        false -> % actually: if R == 0, but it can never be negative (see above)
            Lon = 0
    end,
    {Lat, Lon, Alt}.

上述测试:

物体在(10°,10°,6371000m),没有移动

{1.000000e+001,1.000000e+001,6.371000e+006} % expected
{-5.752220e-001,5.752220e-001,6.371000e+006} % returned

在(0°,10°,6371000m),移动(10m,0m,0m)

{0.000000e+000,1.000009e+001,6.371000e+006} % expected
{0.000000e+000,2.566370e+000,6.370992e+006} % returned

在(0°,10°,6371000m),移动(5000000m,0m,0m)

{0.000000e+000,5.496608e+001,6.371000e+006} % expected
{0.000000e+000,1.670216e+000,3.483159e+006} % returned

所以:我是否错过了一些rad转换或类似的东西?

P.S。:很抱歉语法突出显示错误,但Erlang似乎不可用,所以我选了Shellscript。使它更具可读性。

2 个答案:

答案 0 :(得分:1)

我们假设:

  • 赤道在x-y平面
  • 纬度0,经度0,海拔&gt; 0位于+ x轴
  • 极点在0°经度

根据这些假设,将球形(lat,lng,alt)转换为笛卡儿(x,y,z):

x = alt * cos(lat) * cos(lng)
y = alt * cos(lat) * sin(lng)
z = alt * sin(lat)

要从笛卡儿(x,y,z)转换为球形(lat,lng,alt):

r = sqrt(x*x + y*y)
alt = sqrt(x*x + y*y + z*z)
lat = arcsin(z / alt)
       / arccos(x / r) if r > 0
lng = -|
       \ 0 if r == 0

然后按照@Peter的建议:

(lat, lng, alt) = toSpherical(movement + toCartesian(lat, lng, alt))

答案 1 :(得分:1)

从你对评论的回答中我终于得到了足够的信息:

您已经在地理纬度,经度(等于或类似于WGS84纬度/经度)和高度值的位置给出了物体的位置。高度是从地心测量的。

latitude : [-90 , 90] geographical latitude in decimal degrees, 90° is Northpole<br>
longitude: [-180, 180] longitude in decimal degrees, 0° is Greenwhich
altitude: [0 , INF ] in meters from center of earth.

所以这些坐标被定义为球面坐标,但要注意顺时针与逆时针(见后)

此外,您已经给出了以米为单位测量的运动矢量(dx,dy,dz),它定义了当前位置的相对运动。
这些矢量被定义为笛卡尔矢量。

您的应用程序既不用于导航,也不用于飞行控制,它更适用于游戏领域。

对于计算,您必须知道x,y,z Achsis的相关位置。您应该使用ECEF使用的相同轴对齐。

您需要执行以下步骤:

首先必须知道地理学位不是数学学位:有时候知道数学学位是逆时针的,重要的是它在地理学上是顺时针测量的。 之后你的坐标必须转换成弧度。

将球面坐标转换为笛卡尔空间。 (球形到笛卡尔变换:见http://en.wikipedia.org/wiki/Spherical_coordinate_system) 但是使用Math.atan2()而不是atan()。

(您可以使用http://www.random-science-tools.com/maths/coordinate-converter.htm检查您的代码) 检查这个转换是否也用在Ted Hopp的答案中。

转换后,您在笛卡尔x,y,z空间中有一个坐标,单位为= 1m。

接下来是笛卡儿运动矢量:确保或转换它,使得您具有正确的轴对齐,dx对应于x,-dx对应于-x,对于y,dy,z,dz也是如此点。

然后将该点添加到dx:

p2.x = p.x + dx;
p2.y = p.y + dy;
p2.z = p.z + dz;

然后重新转换回球形坐标,如Wiki链接或Ted Hopp所示。

您在评论中提供的另一个链接(到stackoverflow)执行其他类型的转换,它们不适用于您的问题。

我建议一个特殊的测试用例:将一个球形点设置为经度= 179.9999999然后加100m,结果应该是-180.0000xx +逗号之后的东西(两个喷气式战斗机在遍历基准限制时因此问题崩溃)