Umbau auf server
This commit is contained in:
@@ -0,0 +1,338 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Classes that represent a ‘topocentric’ position on the Earth’s surface."""
|
||||
|
||||
from numpy import arctan2, array, array2string, cos, exp, sin, sqrt
|
||||
from .constants import ANGVEL, DAY_S, RAD2DEG, T0, pi, tau
|
||||
from .earthlib import refract
|
||||
from .framelib import itrs
|
||||
from .functions import (
|
||||
_T, angular_velocity_matrix, mxm, mxv, rot_y, rot_z,
|
||||
)
|
||||
from .descriptorlib import reify
|
||||
from .units import Angle, Distance, _ltude
|
||||
from .vectorlib import VectorFunction
|
||||
|
||||
_EARTH_ANGULAR_VELOCITY_VECTOR = array((0, 0, DAY_S * ANGVEL))
|
||||
|
||||
_lat_options = {'precision': 4, 'floatmode': 'fixed', 'sign': '+',
|
||||
'threshold': 5, 'edgeitems': 2}
|
||||
_lon_options = {'precision': 4, 'floatmode': 'fixed',
|
||||
'threshold': 5, 'edgeitems': 2}
|
||||
_elev_options = {'precision': 1, 'floatmode': 'fixed',
|
||||
'threshold': 5, 'edgeitems': 2}
|
||||
|
||||
class ITRSPosition(VectorFunction):
|
||||
"""An |xyz| position in the Earth-centered Earth-fixed (ECEF) ITRS frame."""
|
||||
|
||||
center = 399
|
||||
|
||||
def __init__(self, itrs_xyz):
|
||||
self.itrs_xyz = itrs_xyz
|
||||
x, y, z = itrs_xyz.au
|
||||
self._velocity_au_per_d = ANGVEL * DAY_S * array((-y, x, 0.0 * z))
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
# When used as a vector function, this Earth geographic location
|
||||
# computes positions from the Earth's center to itself. (This
|
||||
# is a property, rather than an attribute, to avoid a circular
|
||||
# reference that delays garbage collection.)
|
||||
return self
|
||||
|
||||
def _at(self, t):
|
||||
"""Compute GCRS position and velocity at time `t`."""
|
||||
r = self.itrs_xyz.au
|
||||
v = self._velocity_au_per_d
|
||||
|
||||
RT = _T(itrs.rotation_at(t))
|
||||
r = mxv(RT, r)
|
||||
v = mxv(RT, v)
|
||||
return r, v, None, None
|
||||
|
||||
class GeographicPosition(ITRSPosition):
|
||||
"""A latitude-longitude-elevation position on Earth.
|
||||
|
||||
Each instance of this class holds an |xyz| vector for a geographic
|
||||
position on, above, or below the Earth’s surface, in the ITRS
|
||||
reference frame: the international standard for an Earth-centered
|
||||
Earth-fixed (ECEF) reference frame. Instead of instantiating this
|
||||
class directly, Skyfield users usually give a reference geoid the
|
||||
longitude and latitude they are interested in::
|
||||
|
||||
from skyfield.api import wgs84
|
||||
topos = wgs84.latlon(37.3414, -121.6429)
|
||||
|
||||
Once a geographic position has been created, here are its attributes
|
||||
and methods:
|
||||
|
||||
"""
|
||||
vector_name = 'Geodetic'
|
||||
|
||||
def __init__(self, model, latitude, longitude, elevation, itrs_xyz):
|
||||
super(GeographicPosition, self).__init__(itrs_xyz)
|
||||
self.model = model
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.elevation = elevation
|
||||
self._R_lat = _R_lat = rot_y(latitude.radians)[::-1]
|
||||
self._R_latlon = mxm(_R_lat, rot_z(-longitude.radians))
|
||||
|
||||
@reify # not @property, so users have the option of overwriting it
|
||||
def target_name(self):
|
||||
return '{0} latitude {1} N longitude {2} E elevation {3} m'.format(
|
||||
self.model.name,
|
||||
array2string(self.latitude.degrees, **_lat_options),
|
||||
array2string(self.longitude.degrees, **_lon_options),
|
||||
array2string(self.elevation.m, **_elev_options))
|
||||
|
||||
def lst_hours_at(self, t):
|
||||
"""Return the Local Apparent Sidereal Time, in hours, at time ``t``.
|
||||
|
||||
This location’s Local Apparent Sidereal Time (LAST) is the right
|
||||
ascension of the zenith at the time ``t``, as measured against
|
||||
the “true” Earth equator and equinox (rather than the fictional
|
||||
“mean” equator and equinox, which ignore the Earth’s nutation).
|
||||
|
||||
"""
|
||||
sprime = -47.0e-6 * (t.whole - T0 + t.tdb_fraction) / 36525.0
|
||||
return (t.gast + self.longitude.hours + sprime / 54000.0) % 24.0
|
||||
|
||||
def refract(self, altitude_degrees, temperature_C, pressure_mbar):
|
||||
"""Predict how the atmosphere will refract a position.
|
||||
|
||||
Given a body that is standing ``altitude_degrees`` above the
|
||||
true horizon, return an ``Angle`` predicting its apparent
|
||||
altitude given the supplied temperature and pressure, either of
|
||||
which can be the string ``'standard'`` to use 10°C and a
|
||||
pressure of 1010 mbar adjusted for the elevation of this
|
||||
geographic location.
|
||||
|
||||
"""
|
||||
if temperature_C == 'standard':
|
||||
temperature_C = 10.0
|
||||
if pressure_mbar == 'standard':
|
||||
pressure_mbar = 1010.0 * exp(-self.elevation.m / 9.1e3)
|
||||
alt = refract(altitude_degrees, temperature_C, pressure_mbar)
|
||||
return Angle(degrees=alt)
|
||||
|
||||
def rotation_at(self, t):
|
||||
"""Compute rotation from GCRS to this location’s altazimuth system."""
|
||||
return mxm(self._R_latlon, itrs.rotation_at(t))
|
||||
|
||||
def _dRdt_times_RT_at(self, t):
|
||||
# TODO: taking the derivative of the instantaneous angular
|
||||
# velocity would provide a more accurate transform.
|
||||
R = mxv(self._R_lat, _EARTH_ANGULAR_VELOCITY_VECTOR)
|
||||
return angular_velocity_matrix(R)
|
||||
|
||||
class Geoid(object):
|
||||
"""An Earth ellipsoid: maps latitudes and longitudes to |xyz| positions.
|
||||
|
||||
Instead of creating their own geoid object, most Skyfield users
|
||||
simply use the `wgs84` object that comes built-in.
|
||||
|
||||
The math for turning a position into latitude and longitude is based
|
||||
on Dr. T.S. Kelso's quite helpful article `Orbital Coordinate
|
||||
Systems, Part III <https://www.celestrak.org/columns/v02n03/>`_.
|
||||
|
||||
"""
|
||||
def __init__(self, name, radius_m, inverse_flattening):
|
||||
self.name = name
|
||||
self.radius = Distance(m=radius_m)
|
||||
self.inverse_flattening = inverse_flattening
|
||||
omf = (inverse_flattening - 1.0) / inverse_flattening
|
||||
self._one_minus_flattening_squared = omf * omf
|
||||
f = 1.0 / inverse_flattening
|
||||
self._e2 = 2.0*f - f*f
|
||||
|
||||
@reify
|
||||
def polar_radius(self):
|
||||
"""The Earth’s polar radius, as a :class:`~skyfield.units.Distance`."""
|
||||
return Distance(self.radius.au * (1.0 - 1.0 / self.inverse_flattening))
|
||||
|
||||
def latlon(self, latitude_degrees, longitude_degrees, elevation_m=0.0,
|
||||
cls=GeographicPosition):
|
||||
"""Return a `GeographicPosition` for a given latitude and longitude.
|
||||
|
||||
The longitude and latitude should both be specified in degrees.
|
||||
If no elevation in meters is supplied, the returned position
|
||||
will lie on the surface of the ellipsoid. Longitude is positive
|
||||
towards the east, so supply a negative number for west::
|
||||
|
||||
from skyfield.api import wgs84
|
||||
observatory = wgs84.latlon(37.3414, -121.6429) # 121.6° West
|
||||
|
||||
You can avoid remembering which directions are negative by using
|
||||
Skyfield’s compass direction constants, which have the values +1
|
||||
and −1::
|
||||
|
||||
from skyfield.api import N, S, E, W
|
||||
observatory = wgs84.latlon(37.3414 * N, 121.6429 * W)
|
||||
|
||||
"""
|
||||
latitude = Angle(degrees=latitude_degrees)
|
||||
longitude = Angle(degrees=longitude_degrees)
|
||||
elevation = Distance(m=elevation_m)
|
||||
|
||||
lat = latitude.radians
|
||||
lon = longitude.radians
|
||||
radius_au = self.radius.au
|
||||
elevation_au = elevation.au
|
||||
|
||||
sinphi = sin(lat)
|
||||
cosphi = cos(lat)
|
||||
omf2 = self._one_minus_flattening_squared
|
||||
c = 1.0 / sqrt(cosphi * cosphi + sinphi * sinphi * omf2)
|
||||
s = omf2 * c
|
||||
|
||||
# At equator: 6378 km, the Earth's actual radius at the equator.
|
||||
# At the pole: 6399 km, the Earth's radius of curvature at the pole.
|
||||
radius_xy = radius_au * c
|
||||
xy = (radius_xy + elevation_au) * cosphi
|
||||
x = xy * cos(lon)
|
||||
y = xy * sin(lon)
|
||||
|
||||
# At equator: 6335 km, the Earth's radius of curvature at the equator.
|
||||
# At the pole: 6357 km, the Earth's actual radius at the pole.
|
||||
radius_z = radius_au * s
|
||||
z = (radius_z + elevation_au) * sinphi
|
||||
|
||||
r = array((x, y, z))
|
||||
return cls(self, latitude, longitude, elevation, Distance(r))
|
||||
|
||||
def latlon_of(self, position):
|
||||
"""Return the latitude and longitude of a ``position``.
|
||||
|
||||
The position’s ``.center`` must be 399, the center of the Earth.
|
||||
Geodetic latitude and longitude are returned as a pair of
|
||||
:class:`~skyfield.units.Angle` objects.
|
||||
|
||||
"""
|
||||
xyz_au, x, y, R, aC, hyp, lat = self._compute_latitude(position)
|
||||
lon = (arctan2(y, x) - pi) % tau - pi
|
||||
return Angle(radians=lat), Angle(radians=lon)
|
||||
|
||||
def height_of(self, position):
|
||||
"""Return the height above the Earth’s ellipsoid of a ``position``.
|
||||
|
||||
The position’s ``.center`` must be 399, the center of the Earth.
|
||||
A :class:`~skyfield.units.Distance` is returned giving the
|
||||
position’s geodetic height above the Earth’s surface.
|
||||
|
||||
"""
|
||||
xyz_au, x, y, R, aC, hyp, lat = self._compute_latitude(position)
|
||||
height_au = sqrt(hyp * hyp + R * R) - aC
|
||||
return Distance(height_au)
|
||||
|
||||
def geographic_position_of(self, position):
|
||||
"""Return the `GeographicPosition` of a ``position``.
|
||||
|
||||
The position’s ``.center`` must be 399, the center of the Earth.
|
||||
A `GeographicPosition` is returned giving the position’s
|
||||
geodetic ``latitude`` and ``longitude``, and an ``elevation``
|
||||
above or below the surface of the ellipsoid.
|
||||
|
||||
"""
|
||||
xyz_au, x, y, R, aC, hyp, lat = self._compute_latitude(position)
|
||||
lon = (arctan2(y, x) - pi) % tau - pi
|
||||
height_au = sqrt(hyp * hyp + R * R) - aC
|
||||
return GeographicPosition(
|
||||
latitude=Angle(radians=lat),
|
||||
longitude=Angle(radians=lon),
|
||||
elevation=Distance(height_au),
|
||||
itrs_xyz=Distance(xyz_au),
|
||||
model=self,
|
||||
)
|
||||
|
||||
def subpoint_of(self, position):
|
||||
"""Return the point on the ellipsoid directly below a ``position``.
|
||||
|
||||
The position’s ``.center`` must be 399, the center of the Earth.
|
||||
Returns a `GeographicPosition` giving the geodetic ``latitude``
|
||||
and ``longitude`` that lie directly below the input position,
|
||||
and an ``elevation`` above the ellipsoid of zero.
|
||||
|
||||
"""
|
||||
xyz_au, x, y, R, aC, hyp, lat = self._compute_latitude(position)
|
||||
lon = (arctan2(y, x) - pi) % tau - pi
|
||||
return self.latlon(lat * RAD2DEG, lon * RAD2DEG)
|
||||
|
||||
def _compute_latitude(self, position):
|
||||
if position.center != 399:
|
||||
raise ValueError(
|
||||
'you can only calculate a geographic position from a'
|
||||
' position which is geocentric (center=399), but this'
|
||||
' position has a center of {0}'.format(position.center)
|
||||
)
|
||||
xyz_au = position.frame_xyz(itrs).au
|
||||
x, y, z = xyz_au
|
||||
a = self.radius.au
|
||||
e2 = self._e2
|
||||
R = sqrt(x*x + y*y)
|
||||
lat = arctan2(z, R)
|
||||
for iteration in 0,1,2:
|
||||
sin_lat = sin(lat)
|
||||
e2_sin_lat = e2 * sin_lat
|
||||
# At 0°, aC = 6378 km, Earth's actual radius at the equator.
|
||||
# At 90°, aC = 6399 km, Earth's radius of curvature at the pole.
|
||||
aC = a / sqrt(1.0 - e2_sin_lat * sin_lat)
|
||||
hyp = z + aC * e2_sin_lat
|
||||
lat = arctan2(hyp, R)
|
||||
return xyz_au, x, y, R, aC, hyp, lat
|
||||
|
||||
subpoint = geographic_position_of # deprecated method name
|
||||
|
||||
wgs84 = Geoid('WGS84', 6378137.0, 298.257223563)
|
||||
iers2010 = Geoid('IERS2010', 6378136.6, 298.25642)
|
||||
|
||||
wgs84.__doc__ = """World Geodetic System 1984 `Geoid`.
|
||||
|
||||
This is the standard geoid used by the GPS system,
|
||||
and is likely the standard that’s intended
|
||||
if you are supplied a latitude and longitude
|
||||
that don’t specify an alternative geoid.
|
||||
|
||||
"""
|
||||
iers2010.__doc__ = 'International Earth Rotation Service 2010 `Geoid`.'
|
||||
|
||||
# Compatibility with old versions of Skyfield:
|
||||
|
||||
class Topos(GeographicPosition):
|
||||
"""Deprecated: use ``wgs84.latlon()`` or ``iers2010.latlon()`` instead."""
|
||||
|
||||
def __init__(self, latitude=None, longitude=None, latitude_degrees=None,
|
||||
longitude_degrees=None, elevation_m=0.0, x=0.0, y=0.0):
|
||||
|
||||
if latitude_degrees is not None:
|
||||
pass
|
||||
elif isinstance(latitude, Angle):
|
||||
latitude_degrees = latitude.degrees
|
||||
elif isinstance(latitude, (str, float, tuple)):
|
||||
latitude_degrees = _ltude(latitude, 'latitude', 'N', 'S')
|
||||
else:
|
||||
raise TypeError('please provide either latitude_degrees=<float>'
|
||||
' or latitude=<skyfield.units.Angle object>'
|
||||
' with north being positive')
|
||||
|
||||
if longitude_degrees is not None:
|
||||
pass
|
||||
elif isinstance(longitude, Angle):
|
||||
longitude_degrees = longitude.degrees
|
||||
elif isinstance(longitude, (str, float, tuple)):
|
||||
longitude_degrees = _ltude(longitude, 'longitude', 'E', 'W')
|
||||
else:
|
||||
raise TypeError('please provide either longitude_degrees=<float>'
|
||||
' or longitude=<skyfield.units.Angle object>'
|
||||
' with east being positive')
|
||||
|
||||
# Sneaky: the model thinks it's creating an object when really
|
||||
# it's just calling our superclass __init__() for us. Alas, the
|
||||
# crimes committed to avoid duplicating code! (This is actually
|
||||
# quite clean compared to the other alternatives I tried.)
|
||||
iers2010.latlon(latitude_degrees, longitude_degrees, elevation_m,
|
||||
super(Topos, self).__init__)
|
||||
|
||||
self.R_lat = self._R_lat # On this old class, it was public.
|
||||
|
||||
def itrf_xyz(self):
|
||||
return self.itrs_xyz
|
||||
Reference in New Issue
Block a user