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