1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
package org.terst.nav.temp // Temporarily placing in 'temp' due to permissions
import android.hardware.GeomagneticField
import android.location.Location
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import java.util.Date
/**
* Data class representing processed heading information.
* @param trueHeading The heading relative to true North (0-359.9 degrees).
* @param magneticHeading The heading relative to magnetic North (0-359.9 degrees).
* @param magneticVariation The difference between true and magnetic North at the current location (+E, -W).
* @param cog Course Over Ground (0-359.9 degrees).
*/
data class HeadingInfo(
val trueHeading: Float,
val magneticHeading: Float,
val magneticVariation: Float,
val cog: Float
)
/**
* Processor for handling heading data, including magnetic variation calculations
* using the Android GeomagneticField.
*/
class HeadingDataProcessor {
private val _headingInfoFlow = MutableStateFlow(HeadingInfo(0f, 0f, 0f, 0f))
val headingInfoFlow: StateFlow<HeadingInfo> = _headingInfoFlow.asStateFlow()
private var currentLatitude: Double = 0.0
private var currentLongitude: Double = 0.0
private var currentAltitude: Double = 0.0
/**
* Updates the current geographic location for magnetic variation calculations.
*/
fun updateLocation(latitude: Double, longitude: Double, altitude: Double) {
currentLatitude = latitude
currentLongitude = longitude
currentAltitude = altitude
// Recalculate magnetic variation if location changes
updateHeadingInfo(_headingInfoFlow.value.trueHeading, _headingInfoFlow.value.cog, true)
}
/**
* Processes a new true heading and Course Over Ground (COG) value.
* @param newTrueHeading The new true heading in degrees.
* @param newCog The new COG in degrees.
*/
fun updateTrueHeadingAndCog(newTrueHeading: Float, newCog: Float) {
updateHeadingInfo(newTrueHeading, newCog, true)
}
/**
* Processes a new magnetic heading and Course Over Ground (COG) value.
* @param newMagneticHeading The new magnetic heading in degrees.
* @param newCog The new COG in degrees.
*/
fun updateMagneticHeadingAndCog(newMagneticHeading: Float, newCog: Float) {
updateHeadingInfo(newMagneticHeading, newCog, false)
}
private fun updateHeadingInfo(heading: Float, cog: Float, isTrueHeadingInput: Boolean) {
val magneticVariation = calculateMagneticVariation()
val (finalTrueHeading, finalMagneticHeading) = if (isTrueHeadingInput) {
Pair(heading, (heading - magneticVariation + 360) % 360)
} else {
Pair((heading + magneticVariation + 360) % 360, heading)
}
_headingInfoFlow.update {
it.copy(
trueHeading = finalTrueHeading,
magneticHeading = finalMagneticHeading,
magneticVariation = magneticVariation,
cog = cog
)
}
}
/**
* Calculates the magnetic variation (declination) for the current location.
* @return Magnetic variation in degrees (+E, -W).
*/
private fun calculateMagneticVariation(): Float {
// GeomagneticField requires current time in milliseconds
val currentTimeMillis = System.currentTimeMillis()
// Create a dummy Location object to get altitude if only lat/lon are updated
// GeomagneticField needs altitude, using 0 if not provided
val geoField = GeomagneticField(
currentLatitude.toFloat(),
currentLongitude.toFloat(),
currentAltitude.toFloat(), // Altitude in meters
currentTimeMillis
)
return geoField.declination // Declination is the magnetic variation
}
// Helper function to normalize angles (0-359.9) - though modulo handles this for positive floats
private fun normalizeAngle(angle: Float): Float {
return (angle % 360 + 360) % 360
}
}
|