diff --git a/demo/nodejs/calendar/astronomy.ts b/demo/nodejs/calendar/astronomy.ts index 6ba14d9d..342688e2 100644 --- a/demo/nodejs/calendar/astronomy.ts +++ b/demo/nodejs/calendar/astronomy.ts @@ -4038,7 +4038,7 @@ export function HelioDistance(body: Body, date: FlexibleDateTime): number { export abstract class PositionFunction { /** * @brief Returns a relative position vector for a given time. - * @param {Body} time + * @param {AstroTime} time * The time at which to evaluate a relative position vector. */ abstract Position(time: AstroTime): Vector; diff --git a/demo/python/astronomy.py b/demo/python/astronomy.py index 918ff944..4a3db707 100644 --- a/demo/python/astronomy.py +++ b/demo/python/astronomy.py @@ -35,6 +35,7 @@ import math import datetime import enum import re +import abc def _cbrt(x): if x < 0.0: @@ -4425,6 +4426,161 @@ def HelioDistance(body, time): return HelioVector(body, time).Length() +class PositionFunction(abc.ABC): + """A function for which to solve a light-travel time problem. + + This abstract class defines the contract for wrapping a + position vector as a function of time. A class derived from + `PositionFunction` must define a `Position` method that + returns a position vector for a given time. + + The function #CorrectLightTravel solves a generalized + problem of deducing how far in the past light must have + left a target object to be seen by an observer at a + specified time. It is passed an instance of `PositionFunction` + that expresses a relative position vector function. + """ + def __init__(self): + pass + + @abc.abstractmethod + def Position(self, time): + """Returns a relative position vector for a given time. + + Parameters + ---------- + time : Time + The time at which to evaluate a relative position vector. + + Returns + ------- + Vector + """ + +def CorrectLightTravel(func, time): + """Solve for light travel time of a vector function. + + When observing a distant object, for example Jupiter as seen from Earth, + the amount of time it takes for light to travel from the object to the + observer can significantly affect the object's apparent position. + This function is a generic solver that figures out how long in the + past light must have left the observed object to reach the observer + at the specified observation time. It uses #PositionFunction + to express an arbitrary position vector as a function of time. + + This function repeatedly calls `func.Position`, passing a series of time + estimates in the past. Then `func.Position` must return a relative state vector between + the observer and the target. `CorrectLightTravel` keeps calling + `func.Position` with more and more refined estimates of the time light must have + left the target to arrive at the observer. + + For common use cases, it is simpler to use #BackdatePosition + for calculating the light travel time correction of one body observing another body. + + Parameters + ---------- + func : PositionFunction + An arbitrary position vector as a function of time. + + time : Time + The observation time for which to solve for light travel delay. + + Returns + ------- + Vector + The position vector at the solved backdated time. + The `t` field holds the time that light left the observed + body to arrive at the observer at the observation time. + """ + ltime = time + for _ in range(10): + pos = func.Position(ltime) + ltime2 = time.AddDays(-pos.Length() / C_AUDAY) + dt = abs(ltime2.tt - ltime.tt) + if dt < 1.0e-9: # 86.4 microseconds + return pos + ltime = ltime2 + raise NoConvergeError() + + +class _BodyPosition(PositionFunction): + def __init__(self, observerBody, targetBody, aberration, observerPos): + super().__init__() + self.observerBody = observerBody + self.targetBody = targetBody + self.aberration = aberration + self.observerPos = observerPos + + def Position(self, time): + if self.aberration: + # The following discussion is worded with the observer body being the Earth, + # which is often the case. However, the same reasoning applies to any observer body + # without loss of generality. + # + # To include aberration, make a good first-order approximation + # by backdating the Earth's position also. + # This is confusing, but it works for objects within the Solar System + # because the distance the Earth moves in that small amount of light + # travel time (a few minutes to a few hours) is well approximated + # by a line segment that substends the angle seen from the remote + # body viewing Earth. That angle is pretty close to the aberration + # angle of the moving Earth viewing the remote body. + # In other words, both of the following approximate the aberration angle: + # (transverse distance Earth moves) / (distance to body) + # (transverse speed of Earth) / (speed of light). + observerPos = HelioVector(self.observerBody, time) + else: + # No aberration, so use the pre-calculated initial position of + # the observer body that is already stored in this object. + observerPos = self.observerPos + # Subtract the bodies' heliocentric positions to obtain a relative position vector. + return HelioVector(self.targetBody, time) - observerPos + + +def BackdatePosition(time, observerBody, targetBody, aberration): + """Solve for light travel time correction of apparent position. + + When observing a distant object, for example Jupiter as seen from Earth, + the amount of time it takes for light to travel from the object to the + observer can significantly affect the object's apparent position. + + This function solves the light travel time correction for the apparent + relative position vector of a target body as seen by an observer body + at a given observation time. + + For a more generalized light travel correction solver, see #CorrectLightTravel. + + Parameters + ---------- + time : Time + The time of observation. + observerBody : Body + The body to be used as the observation location. + targetBody : Body + The body to be observed. + aberration : bool + `True` to correct for aberration, or `False` to leave uncorrected. + + Returns + ------- + Vector + The position vector at the solved backdated time. + Its `t` field holds the time that light left the observed + body to arrive at the observer at the observation time. + """ + if aberration: + # With aberration, `BackdatePosition` will calculate `observerPos` at different times. + # Therefore, do not waste time calculating it now. + # Provide a placeholder value. + observerPos = None + else: + # Without aberration, we need the observer body position at the observation time only. + # For efficiency, calculate it once and hold onto it, so `BodyPosition` can keep using it. + observerPos = HelioVector(observerBody, time) + func = _BodyPosition(observerBody, targetBody, aberration, observerPos) + return CorrectLightTravel(func, time) + + def GeoVector(body, time, aberration): """Calculates geocentric Cartesian coordinates of a body in the J2000 equatorial system. @@ -4435,7 +4591,7 @@ def GeoVector(body, time, aberration): If given an invalid value for `body`, this function will raise an exception. - Unlike #HelioVector, this function always corrects for light travel time. + Unlike #HelioVector, this function corrects for light travel time. This means the position of the body is "back-dated" by the amount of time it takes light to travel from that body to an observer on the Earth. @@ -4464,37 +4620,8 @@ def GeoVector(body, time, aberration): if body == Body.Earth: return Vector(0.0, 0.0, 0.0, time) - if not aberration: - # No aberration, so calculate Earth's position once, at the time of observation. - earth = _CalcEarth(time) - # Correct for light-travel time, to get position of body as seen from Earth's center. - ltime = time - for _ in range(10): - h = HelioVector(body, ltime) - if aberration: - # Include aberration, so make a good first-order approximation - # by backdating the Earth's position also. - # This is confusing, but it works for objects within the Solar System - # because the distance the Earth moves in that small amount of light - # travel time (a few minutes to a few hours) is well approximated - # by a line segment that substends the angle seen from the remote - # body viewing Earth. That angle is pretty close to the aberration - # angle of the moving Earth viewing the remote body. - # In other words, both of the following approximate the aberration angle: - # (transverse distance Earth moves) / (distance to body) - # (transverse speed of Earth) / (speed of light). - earth = _CalcEarth(ltime) - - geo = Vector(h.x-earth.x, h.y-earth.y, h.z-earth.z, time) - ltime2 = time.AddDays(-geo.Length() / C_AUDAY) - dt = abs(ltime2.tt - ltime.tt) - if dt < 1.0e-9: - return geo - - ltime = ltime2 - - raise Error('Light-travel time solver did not converge: dt={}'.format(dt)) + return BackdatePosition(time, Body.Earth, body, aberration) def _ExportState(terse, time): diff --git a/generate/template/astronomy.cs b/generate/template/astronomy.cs index c1b36115..881b34e2 100644 --- a/generate/template/astronomy.cs +++ b/generate/template/astronomy.cs @@ -4438,7 +4438,7 @@ $ASTRO_IAU_DATA() /// `Aberration.Corrected` to correct for aberration, or `Aberration.None` to leave uncorrected. /// /// The position vector at the solved backdated time. - /// The `t` field holds the time that light left the observed + /// Its `t` field holds the time that light left the observed /// body to arrive at the observer at the observation time. /// public static AstroVector BackdatePosition( diff --git a/generate/template/astronomy.py b/generate/template/astronomy.py index f96f2546..23d42183 100644 --- a/generate/template/astronomy.py +++ b/generate/template/astronomy.py @@ -35,6 +35,7 @@ import math import datetime import enum import re +import abc def _cbrt(x): if x < 0.0: @@ -2383,6 +2384,161 @@ def HelioDistance(body, time): return HelioVector(body, time).Length() +class PositionFunction(abc.ABC): + """A function for which to solve a light-travel time problem. + + This abstract class defines the contract for wrapping a + position vector as a function of time. A class derived from + `PositionFunction` must define a `Position` method that + returns a position vector for a given time. + + The function #CorrectLightTravel solves a generalized + problem of deducing how far in the past light must have + left a target object to be seen by an observer at a + specified time. It is passed an instance of `PositionFunction` + that expresses a relative position vector function. + """ + def __init__(self): + pass + + @abc.abstractmethod + def Position(self, time): + """Returns a relative position vector for a given time. + + Parameters + ---------- + time : Time + The time at which to evaluate a relative position vector. + + Returns + ------- + Vector + """ + +def CorrectLightTravel(func, time): + """Solve for light travel time of a vector function. + + When observing a distant object, for example Jupiter as seen from Earth, + the amount of time it takes for light to travel from the object to the + observer can significantly affect the object's apparent position. + This function is a generic solver that figures out how long in the + past light must have left the observed object to reach the observer + at the specified observation time. It uses #PositionFunction + to express an arbitrary position vector as a function of time. + + This function repeatedly calls `func.Position`, passing a series of time + estimates in the past. Then `func.Position` must return a relative state vector between + the observer and the target. `CorrectLightTravel` keeps calling + `func.Position` with more and more refined estimates of the time light must have + left the target to arrive at the observer. + + For common use cases, it is simpler to use #BackdatePosition + for calculating the light travel time correction of one body observing another body. + + Parameters + ---------- + func : PositionFunction + An arbitrary position vector as a function of time. + + time : Time + The observation time for which to solve for light travel delay. + + Returns + ------- + Vector + The position vector at the solved backdated time. + The `t` field holds the time that light left the observed + body to arrive at the observer at the observation time. + """ + ltime = time + for _ in range(10): + pos = func.Position(ltime) + ltime2 = time.AddDays(-pos.Length() / C_AUDAY) + dt = abs(ltime2.tt - ltime.tt) + if dt < 1.0e-9: # 86.4 microseconds + return pos + ltime = ltime2 + raise NoConvergeError() + + +class _BodyPosition(PositionFunction): + def __init__(self, observerBody, targetBody, aberration, observerPos): + super().__init__() + self.observerBody = observerBody + self.targetBody = targetBody + self.aberration = aberration + self.observerPos = observerPos + + def Position(self, time): + if self.aberration: + # The following discussion is worded with the observer body being the Earth, + # which is often the case. However, the same reasoning applies to any observer body + # without loss of generality. + # + # To include aberration, make a good first-order approximation + # by backdating the Earth's position also. + # This is confusing, but it works for objects within the Solar System + # because the distance the Earth moves in that small amount of light + # travel time (a few minutes to a few hours) is well approximated + # by a line segment that substends the angle seen from the remote + # body viewing Earth. That angle is pretty close to the aberration + # angle of the moving Earth viewing the remote body. + # In other words, both of the following approximate the aberration angle: + # (transverse distance Earth moves) / (distance to body) + # (transverse speed of Earth) / (speed of light). + observerPos = HelioVector(self.observerBody, time) + else: + # No aberration, so use the pre-calculated initial position of + # the observer body that is already stored in this object. + observerPos = self.observerPos + # Subtract the bodies' heliocentric positions to obtain a relative position vector. + return HelioVector(self.targetBody, time) - observerPos + + +def BackdatePosition(time, observerBody, targetBody, aberration): + """Solve for light travel time correction of apparent position. + + When observing a distant object, for example Jupiter as seen from Earth, + the amount of time it takes for light to travel from the object to the + observer can significantly affect the object's apparent position. + + This function solves the light travel time correction for the apparent + relative position vector of a target body as seen by an observer body + at a given observation time. + + For a more generalized light travel correction solver, see #CorrectLightTravel. + + Parameters + ---------- + time : Time + The time of observation. + observerBody : Body + The body to be used as the observation location. + targetBody : Body + The body to be observed. + aberration : bool + `True` to correct for aberration, or `False` to leave uncorrected. + + Returns + ------- + Vector + The position vector at the solved backdated time. + Its `t` field holds the time that light left the observed + body to arrive at the observer at the observation time. + """ + if aberration: + # With aberration, `BackdatePosition` will calculate `observerPos` at different times. + # Therefore, do not waste time calculating it now. + # Provide a placeholder value. + observerPos = None + else: + # Without aberration, we need the observer body position at the observation time only. + # For efficiency, calculate it once and hold onto it, so `BodyPosition` can keep using it. + observerPos = HelioVector(observerBody, time) + func = _BodyPosition(observerBody, targetBody, aberration, observerPos) + return CorrectLightTravel(func, time) + + def GeoVector(body, time, aberration): """Calculates geocentric Cartesian coordinates of a body in the J2000 equatorial system. @@ -2393,7 +2549,7 @@ def GeoVector(body, time, aberration): If given an invalid value for `body`, this function will raise an exception. - Unlike #HelioVector, this function always corrects for light travel time. + Unlike #HelioVector, this function corrects for light travel time. This means the position of the body is "back-dated" by the amount of time it takes light to travel from that body to an observer on the Earth. @@ -2422,37 +2578,8 @@ def GeoVector(body, time, aberration): if body == Body.Earth: return Vector(0.0, 0.0, 0.0, time) - if not aberration: - # No aberration, so calculate Earth's position once, at the time of observation. - earth = _CalcEarth(time) - # Correct for light-travel time, to get position of body as seen from Earth's center. - ltime = time - for _ in range(10): - h = HelioVector(body, ltime) - if aberration: - # Include aberration, so make a good first-order approximation - # by backdating the Earth's position also. - # This is confusing, but it works for objects within the Solar System - # because the distance the Earth moves in that small amount of light - # travel time (a few minutes to a few hours) is well approximated - # by a line segment that substends the angle seen from the remote - # body viewing Earth. That angle is pretty close to the aberration - # angle of the moving Earth viewing the remote body. - # In other words, both of the following approximate the aberration angle: - # (transverse distance Earth moves) / (distance to body) - # (transverse speed of Earth) / (speed of light). - earth = _CalcEarth(ltime) - - geo = Vector(h.x-earth.x, h.y-earth.y, h.z-earth.z, time) - ltime2 = time.AddDays(-geo.Length() / C_AUDAY) - dt = abs(ltime2.tt - ltime.tt) - if dt < 1.0e-9: - return geo - - ltime = ltime2 - - raise Error('Light-travel time solver did not converge: dt={}'.format(dt)) + return BackdatePosition(time, Body.Earth, body, aberration) def _ExportState(terse, time): diff --git a/generate/template/astronomy.ts b/generate/template/astronomy.ts index cf681fed..a4be7ae1 100644 --- a/generate/template/astronomy.ts +++ b/generate/template/astronomy.ts @@ -3032,7 +3032,7 @@ export function HelioDistance(body: Body, date: FlexibleDateTime): number { export abstract class PositionFunction { /** * @brief Returns a relative position vector for a given time. - * @param {Body} time + * @param {AstroTime} time * The time at which to evaluate a relative position vector. */ abstract Position(time: AstroTime): Vector; diff --git a/source/csharp/README.md b/source/csharp/README.md index e3fc98f2..b8eccca7 100644 --- a/source/csharp/README.md +++ b/source/csharp/README.md @@ -333,7 +333,7 @@ For a more generalized light travel correction solver, see [`Astronomy.CorrectLi | [`Body`](#Body) | `targetBody` | The body to be observed. | | [`Aberration`](#Aberration) | `aberration` | `Aberration.Corrected` to correct for aberration, or `Aberration.None` to leave uncorrected. | -**Returns:** The position vector at the solved backdated time. The `t` field holds the time that light left the observed body to arrive at the observer at the observation time. +**Returns:** The position vector at the solved backdated time. Its `t` field holds the time that light left the observed body to arrive at the observer at the observation time. ### Astronomy.BaryState(body, time) ⇒ [`StateVector`](#StateVector) diff --git a/source/csharp/astronomy.cs b/source/csharp/astronomy.cs index 3cd197ee..5976d52e 100644 --- a/source/csharp/astronomy.cs +++ b/source/csharp/astronomy.cs @@ -5650,7 +5650,7 @@ namespace CosineKitty /// `Aberration.Corrected` to correct for aberration, or `Aberration.None` to leave uncorrected. /// /// The position vector at the solved backdated time. - /// The `t` field holds the time that light left the observed + /// Its `t` field holds the time that light left the observed /// body to arrive at the observer at the observation time. /// public static AstroVector BackdatePosition( diff --git a/source/js/astronomy.d.ts b/source/js/astronomy.d.ts index c6a33e53..3cff64a9 100644 --- a/source/js/astronomy.d.ts +++ b/source/js/astronomy.d.ts @@ -965,7 +965,7 @@ export declare function HelioDistance(body: Body, date: FlexibleDateTime): numbe export declare abstract class PositionFunction { /** * @brief Returns a relative position vector for a given time. - * @param {Body} time + * @param {AstroTime} time * The time at which to evaluate a relative position vector. */ abstract Position(time: AstroTime): Vector; diff --git a/source/js/astronomy.ts b/source/js/astronomy.ts index 6ba14d9d..342688e2 100644 --- a/source/js/astronomy.ts +++ b/source/js/astronomy.ts @@ -4038,7 +4038,7 @@ export function HelioDistance(body: Body, date: FlexibleDateTime): number { export abstract class PositionFunction { /** * @brief Returns a relative position vector for a given time. - * @param {Body} time + * @param {AstroTime} time * The time at which to evaluate a relative position vector. */ abstract Position(time: AstroTime): Vector; diff --git a/source/python/README.md b/source/python/README.md index 0b72de96..4f18edea 100644 --- a/source/python/README.md +++ b/source/python/README.md @@ -763,6 +763,36 @@ to report information about the center of the Moon passing through the ecliptic --- + +### class PositionFunction + +**A function for which to solve a light-travel time problem.** + +This abstract class defines the contract for wrapping a +position vector as a function of time. A class derived from +`PositionFunction` must define a `Position` method that +returns a position vector for a given time. +The function [`CorrectLightTravel`](#CorrectLightTravel) solves a generalized +problem of deducing how far in the past light must have +left a target object to be seen by an observer at a +specified time. It is passed an instance of `PositionFunction` +that expresses a relative position vector function. + +#### member functions + + +### PositionFunction.Position(self, time) + +**Returns a relative position vector for a given time.** + +| Type | Parameter | Description | +| --- | --- | --- | +| [`Time`](#Time) | `time` | The time at which to evaluate a relative position vector. | + +**Returns**: [`Vector`](#Vector) + +--- + ### class RotationMatrix @@ -1218,6 +1248,33 @@ and the specified body as seen from the center of the Earth. --- + +### BackdatePosition(time, observerBody, targetBody, aberration) + +**Solve for light travel time correction of apparent position.** + +When observing a distant object, for example Jupiter as seen from Earth, +the amount of time it takes for light to travel from the object to the +observer can significantly affect the object's apparent position. +This function solves the light travel time correction for the apparent +relative position vector of a target body as seen by an observer body +at a given observation time. +For a more generalized light travel correction solver, see [`CorrectLightTravel`](#CorrectLightTravel). + +| Type | Parameter | Description | +| --- | --- | --- | +| [`Time`](#Time) | `time` | The time of observation. | +| [`Body`](#Body) | `observerBody` | The body to be used as the observation location. | +| [`Body`](#Body) | `targetBody` | The body to be observed. | +| `bool` | `aberration` | `True` to correct for aberration, or `False` to leave uncorrected. | + +**Returns**: [`Vector`](#Vector) +The position vector at the solved backdated time. +Its `t` field holds the time that light left the observed +body to arrive at the observer at the observation time. + +--- + ### BaryState(body, time) @@ -1296,6 +1353,39 @@ the converted B1875 (ra,dec) for that point. --- + +### CorrectLightTravel(func, time) + +**Solve for light travel time of a vector function.** + +When observing a distant object, for example Jupiter as seen from Earth, +the amount of time it takes for light to travel from the object to the +observer can significantly affect the object's apparent position. +This function is a generic solver that figures out how long in the +past light must have left the observed object to reach the observer +at the specified observation time. It uses [`PositionFunction`](#PositionFunction) +to express an arbitrary position vector as a function of time. +This function repeatedly calls `func.Position`, passing a series of time +estimates in the past. Then `func.Position` must return a relative state vector between +the observer and the target. `CorrectLightTravel` keeps calling +`func.Position` with more and more refined estimates of the time light must have +left the target to arrive at the observer. +For common use cases, it is simpler to use [`BackdatePosition`](#BackdatePosition) +for calculating the light travel time correction of one body observing another body. +time : Time + The observation time for which to solve for light travel delay. + +| Type | Parameter | Description | +| --- | --- | --- | +| [`PositionFunction`](#PositionFunction) | `func` | An arbitrary position vector as a function of time. | + +**Returns**: [`Vector`](#Vector) +The position vector at the solved backdated time. +The `t` field holds the time that light left the observed +body to arrive at the observer at the observation time. + +--- + ### DeltaT_EspenakMeeus(ut) @@ -1526,7 +1616,7 @@ using the center of the Earth as the origin. The result is expressed as a Carte vector in the J2000 equatorial system: the coordinates are based on the mean equator of the Earth at noon UTC on 1 January 2000. If given an invalid value for `body`, this function will raise an exception. -Unlike [`HelioVector`](#HelioVector), this function always corrects for light travel time. +Unlike [`HelioVector`](#HelioVector), this function corrects for light travel time. This means the position of the body is "back-dated" by the amount of time it takes light to travel from that body to an observer on the Earth. Also, the position can optionally be corrected for diff --git a/source/python/astronomy/astronomy.py b/source/python/astronomy/astronomy.py index 918ff944..4a3db707 100644 --- a/source/python/astronomy/astronomy.py +++ b/source/python/astronomy/astronomy.py @@ -35,6 +35,7 @@ import math import datetime import enum import re +import abc def _cbrt(x): if x < 0.0: @@ -4425,6 +4426,161 @@ def HelioDistance(body, time): return HelioVector(body, time).Length() +class PositionFunction(abc.ABC): + """A function for which to solve a light-travel time problem. + + This abstract class defines the contract for wrapping a + position vector as a function of time. A class derived from + `PositionFunction` must define a `Position` method that + returns a position vector for a given time. + + The function #CorrectLightTravel solves a generalized + problem of deducing how far in the past light must have + left a target object to be seen by an observer at a + specified time. It is passed an instance of `PositionFunction` + that expresses a relative position vector function. + """ + def __init__(self): + pass + + @abc.abstractmethod + def Position(self, time): + """Returns a relative position vector for a given time. + + Parameters + ---------- + time : Time + The time at which to evaluate a relative position vector. + + Returns + ------- + Vector + """ + +def CorrectLightTravel(func, time): + """Solve for light travel time of a vector function. + + When observing a distant object, for example Jupiter as seen from Earth, + the amount of time it takes for light to travel from the object to the + observer can significantly affect the object's apparent position. + This function is a generic solver that figures out how long in the + past light must have left the observed object to reach the observer + at the specified observation time. It uses #PositionFunction + to express an arbitrary position vector as a function of time. + + This function repeatedly calls `func.Position`, passing a series of time + estimates in the past. Then `func.Position` must return a relative state vector between + the observer and the target. `CorrectLightTravel` keeps calling + `func.Position` with more and more refined estimates of the time light must have + left the target to arrive at the observer. + + For common use cases, it is simpler to use #BackdatePosition + for calculating the light travel time correction of one body observing another body. + + Parameters + ---------- + func : PositionFunction + An arbitrary position vector as a function of time. + + time : Time + The observation time for which to solve for light travel delay. + + Returns + ------- + Vector + The position vector at the solved backdated time. + The `t` field holds the time that light left the observed + body to arrive at the observer at the observation time. + """ + ltime = time + for _ in range(10): + pos = func.Position(ltime) + ltime2 = time.AddDays(-pos.Length() / C_AUDAY) + dt = abs(ltime2.tt - ltime.tt) + if dt < 1.0e-9: # 86.4 microseconds + return pos + ltime = ltime2 + raise NoConvergeError() + + +class _BodyPosition(PositionFunction): + def __init__(self, observerBody, targetBody, aberration, observerPos): + super().__init__() + self.observerBody = observerBody + self.targetBody = targetBody + self.aberration = aberration + self.observerPos = observerPos + + def Position(self, time): + if self.aberration: + # The following discussion is worded with the observer body being the Earth, + # which is often the case. However, the same reasoning applies to any observer body + # without loss of generality. + # + # To include aberration, make a good first-order approximation + # by backdating the Earth's position also. + # This is confusing, but it works for objects within the Solar System + # because the distance the Earth moves in that small amount of light + # travel time (a few minutes to a few hours) is well approximated + # by a line segment that substends the angle seen from the remote + # body viewing Earth. That angle is pretty close to the aberration + # angle of the moving Earth viewing the remote body. + # In other words, both of the following approximate the aberration angle: + # (transverse distance Earth moves) / (distance to body) + # (transverse speed of Earth) / (speed of light). + observerPos = HelioVector(self.observerBody, time) + else: + # No aberration, so use the pre-calculated initial position of + # the observer body that is already stored in this object. + observerPos = self.observerPos + # Subtract the bodies' heliocentric positions to obtain a relative position vector. + return HelioVector(self.targetBody, time) - observerPos + + +def BackdatePosition(time, observerBody, targetBody, aberration): + """Solve for light travel time correction of apparent position. + + When observing a distant object, for example Jupiter as seen from Earth, + the amount of time it takes for light to travel from the object to the + observer can significantly affect the object's apparent position. + + This function solves the light travel time correction for the apparent + relative position vector of a target body as seen by an observer body + at a given observation time. + + For a more generalized light travel correction solver, see #CorrectLightTravel. + + Parameters + ---------- + time : Time + The time of observation. + observerBody : Body + The body to be used as the observation location. + targetBody : Body + The body to be observed. + aberration : bool + `True` to correct for aberration, or `False` to leave uncorrected. + + Returns + ------- + Vector + The position vector at the solved backdated time. + Its `t` field holds the time that light left the observed + body to arrive at the observer at the observation time. + """ + if aberration: + # With aberration, `BackdatePosition` will calculate `observerPos` at different times. + # Therefore, do not waste time calculating it now. + # Provide a placeholder value. + observerPos = None + else: + # Without aberration, we need the observer body position at the observation time only. + # For efficiency, calculate it once and hold onto it, so `BodyPosition` can keep using it. + observerPos = HelioVector(observerBody, time) + func = _BodyPosition(observerBody, targetBody, aberration, observerPos) + return CorrectLightTravel(func, time) + + def GeoVector(body, time, aberration): """Calculates geocentric Cartesian coordinates of a body in the J2000 equatorial system. @@ -4435,7 +4591,7 @@ def GeoVector(body, time, aberration): If given an invalid value for `body`, this function will raise an exception. - Unlike #HelioVector, this function always corrects for light travel time. + Unlike #HelioVector, this function corrects for light travel time. This means the position of the body is "back-dated" by the amount of time it takes light to travel from that body to an observer on the Earth. @@ -4464,37 +4620,8 @@ def GeoVector(body, time, aberration): if body == Body.Earth: return Vector(0.0, 0.0, 0.0, time) - if not aberration: - # No aberration, so calculate Earth's position once, at the time of observation. - earth = _CalcEarth(time) - # Correct for light-travel time, to get position of body as seen from Earth's center. - ltime = time - for _ in range(10): - h = HelioVector(body, ltime) - if aberration: - # Include aberration, so make a good first-order approximation - # by backdating the Earth's position also. - # This is confusing, but it works for objects within the Solar System - # because the distance the Earth moves in that small amount of light - # travel time (a few minutes to a few hours) is well approximated - # by a line segment that substends the angle seen from the remote - # body viewing Earth. That angle is pretty close to the aberration - # angle of the moving Earth viewing the remote body. - # In other words, both of the following approximate the aberration angle: - # (transverse distance Earth moves) / (distance to body) - # (transverse speed of Earth) / (speed of light). - earth = _CalcEarth(ltime) - - geo = Vector(h.x-earth.x, h.y-earth.y, h.z-earth.z, time) - ltime2 = time.AddDays(-geo.Length() / C_AUDAY) - dt = abs(ltime2.tt - ltime.tt) - if dt < 1.0e-9: - return geo - - ltime = ltime2 - - raise Error('Light-travel time solver did not converge: dt={}'.format(dt)) + return BackdatePosition(time, Body.Earth, body, aberration) def _ExportState(terse, time):