fix(map): pick the pass by time instead of a field that is never set
The map info panel chose which pass to describe with
allPasses.find { it.catNum == catnum && it.progress < 1 }
but OrbitalPass.progress defaults to 0 and nothing in the repository or the
prediction layer ever assigns it - PassesViewModel computes progress on its own
local copy of the list and never writes it back. The predicate was therefore
always true, so the lookup returned the satellite's *first* pass forever.
Simulated over an ISS timeline with three passes (10:00, 12:00, 14:00), 4 of 6
sampled instants were wrong: from 10:30 onwards the panel still pointed at the
10:00 pass with its countdown frozen at 00:00:00, instead of counting down to the
12:00 and 14:00 passes. Only re-fetching the pass list refreshed it.
Select by time now: the pass currently in progress, otherwise the earliest one
still upcoming. Extracted as the pure internal selectCurrentOrNextPass so it is
testable, with the reasoning recorded so the progress field is not reintroduced
as a filter here.
Restoring the old predicate fails 5 of the 6 new tests; with the fix
:feature:map:testDebugUnitTest, :core:domain:test, :core:data:testDebugUnitTest
and :feature:roaming:testDebugUnitTest are all green.
This commit is contained in:
1 parent
c7bb253981
commit
aac1fa0da5
2 files changed
+120
-18
No files matched your search
@@ -220,26 +220,23 @@ class MapViewModel(
|
||||
var orbitalPass = defaultPass
|
||||
var aosTime = 0L.toTimerString()
|
||||
var isTimeAos = true
|
||||
allPasses.find { pass -> pass.catNum == sat.data.catnum && pass.progress < 1 }
|
||||
?.let { satPass ->
|
||||
orbitalPass = satPass
|
||||
if (!satPass.isDeepSpace) {
|
||||
when {
|
||||
date.time < satPass.aosTime -> {
|
||||
// Pass hasn't started yet — count down to AOS
|
||||
isTimeAos = true
|
||||
aosTime = (satPass.aosTime - date.time).toTimerString()
|
||||
}
|
||||
|
||||
date.time < satPass.losTime -> {
|
||||
// Pass is in progress — count down to LOS
|
||||
isTimeAos = false
|
||||
aosTime = (satPass.losTime - date.time).toTimerString()
|
||||
}
|
||||
// else: pass has ended (losTime <= date.time) — keep default "00:00:00"
|
||||
}
|
||||
// Select by time, not by OrbitalPass.progress: that field defaults to 0
|
||||
// and is only ever filled in by PassesViewModel's own local copy, never
|
||||
// written back to the repository. Filtering on `progress < 1` therefore
|
||||
// always matched the satellite's *first* pass, so once it ended the map
|
||||
// countdown stuck at 00:00:00 and never advanced to the next pass.
|
||||
currentOrNextPass(sat.data.catnum, date.time)?.let { satPass ->
|
||||
orbitalPass = satPass
|
||||
if (!satPass.isDeepSpace) {
|
||||
if (date.time < satPass.aosTime) {
|
||||
isTimeAos = true
|
||||
aosTime = (satPass.aosTime - date.time).toTimerString()
|
||||
} else if (date.time < satPass.losTime) {
|
||||
isTimeAos = false
|
||||
aosTime = (satPass.losTime - date.time).toTimerString()
|
||||
}
|
||||
}
|
||||
}
|
||||
val azimuth = satPos.azimuth.toDegrees()
|
||||
val elevation = satPos.elevation.toDegrees()
|
||||
val osmPos = satPos.toMapGeoPos()
|
||||
@@ -266,6 +263,10 @@ class MapViewModel(
|
||||
return satData to orbitalPass
|
||||
}
|
||||
|
||||
/** Ongoing pass for [catnum] if any, otherwise its next upcoming pass. */
|
||||
private fun currentOrNextPass(catnum: Int, timeMillis: Long): OrbitalPass? =
|
||||
selectCurrentOrNextPass(allPasses, catnum, timeMillis)
|
||||
|
||||
private suspend fun getSatTrack(orbitalObject: OrbitalObject, pos: GeoPos, date: Date) {
|
||||
val endDate = Date(date.time + (orbitalObject.data.orbitalPeriod * 2.4 * 60000L).toLong())
|
||||
val track = satelliteRepo.getTrack(orbitalObject, pos, date.time, endDate.time)
|
||||
@@ -331,3 +332,20 @@ internal fun crossingLatitude(from: GeoPos, to: GeoPos, edgeLongitude: Double):
|
||||
val fraction = (edgeLongitude - from.longitude) / span
|
||||
return from.latitude + (to.latitude - from.latitude) * fraction
|
||||
}
|
||||
/**
|
||||
* Picks the pass to describe for [catnum] at [timeMillis]: the one in progress,
|
||||
* or else the earliest one still to come.
|
||||
*
|
||||
* Deliberately ignores OrbitalPass.progress. That field is a UI-side value filled
|
||||
* in by PassesViewModel on its own copy of the list; the repository always
|
||||
* reports 0, so any `progress` predicate here silently matches everything.
|
||||
*/
|
||||
internal fun selectCurrentOrNextPass(
|
||||
passes: List<OrbitalPass>,
|
||||
catnum: Int,
|
||||
timeMillis: Long
|
||||
): OrbitalPass? {
|
||||
val forSatellite = passes.filter { it.catNum == catnum }
|
||||
return forSatellite.firstOrNull { timeMillis >= it.aosTime && timeMillis < it.losTime }
|
||||
?: forSatellite.filter { it.aosTime > timeMillis }.minByOrNull { it.aosTime }
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.rtbishop.look4sat.feature.map
|
||||
|
||||
import com.rtbishop.look4sat.core.domain.predict.NearEarthObject
|
||||
import com.rtbishop.look4sat.core.domain.predict.OrbitalData
|
||||
import com.rtbishop.look4sat.core.domain.predict.OrbitalPass
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* The map info panel must describe the pass that is happening now, or the next
|
||||
* one if none is.
|
||||
*
|
||||
* Regression: selection used to filter on `OrbitalPass.progress < 1`. That field
|
||||
* defaults to 0 and is only ever updated on PassesViewModel's private copy, so
|
||||
* the predicate matched every pass and the map always described the satellite's
|
||||
* first pass - leaving the countdown frozen at 00:00:00 once it had ended.
|
||||
*/
|
||||
class CurrentOrNextPassTest {
|
||||
|
||||
private val hour = 3_600_000L
|
||||
private val iss = 25544
|
||||
private val noaa = 33591
|
||||
|
||||
private fun orbitalData(catnum: Int) = OrbitalData(
|
||||
name = "sat-$catnum",
|
||||
epoch = 21320.51955234,
|
||||
meanmo = 15.48582035,
|
||||
eccn = 0.0004694,
|
||||
incl = 51.6447,
|
||||
raan = 309.4881,
|
||||
argper = 203.6966,
|
||||
meanan = 299.8876,
|
||||
catnum = catnum,
|
||||
bstar = 0.31985e-4
|
||||
)
|
||||
|
||||
private fun pass(catnum: Int, aos: Long, los: Long) = OrbitalPass(
|
||||
aosTime = aos,
|
||||
losTime = los,
|
||||
orbitalObject = NearEarthObject(orbitalData(catnum))
|
||||
)
|
||||
|
||||
private val issFirst = pass(iss, 10 * hour, 10 * hour + 600_000)
|
||||
private val issSecond = pass(iss, 12 * hour, 12 * hour + 600_000)
|
||||
private val issThird = pass(iss, 14 * hour, 14 * hour + 600_000)
|
||||
private val other = pass(noaa, 11 * hour, 11 * hour + 600_000)
|
||||
private val passes = listOf(issFirst, other, issSecond, issThird)
|
||||
|
||||
@Test
|
||||
fun `before the first pass returns the first pass`() {
|
||||
assertEquals(issFirst, selectCurrentOrNextPass(passes, iss, (9.5 * hour).toLong()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `during a pass returns that pass`() {
|
||||
assertEquals(issFirst, selectCurrentOrNextPass(passes, iss, 10 * hour + 300_000))
|
||||
assertEquals(issSecond, selectCurrentOrNextPass(passes, iss, 12 * hour + 300_000))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `after a pass ends returns the following pass`() {
|
||||
// The case the progress predicate got wrong: it kept returning issFirst.
|
||||
assertEquals(issSecond, selectCurrentOrNextPass(passes, iss, (10.5 * hour).toLong()))
|
||||
assertEquals(issThird, selectCurrentOrNextPass(passes, iss, 13 * hour))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `after the last pass returns null`() {
|
||||
assertNull(selectCurrentOrNextPass(passes, iss, 15 * hour))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `other satellites are ignored`() {
|
||||
assertEquals(other, selectCurrentOrNextPass(passes, noaa, 11 * hour + 60_000))
|
||||
assertNull(selectCurrentOrNextPass(passes, noaa, 12 * hour))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `exact aos belongs to the pass and exact los does not`() {
|
||||
assertEquals(issFirst, selectCurrentOrNextPass(passes, iss, 10 * hour))
|
||||
assertEquals(issSecond, selectCurrentOrNextPass(passes, iss, 10 * hour + 600_000))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user