mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
fix: mutual pass search now matches main radar pass definition
Root cause: mutual search used the min-elevation threshold (default 10°) as the AOS/LOS boundary, while the main radar uses the 0° horizon (getLeoPass). With identical stations the windows therefore never matched. Also, an in-progress pass was counted as a new one. - AOS/LOS boundaries now at the 0° horizon (refined to 1s) - Skip in-progress mutual windows at search start (like getLeoPass) - Filter requires BOTH stations to reach their min elevation - Default min elevation follows the main radar passes setting
This commit is contained in:
1 parent
1a3f1bb34c
commit
43788ba7a7
1 file changed
+29
-20
+29
-20
@@ -61,10 +61,12 @@ class MutualViewModel(
|
|||||||
val uiState: StateFlow<MutualUiState> = _uiState.asStateFlow()
|
val uiState: StateFlow<MutualUiState> = _uiState.asStateFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Pre-fill station A with the user's current station position (as grid)
|
// Pre-fill station A with the user's current station position (as grid),
|
||||||
|
// and default min elevation to the same value used by the main radar passes
|
||||||
val pos = settingsRepo.stationPosition.value
|
val pos = settingsRepo.stationPosition.value
|
||||||
val grid = latLonToGrid(pos.latitude, pos.longitude)
|
val grid = latLonToGrid(pos.latitude, pos.longitude)
|
||||||
_uiState.update { it.copy(stationAGrid = grid) }
|
val minElev = settingsRepo.passesSettings.value.minElevation
|
||||||
|
_uiState.update { it.copy(stationAGrid = grid, stationAMinElev = minElev, stationBMinElev = minElev) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onStationALat(value: String) = _uiState.update { it.copy(stationALat = value) }
|
fun onStationALat(value: String) = _uiState.update { it.copy(stationALat = value) }
|
||||||
@@ -107,7 +109,6 @@ class MutualViewModel(
|
|||||||
val results = withContext(Dispatchers.Default) {
|
val results = withContext(Dispatchers.Default) {
|
||||||
findMutualPasses(satellites, posA, posB, minElevA, minElevB, time, hours)
|
findMutualPasses(satellites, posA, posB, minElevA, minElevB, time, hours)
|
||||||
}
|
}
|
||||||
|
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
mutualPasses = results.sortedBy { mp -> mp.startTime },
|
mutualPasses = results.sortedBy { mp -> mp.startTime },
|
||||||
@@ -214,14 +215,14 @@ class MutualViewModel(
|
|||||||
var searchStart = time
|
var searchStart = time
|
||||||
// Find ALL mutual passes for this satellite
|
// Find ALL mutual passes for this satellite
|
||||||
while (true) {
|
while (true) {
|
||||||
val t = findNextMutualPass(sat, posA, posB, minElevADeg, minElevBDeg, searchStart, endTime)
|
val t = findNextMutualPass(sat, posA, posB, searchStart, endTime)
|
||||||
if (t == null) break
|
if (t == null) break
|
||||||
val (aos, los) = t
|
val (aos, los) = t
|
||||||
|
|
||||||
// Refine AOS: finer search near the edge
|
// Refine AOS: finer search near the edge (0° horizon, like the main radar)
|
||||||
val refinedAos = refineEdge(sat, posA, posB, minElevADeg, minElevBDeg, aos, step = 1_000L, goingUp = true)
|
val refinedAos = refineEdge(sat, posA, posB, aos, step = 1_000L, goingUp = true)
|
||||||
// Refine LOS: finer search near the edge
|
// Refine LOS: finer search near the edge
|
||||||
val refinedLos = refineEdge(sat, posA, posB, minElevADeg, minElevBDeg, los, step = 1_000L, goingUp = false)
|
val refinedLos = refineEdge(sat, posA, posB, los, step = 1_000L, goingUp = false)
|
||||||
|
|
||||||
val samples = mutableListOf<Pair<Long, Pair<Double, Double>>>()
|
val samples = mutableListOf<Pair<Long, Pair<Double, Double>>>()
|
||||||
val tracks = mutableListOf<TrackSample>()
|
val tracks = mutableListOf<TrackSample>()
|
||||||
@@ -249,7 +250,8 @@ class MutualViewModel(
|
|||||||
tSample += sampleInterval
|
tSample += sampleInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxElevA > minElevADeg || maxElevB > minElevBDeg) {
|
// Both stations must reach their own min elevation for a usable mutual pass
|
||||||
|
if (maxElevA > minElevADeg && maxElevB > minElevBDeg) {
|
||||||
results.add(
|
results.add(
|
||||||
MutualPass(
|
MutualPass(
|
||||||
catNum = sat.data.catnum,
|
catNum = sat.data.catnum,
|
||||||
@@ -270,20 +272,19 @@ class MutualViewModel(
|
|||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Refine the AOS (goingUp=true) or LOS (goingUp=false) with fine steps. */
|
/** Refine the AOS (goingUp=true) or LOS (goingUp=false) to ~1s precision at the 0° horizon. */
|
||||||
private fun refineEdge(
|
private fun refineEdge(
|
||||||
sat: OrbitalObject, posA: GeoPos, posB: GeoPos,
|
sat: OrbitalObject, posA: GeoPos, posB: GeoPos,
|
||||||
minElevADeg: Double, minElevBDeg: Double,
|
|
||||||
approxTime: Long, step: Long, goingUp: Boolean
|
approxTime: Long, step: Long, goingUp: Boolean
|
||||||
): Long {
|
): Long {
|
||||||
if (goingUp) {
|
if (goingUp) {
|
||||||
// AOS: walk backward from approxTime to find the last sample where both are below,
|
// AOS: walk backward from approxTime to find the last sample where either is below
|
||||||
// then AOS is the next step after that.
|
// the horizon, then AOS is the next step after that.
|
||||||
var t = approxTime
|
var t = approxTime
|
||||||
while (t > approxTime - 70_000L) {
|
while (t > approxTime - 70_000L) {
|
||||||
val eA = elevationDeg(sat, posA, t)
|
val eA = elevationDeg(sat, posA, t)
|
||||||
val eB = elevationDeg(sat, posB, t)
|
val eB = elevationDeg(sat, posB, t)
|
||||||
if (eA > minElevADeg && eB > minElevBDeg) {
|
if (eA > 0.0 && eB > 0.0) {
|
||||||
t -= step
|
t -= step
|
||||||
} else {
|
} else {
|
||||||
return t + step
|
return t + step
|
||||||
@@ -291,13 +292,13 @@ class MutualViewModel(
|
|||||||
}
|
}
|
||||||
return approxTime - 70_000L + step
|
return approxTime - 70_000L + step
|
||||||
} else {
|
} else {
|
||||||
// LOS: walk forward from approxTime to find the first sample where either drops below,
|
// LOS: walk forward from approxTime to find the first sample where either drops
|
||||||
// then LOS is the step before that.
|
// below the horizon, then LOS is the step before that.
|
||||||
var t = approxTime
|
var t = approxTime
|
||||||
while (t < approxTime + 70_000L) {
|
while (t < approxTime + 70_000L) {
|
||||||
val eA = elevationDeg(sat, posA, t)
|
val eA = elevationDeg(sat, posA, t)
|
||||||
val eB = elevationDeg(sat, posB, t)
|
val eB = elevationDeg(sat, posB, t)
|
||||||
if (eA > minElevADeg && eB > minElevBDeg) {
|
if (eA > 0.0 && eB > 0.0) {
|
||||||
t += step
|
t += step
|
||||||
} else {
|
} else {
|
||||||
return t - step
|
return t - step
|
||||||
@@ -314,23 +315,31 @@ class MutualViewModel(
|
|||||||
private fun findNextMutualPass(
|
private fun findNextMutualPass(
|
||||||
sat: OrbitalObject,
|
sat: OrbitalObject,
|
||||||
posA: GeoPos, posB: GeoPos,
|
posA: GeoPos, posB: GeoPos,
|
||||||
minElevADeg: Double, minElevBDeg: Double,
|
|
||||||
startTime: Long, endTime: Long
|
startTime: Long, endTime: Long
|
||||||
): Pair<Long, Long>? {
|
): Pair<Long, Long>? {
|
||||||
var t = startTime
|
var t = startTime
|
||||||
val step = 60_000L
|
val step = 60_000L
|
||||||
|
|
||||||
|
// Skip an in-progress mutual window at the search start (same as getLeoPass):
|
||||||
|
// walk forward until either station drops below the horizon, then keep searching.
|
||||||
|
if (elevationDeg(sat, posA, t) > 0.0 && elevationDeg(sat, posB, t) > 0.0) {
|
||||||
|
while (t < endTime) {
|
||||||
|
if (elevationDeg(sat, posA, t) <= 0.0 || elevationDeg(sat, posB, t) <= 0.0) break
|
||||||
|
t += step
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (t < endTime) {
|
while (t < endTime) {
|
||||||
val elevA = elevationDeg(sat, posA, t)
|
val elevA = elevationDeg(sat, posA, t)
|
||||||
val elevB = elevationDeg(sat, posB, t)
|
val elevB = elevationDeg(sat, posB, t)
|
||||||
|
|
||||||
if (elevA > minElevADeg && elevB > minElevBDeg) {
|
if (elevA > 0.0 && elevB > 0.0) {
|
||||||
var aos = t
|
var aos = t
|
||||||
var rew = t
|
var rew = t
|
||||||
while (rew > startTime - 600_000L) {
|
while (rew > startTime - 600_000L) {
|
||||||
val eA = elevationDeg(sat, posA, rew)
|
val eA = elevationDeg(sat, posA, rew)
|
||||||
val eB = elevationDeg(sat, posB, rew)
|
val eB = elevationDeg(sat, posB, rew)
|
||||||
if (eA < minElevADeg || eB < minElevBDeg) {
|
if (eA <= 0.0 || eB <= 0.0) {
|
||||||
aos = rew + step
|
aos = rew + step
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -342,7 +351,7 @@ class MutualViewModel(
|
|||||||
while (fwd < endTime + 600_000L) {
|
while (fwd < endTime + 600_000L) {
|
||||||
val eA = elevationDeg(sat, posA, fwd)
|
val eA = elevationDeg(sat, posA, fwd)
|
||||||
val eB = elevationDeg(sat, posB, fwd)
|
val eB = elevationDeg(sat, posB, fwd)
|
||||||
if (eA < minElevADeg || eB < minElevBDeg) {
|
if (eA <= 0.0 || eB <= 0.0) {
|
||||||
los = fwd
|
los = fwd
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user