@@ -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