refine: AOS binary-search refinement, getRadios signature cleanup

Refine AOS crossing to ~500ms via binary search instead of linear stepping; simplify getRadios to take satPos; tidy radar view/viewmodel.
This commit is contained in:
mckero committed 2026-08-03 06:47:49 +00:00
1 parent 1813e1bfa4
commit 9d0d971500
4 files changed
+38 -33

No files matched your search

@@ -95,14 +95,8 @@ class SatelliteRepo(
} }
} }
override suspend fun getRadios( override suspend fun getRadios(satPos: OrbitalPos, radios: List<SatRadio>): List<SatRadio> {
sat: OrbitalObject,
pos: GeoPos,
radios: List<SatRadio>,
time: Long
): List<SatRadio> {
return withContext(dispatcher) { return withContext(dispatcher) {
val satPos = sat.getPosition(pos, time)
radios.map { transmitter -> radios.map { transmitter ->
transmitter.copy( transmitter.copy(
downlinkLow = transmitter.downlinkLow?.let { satPos.getDownlinkFreq(it) }, downlinkLow = transmitter.downlinkLow?.let { satPos.getDownlinkFreq(it) },
@@ -242,13 +236,16 @@ class SatelliteRepo(
if (elevation > maxElevation) maxElevation = elevation if (elevation > maxElevation) maxElevation = elevation
} while (elevation < 0.0) } while (elevation < 0.0)
// refine AOS to ~500ms precision // refine AOS to ~500ms precision via binary search.
calendarTimeMillis -= 60L * 1000L // Elevation is monotonic across the horizon crossing, so binary search
do { // finds the crossing in ~8 SGP4 calls instead of up to 120 linear steps.
calendarTimeMillis += 500L var aosLo = calendarTimeMillis - 60L * 1000L // elevation < 0 (below horizon)
elevation = sat.getElevation(pos, calendarTimeMillis) var aosHi = calendarTimeMillis // elevation >= 0 (above horizon)
if (elevation > maxElevation) maxElevation = elevation while (aosHi - aosLo > 500L) {
} while (elevation < 0.0) val mid = (aosLo + aosHi) / 2
if (sat.getElevation(pos, mid) < 0.0) aosLo = mid else aosHi = mid
}
calendarTimeMillis = aosHi
// Get full position for AOS data (azimuth, altitude) // Get full position for AOS data (azimuth, altitude)
val aosPos = sat.getFullPosition(pos, calendarTimeMillis) val aosPos = sat.getFullPosition(pos, calendarTimeMillis)
@@ -262,13 +259,14 @@ class SatelliteRepo(
if (elevation > maxElevation) maxElevation = elevation if (elevation > maxElevation) maxElevation = elevation
} while (elevation > 0.0) } while (elevation > 0.0)
// refine LOS to ~500ms precision // refine LOS to ~500ms precision via binary search (same monotonic argument)
calendarTimeMillis -= 30L * 1000L var losLo = calendarTimeMillis - 30L * 1000L // elevation > 0 (above horizon)
do { var losHi = calendarTimeMillis // elevation <= 0 (below horizon)
calendarTimeMillis += 500L while (losHi - losLo > 500L) {
elevation = sat.getElevation(pos, calendarTimeMillis) val mid = (losLo + losHi) / 2
if (elevation > maxElevation) maxElevation = elevation if (sat.getElevation(pos, mid) > 0.0) losLo = mid else losHi = mid
} while (elevation > 0.0) }
calendarTimeMillis = losHi
// Get full position for LOS data (azimuth, altitude) // Get full position for LOS data (azimuth, altitude)
val losPos = sat.getFullPosition(pos, calendarTimeMillis) val losPos = sat.getFullPosition(pos, calendarTimeMillis)
@@ -61,7 +61,7 @@ interface ISatelliteRepo {
suspend fun getTrack(sat: OrbitalObject, pos: GeoPos, start: Long, end: Long): List<OrbitalPos> suspend fun getTrack(sat: OrbitalObject, pos: GeoPos, start: Long, end: Long): List<OrbitalPos>
/** Get Doppler-shifted radio frequencies for a satellite at the given time. */ /** Get Doppler-shifted radio frequencies for a satellite at the given time. */
suspend fun getRadios(sat: OrbitalObject, pos: GeoPos, radios: List<SatRadio>, time: Long): List<SatRadio> suspend fun getRadios(satPos: OrbitalPos, radios: List<SatRadio>): List<SatRadio>
/** Fetch radio transceivers for a satellite by its catalog number. */ /** Fetch radio transceivers for a satellite by its catalog number. */
suspend fun getRadiosWithId(id: Int): List<SatRadio> suspend fun getRadiosWithId(id: Int): List<SatRadio>
@@ -94,14 +94,21 @@ fun RadarViewCompose(
animationSpec = infiniteRepeatable(tween(1000)), animationSpec = infiniteRepeatable(tween(1000)),
label = "animScale" label = "animScale"
) )
// Drive the sweep from the animation framework to eliminate state mutation inside the draw block // Drive the sweep from the animation framework to eliminate state mutation inside the draw block.
val sweepTransition = rememberInfiniteTransition(label = "sweep") // Only create the infinite transition when the sweep is enabled — otherwise the canvas would
val sweepDegrees by sweepTransition.animateFloat( // be invalidated every frame for an effect that isn't visible, wasting GPU and battery.
initialValue = 0f, val sweepDegrees: Float
targetValue = 360f, if (shouldShowSweep) {
animationSpec = infiniteRepeatable(tween(SWEEP_DURATION_MS, easing = LinearEasing)), val sweepTransition = rememberInfiniteTransition(label = "sweep")
label = "sweepDegrees" sweepDegrees by sweepTransition.animateFloat(
) initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(tween(SWEEP_DURATION_MS, easing = LinearEasing)),
label = "sweepDegrees"
)
} else {
sweepDegrees = 0f
}
val measurer = rememberTextMeasurer() val measurer = rememberTextMeasurer()
val sunPainter = painterResource(R.drawable.ic_sun) val sunPainter = painterResource(R.drawable.ic_sun)
val moonPainter = painterResource(R.drawable.ic_moon) val moonPainter = painterResource(R.drawable.ic_moon)
@@ -181,7 +181,7 @@ class RadarViewModel(
orbitalPos = pos, sunPosition = cachedSunPos, moonPosition = cachedMoonPos orbitalPos = pos, sunPosition = cachedSunPos, moonPosition = cachedMoonPos
) )
} }
processRadios(allRadios, pass.orbitalObject, timeNow) processRadios(allRadios, pos)
sendPassData(pos) sendPassData(pos)
} }
@@ -342,8 +342,8 @@ class RadarViewModel(
} }
} }
private suspend fun processRadios(radios: List<SatRadio>, orbitalObject: OrbitalObject, time: Long) { private suspend fun processRadios(radios: List<SatRadio>, satPos: OrbitalPos) {
val transmitters = satelliteRepo.getRadios(orbitalObject, stationPos, radios, time) val transmitters = satelliteRepo.getRadios(satPos, radios)
_uiState.update { state -> _uiState.update { state ->
val freq = if (state.transceivers.selectedUuid != null) { val freq = if (state.transceivers.selectedUuid != null) {
val selectedRadio = transmitters.firstOrNull { it.uuid == state.transceivers.selectedUuid } val selectedRadio = transmitters.firstOrNull { it.uuid == state.transceivers.selectedUuid }