Skyfield下EarthSatellite的可见区域

3
如何计算地球卫星下方的面积,以便绘制卫星通过时所覆盖的土地区域?Skyfield中是否有任何便利的功能可实现该目标?
编辑:我想澄清一下我所说的“卫星下方区域”的含义。我需要绘制卫星可观测到的可能最大区域,考虑到地球是一个椭球体。我知道如何绘制卫星路径,但现在我需要绘制一些线条来表示卫星飞过地球时可见的区域。

可以根据地球的中心计算卫星的地心位置,并将其映射到地球上的地心位置。对于区域,您应该制作一个查找表,其中包含卫星的大小,并绘制围绕地心位置的区域。 - rfkortekaas
@rfkortekaas 按面积计算,我指的是从卫星位置可见的最大面积(忽略成像负载光学系统)。我现在正在尝试使用三角学构建三角形,并计算与地球表面相交的边缘长度和从地球中心到卫星的向量。虽然我假设地球是一个球体,但我并不喜欢这种假设。但这应该足以为我计算出卫星可见的粗略圆形区域提供足够的信息。 - Gordon13
1个回答

7
你的编辑清楚地表明了你的需求。当地球被视为一个球体时,从卫星可见的区域可以很容易地计算出来。可以在这里找到一些关于可见部分的背景知识。当地球被视为扁球体时,计算可见面积将会更加困难(甚至可能是不可能的)。我认为最好改写问题的那部分并在数学版面上发帖。
如果你想计算地球被视为一个球体时的可见面积,我们需要在Skyfield中进行一些调整。使用TLE api加载卫星后,您可以轻松获得地球上的子点位置。该库将其称为Geocentric位置,但实际上它是Geodetic位置(其中地球被视为扁球)。为了纠正这一点,我们需要调整Geocentric类的subpoint以使用Geocentric位置的计算而不是Geodetic位置的计算。由于reverse_terra函数存在错误和缺少信息,我们还需要替换该函数。我们还需要能够检索地球半径。这导致以下结果:
from skyfield import api
from skyfield.positionlib import ICRF, Geocentric
from skyfield.constants import (AU_M, ERAD, DEG2RAD,
                                IERS_2010_INVERSE_EARTH_FLATTENING, tau)
from skyfield.units import Angle

from numpy import einsum, sqrt, arctan2, pi, cos, sin

def reverse_terra(xyz_au, gast, iterations=3):
    """Convert a geocentric (x,y,z) at time `t` to latitude and longitude.
    Returns a tuple of latitude, longitude, and elevation whose units
    are radians and meters.  Based on Dr. T.S. Kelso's quite helpful
    article "Orbital Coordinate Systems, Part III":
    https://www.celestrak.com/columns/v02n03/
    """
    x, y, z = xyz_au
    R = sqrt(x*x + y*y)

    lon = (arctan2(y, x) - 15 * DEG2RAD * gast - pi) % tau - pi
    lat = arctan2(z, R)

    a = ERAD / AU_M
    f = 1.0 / IERS_2010_INVERSE_EARTH_FLATTENING
    e2 = 2.0*f - f*f
    i = 0
    C = 1.0
    while i < iterations:
        i += 1
        C = 1.0 / sqrt(1.0 - e2 * (sin(lat) ** 2.0))
        lat = arctan2(z + a * C * e2 * sin(lat), R)
    elevation_m = ((R / cos(lat)) - a * C) * AU_M
    earth_R = (a*C)*AU_M
    return lat, lon, elevation_m, earth_R

def subpoint(self, iterations):
    """Return the latitude an longitude directly beneath this position.

    Returns a :class:`~skyfield.toposlib.Topos` whose ``longitude``
    and ``latitude`` are those of the point on the Earth's surface
    directly beneath this position (according to the center of the
    earth), and whose ``elevation`` is the height of this position
    above the Earth's center.
    """
    if self.center != 399:  # TODO: should an __init__() check this?
        raise ValueError("you can only ask for the geographic subpoint"
                            " of a position measured from Earth's center")
    t = self.t
    xyz_au = einsum('ij...,j...->i...', t.M, self.position.au)
    lat, lon, elevation_m, self.earth_R = reverse_terra(xyz_au, t.gast, iterations)

    from skyfield.toposlib import Topos
    return Topos(latitude=Angle(radians=lat),
                    longitude=Angle(radians=lon),
                    elevation_m=elevation_m)

def earth_radius(self):
    return self.earth_R

def satellite_visiable_area(earth_radius, satellite_elevation):
    """Returns the visible area from a satellite in square meters.

    Formula is in the form is 2piR^2h/R+h where:
        R = earth radius
        h = satellite elevation from center of earth
    """
    return ((2 * pi * ( earth_radius ** 2 ) * 
            ( earth_radius + satellite_elevation)) /
            (earth_radius + earth_radius + satellite_elevation))


stations_url = 'http://celestrak.com/NORAD/elements/stations.txt'
satellites = api.load.tle(stations_url)
satellite = satellites['ISS (ZARYA)']
print(satellite)

ts = api.load.timescale()
t = ts.now()

geocentric = satellite.at(t)
geocentric.subpoint = subpoint.__get__(geocentric, Geocentric)
geocentric.earth_radius = earth_radius.__get__(geocentric, Geocentric)

geodetic_sub = geocentric.subpoint(3)

print('Geodetic latitude:', geodetic_sub.latitude)
print('Geodetic longitude:', geodetic_sub.longitude)
print('Geodetic elevation (m)', int(geodetic_sub.elevation.m))
print('Geodetic earth radius (m)', int(geocentric.earth_radius()))

geocentric_sub = geocentric.subpoint(0)
print('Geocentric latitude:', geocentric_sub.latitude)
print('Geocentric longitude:', geocentric_sub.longitude)
print('Geocentric elevation (m)', int(geocentric_sub.elevation.m))
print('Geocentric earth radius (m)', int(geocentric.earth_radius()))
print('Visible area (m^2)', satellite_visiable_area(geocentric.earth_radius(), 
                                                    geocentric_sub.elevation.m))

1
这太好了,非常感谢。目前,球形地球就足够了。我认为用扁球体来完成它的方法是将那个椭球体与在Skyfield中计算出来的地球坐标相配合,然后获取一个锥体来代表卫星的视图(假设是一个放大的光学系统),从卫星坐标开始,并将其与椭球体相交。但这是另一天的问题! - Gordon13
1
你在 reverse_terra() 函数中解决了什么错误?如果 Skyfield 中的函数有问题,我很感兴趣修复它 - 看起来你添加了一个新的返回参数,但并没有必要改变其他参数的计算方式? - Brandon Rhodes
1
@BrandonRhodes,“reverse_terra()”函数中的错误在于未初始化“C”。当地心位置的“iterations”为零时,由于此原因它会失败。添加“earth_R”返回参数是计算卫星在地球中心上方的缺失特征。 - rfkortekaas
1
我从未考虑过零次迭代,很有趣!该参数是为了让人们想要更多的迭代而不是更少。我会考虑添加一个针对零次迭代情况的测试,尽管将其作为单独的例程分离可能更加清晰,因为在这种情况下它返回一个不同的坐标系。也许该例程应该对迭代≤0返回错误。无论如何,感谢您的澄清! - Brandon Rhodes
@BrandonRhodes 不用谢。你说的不同坐标系是对的,但我认为你把它们搞混了。reverse_terra 返回三次迭代的是 大地测量 坐标,而零次迭代的是 地心测量 坐标。 - rfkortekaas
Skyfield使用术语“Geocentric”表示其原点为地心的x、y、z向量,这与USNO在源代码中使用该术语的结果相匹配,而Skyfield旨在匹配该结果。我已经尝试避免将该术语应用于纬度和经度,Skyfield始终将其视为大地测量=真实纬度和经度。如果您发现我无意中将此纬度和经度称为“地心”,请告诉我,我会清理这些参考资料。 - Brandon Rhodes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接