将[经度, 纬度, 高度]格式的数据转换为[x, y, z]格式

1 投票
2 回答
76 浏览
提问于 2025-04-14 16:19

我有一个关于航班的数据集。飞机和地面站之间通过通信,利用GPS我可以得到飞机当前位置和地面站位置之间的距离,还有它们的经度、纬度和高度。

现在我需要把这些经度、纬度和高度的数据转换成一个x,y,z的坐标系统。

因为地面站的位置是固定的,我想把它放在坐标系统的原点,也就是(0,0,0)。我在寻找一个可以处理这种转换的Python模块。

作为例子,我提供了地面站的实际数据和飞机的一个位置,这些数据需要被转换:

地面站 [应该是(0, 0, 0)]
纬度: 41.947.694
经度: 3.209.083
高度(米): 379.41

飞机位置:
纬度: 419.763.015.750
经度: 34.890.851.772
高度(米): 971.32

如果我理解正确的话,把地面站放到原点,我需要从飞机的位置中减去地面站的位置,这样就变成:

地面站:
纬度: 0
经度: 0
高度(米): 0

飞机位置:
纬度: 419.721.068.056
经度: 34.887.642.689
高度(米): 591.91

因为我对处理这些数据还很陌生,尝试了一些简单的解决方案但没有找到能转换这些数据的方法,所以希望你们能帮我找到解决方案。

我需要转换后的数据来模拟3D中的椭球体,以找到可能的交点。其实也可以用其他方法来实现,比如用Matlab也可以,虽然我之前没有用过。

2 个回答

0

将经纬度和高度的GPS坐标转换成“xyz”坐标,这些坐标是基于WGS84参考椭球体的地理坐标(这里的垂直方向是指向椭球体的法线,而不是指向地球中心的方向),这个过程在很多编程库中都有实现。既然提到了Matlab,它的实现方式是geodetic2ecef

这个过程在很多书籍和网站上都有描述,甚至在维基百科上也能找到,所以我们可以放心地从头开始实现它。

如果你把站点和飞机的坐标都转换成这个ECEF的x,y,z坐标,你就可以计算它们之间的差值,这样你就得到了一个以站点为(0, 0, 0)的XYZ坐标系统。

需要注意的是,正如评论中提到的,你不能直接对纬度或经度进行有意义的差值计算。要找出两点之间的距离,我们只能像处理向量或笛卡尔坐标那样进行大小的相减;纬度和经度并不是线性相关的。

现在,之前计算出的xyz差值本身并没有太大意义,因为它们的参考方向是全局的——z轴是与地球自转轴平行的,而xz平面是与本初子午线平行的。

为此,我建议对这些差值进行“本地化”,通过添加一次(双重)旋转,将这些全局方向调整到与参考站相关的方向:新的z轴指向地理垂直方向(向天顶),新的x轴指向站点的北方,y轴则指向东方。

同样,这些计算在文献和网上都有详细记录,所以在这里从头实现它们是安全的。

下面是一个例子,可能的Python实现,虽然效率不高,因为它重复计算了三角函数,但我的目的是尽量让它清晰易懂,而不是追求效率。

import math

a = 6378137  # semi major axis, WGS84
# f = 1 / 298.257223563 # WGS84
e2 = 6.69437999014e-3  # eccentricity, WGS84 e2 = 1 - (1-f)*(1-f)


def gps_to_geocentric_ecef(lat, lon, alt):
    # converts geodetic latitude, longitude, altitude based on WGS84 (GPS coordinates)
    # to geocentric x, y, z in meters (ECEF), sqrt(x^2+y^2+z^2) = geocentric radius
    # |z| distance to equatorial plane, |x| distance to the plane of the prime meridian
    # https://en.wikipedia.org/wiki/Geodetic_coordinates#Conversion
    # https://github.com/proj4js/proj4js/blob/master/lib/datumUtils.js#L68C5-L70C44
    # verify: https://tool-online.com/en/coordinate-converter.php (right select WORLD and XYZ (geocentric))
    # or: https://www.oc.nps.edu/oc2902w/coord/llhxyz.htm - paste lat, lon, height, then press "LLH to ECEF"

    lat = math.radians(lat)
    lon = math.radians(lon)
    sin_lat = math.sin(lat)
    cos_lat = math.cos(lat)
    cos_lon = math.cos(lon)
    sin_lon = math.sin(lon)

    rn = a / (math.sqrt(1 - e2 * sin_lat * sin_lat))
    x = (rn + alt) * cos_lat * cos_lon
    y = (rn + alt) * cos_lat * sin_lon
    z = ((rn * (1 - e2)) + alt) * sin_lat

    return [x, y, z]


def rotate_ecef_to_local_xyz(dx, dy, dz, lat, lon):
    # rotates the coordinate differences dx, dy, dz fromm the global frame (ECEF) to
    # a local frame that has the geodetic normal to (lat, lon) as z-axis, while
    # the new x-axis is towards North and the y-axis towards East

    lat = math.radians(lat)
    lon = math.radians(lon)

    cos_lon = math.cos(lon)
    sin_lon = math.sin(lon)
    cos_lat = math.cos(lat)
    sin_lat = math.sin(lat)
    dx, dy, dz = (- dx * cos_lon * sin_lat - dy * sin_lon * sin_lat + dz * cos_lat,
                  - dx * sin_lon + dy * cos_lon,
                  dx * cos_lon * cos_lat + dy * sin_lon * cos_lat + dz * sin_lat)

    return [dx, dy, dz]


def main():
    bgr_lat, bgr_lon, bgr_alt = [41.947694, 3.209083, 379.41]
    airplane_gps = [
        [41.9763015750,  3.4890851772, 971.32]
    ]

    # ecef coordinates for the station:
    bgr_xyz = gps_to_geocentric_ecef(bgr_lat, bgr_lon, bgr_alt)

    # ecef coordinates for the (first) airplane:
    plane_xyz = gps_to_geocentric_ecef(*airplane_gps[0])

    # xyz position of the airplane relative to the station, ecef orientation:
    diff_xyz = [plane_xyz[i] - bgr_xyz[i] for i in range(3)]

    # xyz position of the airplane relative to the station, station-local orientation
    local_xyz = rotate_ecef_to_local_xyz(*diff_xyz, bgr_lat, bgr_lon)

    print(local_xyz)  # final result, in meters (x-towards North, y-towards East, z-towards Zenith)


if __name__ == '__main__':
    main()

由于高度是以米为单位给出的,我也使用了WGS84的半长轴(也是以米为单位),结果也是以米为单位:这个平面距离北方3215米,距离东方23210米,高度为549米(这是从通过参考站BGR的切平面测量的高度)。

虽然我通过多种方式仔细验证了这段代码,但我还是希望没有人会基于它来飞飞机 :)

0

你可以使用pyproj这个库,并定义一下

import pyproj
import math

P = pyproj.Proj(proj='utm', zone=32, ellps='WGS84', preserve_units=True)
G = pyproj.Geod(ellps='WGS84')


base_x_offset = 19930.039590830565
base_y_offset = 4660215.067408236

def lonlat_to_xy(Lon, Lat):
    p = P(Lon, Lat)    
    return (p[0] - base_x_offset, p[1] - base_y_offset)    

def xy_to_lonlat(x,y):
    return P(x + base_x_offset, y + base_y_offset, inverse=True)    

def distance(Lat1, Lon1, Lat2, Lon2):
    return G.inv(Lon1, Lat1, Lon2, Lat2)[2]

然后用下面的代码来检查:

base_lon = 3.209083
base_lat = 41.947694

plane_lon = 3.4890851772
plane_lat = 41.9763015750

p_base = lonlat_to_xy(base_lon, base_lat)
print(p_base)

p_plane = lonlat_to_xy(plane_lon, plane_lat)
print(p_plane)

还有

xy_to_lonlat( lonlat_to_xy(plane_lon, plane_lat)[0] , lonlat_to_xy(plane_lon, plane_lat)[1] )

xy_to_lonlat( lonlat_to_xy(base_lon, base_lat)[0] , lonlat_to_xy(base_lon, base_lat)[1] )

不过,这个方法没有处理z坐标。而且在把一个球体转换成平面地图的时候,可能会出现很多问题。

撰写回答