Skip to content

Commit 453e406

Browse files
committed
Add spherical harmonics scalar support
1 parent 5e9d043 commit 453e406

1 file changed

Lines changed: 83 additions & 31 deletions

File tree

src/main/kotlin/com/kylecorry/sol/science/geology/SphericalHarmonics.kt

Lines changed: 83 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,85 @@ internal class SphericalHarmonics(
4343
altitude: Distance = Distance.meters(0f),
4444
time: Instant = Instant.now(),
4545
): Vector3 {
46+
return calculate(
47+
coordinate,
48+
altitude,
49+
time
50+
) { yearsSinceBase, legendre, relativeRadiusPower, cosMLon, sinMLon, gdLatitudeRad, mGcLatitudeRad ->
51+
val maxN = gCoefficients.size
52+
val inverseCosLatitude = 1.0f / cos(mGcLatitudeRad)
53+
54+
var gcX = 0f
55+
var gcY = 0f
56+
var gcZ = 0f
57+
58+
for (n in 1 until maxN) {
59+
for (m in 0..n) {
60+
// Adjust for time
61+
val g = gCoefficients[n][m] + yearsSinceBase * (deltaGCoefficients?.get(n)?.get(m) ?: 0f)
62+
val h = hCoefficients[n][m] + yearsSinceBase * (deltaHCoefficients?.get(n)?.get(m) ?: 0f)
63+
64+
// Negative derivative with respect to latitude, divided by
65+
// radius. This looks like the negation of the version in the
66+
// NOAA Technical report because that report used
67+
// P_n^m(sin(theta)) and we use P_n^m(cos(90 - theta)), so the
68+
// derivative with respect to theta is negated.
69+
gcX += relativeRadiusPower[n + 2] * (g * cosMLon[m] + h * sinMLon[m]) * legendre.mPDeriv[n][m] * schmidtQuasiNormFactors[n][m]
70+
// Negative derivative with respect to longitude, divided by
71+
// radius.
72+
gcY += relativeRadiusPower[n + 2] * m * (g * sinMLon[m] - h * cosMLon[m]) * legendre.mP[n][m] * schmidtQuasiNormFactors[n][m] * inverseCosLatitude
73+
// Negative derivative with respect to radius.
74+
gcZ -= (n + 1) * relativeRadiusPower[n + 2] * (g * cosMLon[m] + h * sinMLon[m]) * legendre.mP[n][m] * schmidtQuasiNormFactors[n][m]
75+
}
76+
}
77+
78+
val latDiffRad = gdLatitudeRad - mGcLatitudeRad
79+
val x = (gcX * cos(latDiffRad) + gcZ * sin(latDiffRad))
80+
val y = gcY
81+
val z = (-gcX * sin(latDiffRad) + gcZ * cos(latDiffRad))
82+
Vector3(x, y, z)
83+
}
84+
}
85+
86+
fun getScalar(
87+
coordinate: Coordinate,
88+
altitude: Distance = Distance.meters(0f),
89+
time: Instant = Instant.now(),
90+
): Float {
91+
return calculate(
92+
coordinate,
93+
altitude,
94+
time
95+
) { yearsSinceBase, legendre, relativeRadiusPower, cosMLon, sinMLon, gdLatitudeRad, mGcLatitudeRad ->
96+
val maxN = gCoefficients.size
97+
var scalar = 0f
98+
99+
for (n in 1 until maxN) {
100+
for (m in 0..n) {
101+
// Adjust for time
102+
val g = gCoefficients[n][m] + yearsSinceBase * (deltaGCoefficients?.get(n)?.get(m) ?: 0f)
103+
val h = hCoefficients[n][m] + yearsSinceBase * (deltaHCoefficients?.get(n)?.get(m) ?: 0f)
104+
105+
scalar += relativeRadiusPower[n + 2] * (g * cosMLon[m] + h * sinMLon[m]) * legendre.mP[n][m] * schmidtQuasiNormFactors[n][m]
106+
}
107+
}
108+
109+
scalar
110+
}
111+
}
112+
113+
private inline fun <T> calculate(
114+
coordinate: Coordinate,
115+
altitude: Distance,
116+
time: Instant,
117+
crossinline calculator: (yearsSinceBase: Float, legendre: LegendreTable, relativeRadiusPower: FloatArray, cosMLon: FloatArray, sinMLon: FloatArray, gdLatitudeRad: Float, mGcLatitudeRad: Float) -> T
118+
): T {
46119
val timeMillis = time.toEpochMilli()
47120

48121
// Workaround to handle poles
49122
val gdLongitudeDeg = coordinate.longitude.toFloat()
50123
val gdLatitudeDeg = coordinate.latitude.toFloat().coerceIn(-90f + 1e-5f, 90f - 1e-5f)
124+
val gdLatitudeRad = gdLatitudeDeg.toRadians()
51125
val altitudeMeters = altitude.meters().distance
52126
val geocentric = computeGeocentricCoordinates(gdLatitudeDeg, gdLongitudeDeg, altitudeMeters)
53127
val mGcLongitudeRad = geocentric.x
@@ -77,38 +151,16 @@ internal class SphericalHarmonics(
77151
cosMLon[m] = cosMLon[m - x] * cosMLon[x] - sinMLon[m - x] * sinMLon[x]
78152
}
79153

80-
val inverseCosLatitude = 1.0f / cos(mGcLatitudeRad)
81154
val yearsSinceBase = (timeMillis - (baseTimeMillis ?: timeMillis)) / (365f * 24 * 60 * 60 * 1000)
82-
83-
var gcX = 0f
84-
var gcY = 0f
85-
var gcZ = 0f
86-
87-
for (n in 1 until maxN) {
88-
for (m in 0..n) {
89-
// Adjust for time
90-
val g = gCoefficients[n][m] + yearsSinceBase * (deltaGCoefficients?.get(n)?.get(m) ?: 0f)
91-
val h = hCoefficients[n][m] + yearsSinceBase * (deltaHCoefficients?.get(n)?.get(m) ?: 0f)
92-
93-
// Negative derivative with respect to latitude, divided by
94-
// radius. This looks like the negation of the version in the
95-
// NOAA Technical report because that report used
96-
// P_n^m(sin(theta)) and we use P_n^m(cos(90 - theta)), so the
97-
// derivative with respect to theta is negated.
98-
gcX += relativeRadiusPower[n + 2] * (g * cosMLon[m] + h * sinMLon[m]) * legendre.mPDeriv[n][m] * schmidtQuasiNormFactors[n][m]
99-
// Negative derivative with respect to longitude, divided by
100-
// radius.
101-
gcY += relativeRadiusPower[n + 2] * m * (g * sinMLon[m] - h * cosMLon[m]) * legendre.mP[n][m] * schmidtQuasiNormFactors[n][m] * inverseCosLatitude
102-
// Negative derivative with respect to radius.
103-
gcZ -= (n + 1) * relativeRadiusPower[n + 2] * (g * cosMLon[m] + h * sinMLon[m]) * legendre.mP[n][m] * schmidtQuasiNormFactors[n][m]
104-
}
105-
}
106-
107-
val latDiffRad = gdLatitudeDeg.toRadians() - mGcLatitudeRad
108-
val x = (gcX * cos(latDiffRad) + gcZ * sin(latDiffRad))
109-
val y = gcY
110-
val z = (-gcX * sin(latDiffRad) + gcZ * cos(latDiffRad))
111-
return Vector3(x, y, z)
155+
return calculator(
156+
yearsSinceBase,
157+
legendre,
158+
relativeRadiusPower,
159+
cosMLon,
160+
sinMLon,
161+
gdLatitudeRad,
162+
mGcLatitudeRad
163+
)
112164
}
113165

114166

0 commit comments

Comments
 (0)