summaryrefslogtreecommitdiff
path: root/android-app/app/src/main/temp/HeadingDataProcessor.kt
blob: 7625f90ea89797dce924f39ba0a8b33f9d02e8f8 (plain)
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
    }
}