From b1ebd8d0d7b1eca2a79b4cd0f4e3edbeefba27ba Mon Sep 17 00:00:00 2001 From: Don Cross Date: Sat, 9 Apr 2022 19:41:15 -0400 Subject: [PATCH] Kotlin: convert AstroVector to Observer. Added `AstroVector.toObserver`, which converts a geocentric equatorial vector (EQJ or EQD) into a geographic location in an `Observer` object. --- generate/template/astronomy.kt | 28 +++++++++++++- source/kotlin/doc/-astro-vector/index.md | 1 + .../kotlin/doc/-astro-vector/to-observer.md | 22 +++++++++++ source/kotlin/doc/-observer/to-vector.md | 2 +- .../github/cosinekitty/astronomy/astronomy.kt | 28 +++++++++++++- .../io/github/cosinekitty/astronomy/Tests.kt | 37 +++++++++++++++++++ 6 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 source/kotlin/doc/-astro-vector/to-observer.md diff --git a/generate/template/astronomy.kt b/generate/template/astronomy.kt index 60215a17..71b40735 100644 --- a/generate/template/astronomy.kt +++ b/generate/template/astronomy.kt @@ -699,13 +699,39 @@ data class AstroVector( sphere.dist ) } + + /** + * Calculates the geographic location corresponding to a geocentric equatorial vector. + * + * This is the inverse function of [Observer.toVector]. + * Given an equatorial vector from the center of the Earth to + * an observer on or near the Earth's surface, this function returns + * the geographic latitude, longitude, and elevation for that observer. + * + * @param equator + * Selects the date of the Earth's equator in which this vector is expressed. + * The caller may select [EquatorEpoch.J2000] to use the orientation of the Earth's equator + * at noon UTC on January 1, 2000, in which case this function corrects for precession + * and nutation of the Earth as it was at the moment specified by the time `this.t`. + * Or the caller may select [EquatorEpoch.OfDate] to use the Earth's equator at `this.t` + * as the orientation. + * + * @return The geographic coordinates corresponding to the vector. + */ + fun toObserver(equator: EquatorEpoch): Observer { + val vector = when(equator) { + EquatorEpoch.J2000 -> gyration(this, PrecessDirection.From2000) + EquatorEpoch.OfDate -> this + } + return inverseTerra(vector) + } } /** * Multiply a scalar by a vector, yielding another vector. */ operator fun Double.times(vec: AstroVector) = - AstroVector(this*vec.x, this*vec.y, this*vec.z, vec.t) + AstroVector(this*vec.x, this*vec.y, this*vec.z, vec.t) /** diff --git a/source/kotlin/doc/-astro-vector/index.md b/source/kotlin/doc/-astro-vector/index.md index b70485ad..d98a428b 100644 --- a/source/kotlin/doc/-astro-vector/index.md +++ b/source/kotlin/doc/-astro-vector/index.md @@ -25,6 +25,7 @@ A 3D Cartesian vector whose components are expressed in Astronomical Units (AU). | [plus](plus.md) | [jvm]
operator fun [plus](plus.md)(other: [AstroVector](index.md)): [AstroVector](index.md)
Adds two vectors. Both operands must have identical times. | | [toEquatorial](to-equatorial.md) | [jvm]
fun [toEquatorial](to-equatorial.md)(): [Equatorial](../-equatorial/index.md)
Given an equatorial vector, calculates equatorial angular coordinates. | | [toHorizontal](to-horizontal.md) | [jvm]
fun [toHorizontal](to-horizontal.md)(refraction: [Refraction](../-refraction/index.md)): [Spherical](../-spherical/index.md)
Converts Cartesian coordinates to horizontal coordinates. | +| [toObserver](to-observer.md) | [jvm]
fun [toObserver](to-observer.md)(equator: [EquatorEpoch](../-equator-epoch/index.md)): [Observer](../-observer/index.md)
Calculates the geographic location corresponding to a geocentric equatorial vector. | | [toSpherical](to-spherical.md) | [jvm]
fun [toSpherical](to-spherical.md)(): [Spherical](../-spherical/index.md)
Converts Cartesian coordinates to spherical coordinates. | | [unaryMinus](unary-minus.md) | [jvm]
operator fun [unaryMinus](unary-minus.md)(): [AstroVector](index.md)
Negates a vector; the same as multiplying the vector by the scalar -1. | diff --git a/source/kotlin/doc/-astro-vector/to-observer.md b/source/kotlin/doc/-astro-vector/to-observer.md new file mode 100644 index 00000000..07bc9a23 --- /dev/null +++ b/source/kotlin/doc/-astro-vector/to-observer.md @@ -0,0 +1,22 @@ +//[astronomy](../../../index.md)/[io.github.cosinekitty.astronomy](../index.md)/[AstroVector](index.md)/[toObserver](to-observer.md) + +# toObserver + +[jvm]\ +fun [toObserver](to-observer.md)(equator: [EquatorEpoch](../-equator-epoch/index.md)): [Observer](../-observer/index.md) + +Calculates the geographic location corresponding to a geocentric equatorial vector. + +This is the inverse function of [Observer.toVector](../-observer/to-vector.md). Given an equatorial vector from the center of the Earth to an observer on or near the Earth's surface, this function returns the geographic latitude, longitude, and elevation for that observer. + +#### Return + +The geographic coordinates corresponding to the vector. + +## Parameters + +jvm + +| | | +|---|---| +| equator | Selects the date of the Earth's equator in which this vector is expressed. The caller may select [EquatorEpoch.J2000] to use the orientation of the Earth's equator at noon UTC on January 1, 2000, in which case this function corrects for precession and nutation of the Earth as it was at the moment specified by the time `this.t`. Or the caller may select [EquatorEpoch.OfDate] to use the Earth's equator at `this.t` as the orientation. | diff --git a/source/kotlin/doc/-observer/to-vector.md b/source/kotlin/doc/-observer/to-vector.md index 4649af41..42dbaa8c 100644 --- a/source/kotlin/doc/-observer/to-vector.md +++ b/source/kotlin/doc/-observer/to-vector.md @@ -13,7 +13,7 @@ The caller may pass a value in equator to select either [EquatorEpoch.J2000](../ The returned vector has components expressed in astronomical units (AU). To convert to kilometers, multiply the vector values by the scalar value [KM_PER_AU](../-k-m_-p-e-r_-a-u.md). -The inverse of this function is also available: AstroVector.toObserver. +The inverse of this function is also available: [AstroVector.toObserver](../-astro-vector/to-observer.md). #### Return diff --git a/source/kotlin/src/main/kotlin/io/github/cosinekitty/astronomy/astronomy.kt b/source/kotlin/src/main/kotlin/io/github/cosinekitty/astronomy/astronomy.kt index 37e7f14b..3cac11de 100644 --- a/source/kotlin/src/main/kotlin/io/github/cosinekitty/astronomy/astronomy.kt +++ b/source/kotlin/src/main/kotlin/io/github/cosinekitty/astronomy/astronomy.kt @@ -699,13 +699,39 @@ data class AstroVector( sphere.dist ) } + + /** + * Calculates the geographic location corresponding to a geocentric equatorial vector. + * + * This is the inverse function of [Observer.toVector]. + * Given an equatorial vector from the center of the Earth to + * an observer on or near the Earth's surface, this function returns + * the geographic latitude, longitude, and elevation for that observer. + * + * @param equator + * Selects the date of the Earth's equator in which this vector is expressed. + * The caller may select [EquatorEpoch.J2000] to use the orientation of the Earth's equator + * at noon UTC on January 1, 2000, in which case this function corrects for precession + * and nutation of the Earth as it was at the moment specified by the time `this.t`. + * Or the caller may select [EquatorEpoch.OfDate] to use the Earth's equator at `this.t` + * as the orientation. + * + * @return The geographic coordinates corresponding to the vector. + */ + fun toObserver(equator: EquatorEpoch): Observer { + val vector = when(equator) { + EquatorEpoch.J2000 -> gyration(this, PrecessDirection.From2000) + EquatorEpoch.OfDate -> this + } + return inverseTerra(vector) + } } /** * Multiply a scalar by a vector, yielding another vector. */ operator fun Double.times(vec: AstroVector) = - AstroVector(this*vec.x, this*vec.y, this*vec.z, vec.t) + AstroVector(this*vec.x, this*vec.y, this*vec.z, vec.t) /** diff --git a/source/kotlin/src/test/kotlin/io/github/cosinekitty/astronomy/Tests.kt b/source/kotlin/src/test/kotlin/io/github/cosinekitty/astronomy/Tests.kt index 7b15f49c..8d6e9863 100644 --- a/source/kotlin/src/test/kotlin/io/github/cosinekitty/astronomy/Tests.kt +++ b/source/kotlin/src/test/kotlin/io/github/cosinekitty/astronomy/Tests.kt @@ -1074,4 +1074,41 @@ class Tests { } //---------------------------------------------------------------------------------------- + + private fun VerifyGeoid(observer: Observer, time: AstroTime, equator: EquatorEpoch) { + val degreeTolerance = 1.0e-12 + val meterTolerance = 1.0e-8 + val vector = observer.toVector(time, equator) + val check = vector.toObserver(equator) + val latDiff = abs(check.latitude - observer.latitude) + val lonDiff = dcos(observer.latitude) * abs(check.longitude - observer.longitude) + val heightDiff = abs(check.height - observer.height) + assertTrue(latDiff < degreeTolerance, "excessive latitude error = $latDiff") + assertTrue(lonDiff < degreeTolerance, "excessive longitude error = $lonDiff") + assertTrue(heightDiff < meterTolerance, "excessive height error = $heightDiff") + } + + @Test + fun `Verify inverse geoid calculations`() { + // Calculate position vectors for a variety of geographic coordinates. + // Verify that we can convert each observer back into a matching vector. + val time = AstroTime(8134.392799058808) // 2022-04-09T21:25:37.839Z + var latitude = -85.0 + while (latitude <= +85.0) { + var longitude = -175.0 + while (longitude <= +175.0) { + var height = -5000.0 + while (height <= +5000.0) { + val observer = Observer(latitude, longitude, height) + VerifyGeoid(observer, time, EquatorEpoch.OfDate) + VerifyGeoid(observer, time, EquatorEpoch.J2000) + height += 1000.0 + } + longitude += 5.0 + } + latitude += 5.0 + } + } + + //---------------------------------------------------------------------------------------- }