From 8f56ea05ecd594b7a0e3e05589868b9a7e72ec3f Mon Sep 17 00:00:00 2001 From: QIU Date: Mon, 17 Aug 2026 17:17:19 +0000 Subject: [PATCH] refactor(roaming): reuse positionToQth instead of the ported grid tables RoamingScreen carried ~170 lines of decompiled range-lookup tables (encodeLon/encodeLat) that re-implemented exactly what core:domain's positionToQth already does. A probe calling both over 16,471 sampled coordinates (every 2 degrees across the full globe) found byte-identical 8-char locators, so the tables were pure duplication - two implementations of the same Maidenhead encoding that had to be kept in sync (the earlier boundary fix had to be applied twice). Replace them with positionToQth and split its standard-ordered output (lonField latField lonSquare latSquare lonSub latSub lonSubsub latSubsub) back into the per-axis segments the UI consumes: the 3x3 ring (first 4 chars), markerLeft (lon subsquare), markerTop (lat subsquare). Out-of-range input keeps the old blank-segment behaviour: positionToQth returns null, the locator becomes spaces, qthNeighbors returns an empty ring, and the marker lookups fall back to 0. Net -160 lines. All existing RoamingState tests and the new equivalence probe pass; :feature:roaming compiles. --- .../look4sat/feature/roaming/RoamingScreen.kt | 194 ++---------------- 1 file changed, 17 insertions(+), 177 deletions(-) diff --git a/feature/roaming/src/main/java/com/rtbishop/look4sat/feature/roaming/RoamingScreen.kt b/feature/roaming/src/main/java/com/rtbishop/look4sat/feature/roaming/RoamingScreen.kt index 830d4e24..92d0080c 100644 --- a/feature/roaming/src/main/java/com/rtbishop/look4sat/feature/roaming/RoamingScreen.kt +++ b/feature/roaming/src/main/java/com/rtbishop/look4sat/feature/roaming/RoamingScreen.kt @@ -71,6 +71,7 @@ import androidx.core.content.ContextCompat import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +import com.rtbishop.look4sat.core.domain.utility.positionToQth import com.rtbishop.look4sat.core.domain.utility.qthNeighbors /** @@ -404,10 +405,22 @@ fun roamingStateFromLocation(lat: Double, lon: Double, fixTime: Long, hourPrefix val lonSec = ((lonMinFull - lonMin) * 60.0).toInt() val lonLabel = "经度 $lonDeg° $lonMin' $lonSec\" $ew" val lonValue = "$lon° " - // Ported: 8-char grid encoding (range lookup) - val (str, str2, str3, str4) = encodeLon(lon) - val (str5, str6, str38, str9) = encodeLat(lat) - val loc = str + str5 + str2 + str6 + str3 + str38 + str4 + str9 + // 8-char Maidenhead grid: reuse the shared positionToQth instead of the + // ported range-lookup tables. The decompiled encodeLon/encodeLat produced + // byte-identical locators (verified over 16,471 sampled coordinates), so + // this is a pure de-duplication: one implementation to keep correct. + // loc layout (standard Maidenhead): lonField latField lonSquare latSquare + // lonSub latSub lonSubsub latSubsub — exactly the segment order the + // decompiled tables emitted. + val loc = positionToQth(lat, lon) ?: " " + val str = loc.substring(0, 1) // lon field + val str2 = loc.substring(2, 3) // lon square + val str3 = loc.substring(4, 5) // lon subsquare + val str4 = loc.substring(6, 7) // lon subsub + val str5 = loc.substring(1, 2) // lat field + val str6 = loc.substring(3, 4) // lat square + val str38 = loc.substring(5, 6) // lat subsquare + val str9 = loc.substring(7, 8) // lat subsub // The 3x3 ring is pure Maidenhead arithmetic, so derive it from the shared // helper instead of the decompiled per-edge branches. Those branches carried // the field letter in only some of the cells they moved (the north edge @@ -451,176 +464,3 @@ private val latMargin = mapOf( 'i' to 128, 'j' to 119, 'k' to 110, 'l' to 101, 'm' to 92, 'n' to 83, 'o' to 74, 'p' to 65, 'q' to 56, 'r' to 48, 's' to 40, 't' to 32, 'u' to 24, 'v' to 16, 'w' to 8, 'x' to -2 ) - -// Ported lines 311-611: longitude -> (20 deg zone letter, 2 deg digit, 2' letter, 30" digit) -private fun encodeLon(inputLon: Double): List { - // The decompiled range table uses closed bounds on both adjacent cells - // (e.g. -20..0 followed by 0..20), and `when` picks the first match. Exact - // field/square/subsquare boundaries therefore fell into the previous cell: - // lon=0 produced I... instead of J..., lon=108 lost one square/subsquare. - // A tiny positive nudge implements the standard half-open [low, high) - // convention without rewriting the faithful lookup table; keep +180 inside - // the final R cell. - var longitude = if (inputLon < 180.0) inputLon + 1e-10 else 180.0 - 1e-10 - var d = 0.0 - val str = when { - (longitude >= -180.0) && (longitude <= -160.0) -> { longitude += 180.0; "A" } - (longitude >= -160.0) && (longitude <= -140.0) -> { longitude += 160.0; "B" } - (longitude >= -140.0) && (longitude <= -120.0) -> { longitude += 140.0; "C" } - (longitude >= -120.0) && (longitude <= -100.0) -> { longitude += 120.0; "D" } - (longitude >= -100.0) && (longitude <= -80.0) -> { longitude += 100.0; "E" } - (longitude >= -80.0) && (longitude <= -60.0) -> { longitude += 80.0; "F" } - (longitude >= -60.0) && (longitude <= -40.0) -> { longitude += 60.0; "G" } - (longitude >= -40.0) && (longitude <= -20.0) -> { longitude += 40.0; "H" } - (longitude >= -20.0) && (longitude <= 0.0) -> { longitude += 20.0; "I" } - (longitude >= 0.0) && (longitude <= 20.0) -> "J" - (longitude >= 20.0) && (longitude <= 40.0) -> { longitude -= 20.0; "K" } - (longitude >= 40.0) && (longitude <= 60.0) -> { longitude -= 40.0; "L" } - (longitude >= 60.0) && (longitude <= 80.0) -> { longitude -= 60.0; "M" } - (longitude >= 80.0) && (longitude <= 100.0) -> { longitude -= 80.0; "N" } - (longitude >= 100.0) && (longitude <= 120.0) -> { longitude -= 100.0; "O" } - (longitude >= 120.0) && (longitude <= 140.0) -> { longitude -= 120.0; "P" } - (longitude >= 140.0) && (longitude <= 160.0) -> { longitude -= 140.0; "Q" } - (longitude >= 160.0) && (longitude <= 180.0) -> { longitude -= 160.0; "R" } - else -> { longitude = 0.0; " " } - } - val str2 = when { - ((longitude >= 0.0) && (longitude <= 2.0)) || ((longitude >= -20.0) && (longitude <= -18.0)) -> { d = longitude * 60.0; "0" } - ((longitude >= 2.0) && (longitude <= 4.0)) || ((longitude >= -18.0) && (longitude <= -16.0)) -> { d = (longitude - 2.0) * 60.0; "1" } - ((longitude >= 4.0) && (longitude <= 6.0)) || ((longitude >= -16.0) && (longitude <= -14.0)) -> { d = (longitude - 4.0) * 60.0; "2" } - ((longitude >= 6.0) && (longitude <= 8.0)) || ((longitude >= -14.0) && (longitude <= -12.0)) -> { d = (longitude - 6.0) * 60.0; "3" } - ((longitude >= 8.0) && (longitude <= 10.0)) || ((longitude >= -12.0) && (longitude <= -10.0)) -> { d = (longitude - 8.0) * 60.0; "4" } - ((longitude >= 10.0) && (longitude <= 12.0)) || ((longitude >= -10.0) && (longitude <= -8.0)) -> { d = (longitude - 10.0) * 60.0; "5" } - ((longitude >= 12.0) && (longitude <= 14.0)) || ((longitude >= -8.0) && (longitude <= -6.0)) -> { d = (longitude - 12.0) * 60.0; "6" } - ((longitude >= 14.0) && (longitude <= 16.0)) || ((longitude >= -6.0) && (longitude <= -4.0)) -> { d = (longitude - 14.0) * 60.0; "7" } - ((longitude >= 16.0) && (longitude <= 18.0)) || ((longitude >= -4.0) && (longitude <= -2.0)) -> { d = (longitude - 16.0) * 60.0; "8" } - ((longitude >= 18.0) && (longitude <= 20.0)) || ((longitude >= -2.0) && (longitude <= 0.0)) -> { d = (longitude - 18.0) * 60.0; "9" } - else -> { d = 0.0; " " } - } - val str3 = when { - ((d >= 0.0) && (d <= 5.0)) || ((d >= -120.0) && (d <= -115.0)) -> { longitude = d * 60.0; "a" } - ((d >= 5.0) && (d <= 10.0)) || ((d >= -115.0) && (d <= -110.0)) -> { longitude = (d - 5.0) * 60.0; "b" } - ((d >= 10.0) && (d <= 15.0)) || ((d >= -110.0) && (d <= -105.0)) -> { longitude = (d - 10.0) * 60.0; "c" } - ((d >= 15.0) && (d <= 20.0)) || ((d >= -105.0) && (d <= -100.0)) -> { longitude = (d - 15.0) * 60.0; "d" } - ((d >= 20.0) && (d <= 25.0)) || ((d >= -100.0) && (d <= -95.0)) -> { longitude = (d - 20.0) * 60.0; "e" } - ((d >= 25.0) && (d <= 30.0)) || ((d >= -95.0) && (d <= -90.0)) -> { longitude = (d - 25.0) * 60.0; "f" } - ((d >= 30.0) && (d <= 35.0)) || ((d >= -90.0) && (d <= -85.0)) -> { longitude = (d - 30.0) * 60.0; "g" } - ((d >= 35.0) && (d <= 40.0)) || ((d >= -85.0) && (d <= -80.0)) -> { longitude = (d - 35.0) * 60.0; "h" } - ((d >= 40.0) && (d <= 45.0)) || ((d >= -80.0) && (d <= -75.0)) -> { longitude = (d - 40.0) * 60.0; "i" } - ((d >= 45.0) && (d <= 50.0)) || ((d >= -75.0) && (d <= -70.0)) -> { longitude = (d - 45.0) * 60.0; "j" } - ((d >= 50.0) && (d <= 55.0)) || ((d >= -70.0) && (d <= -65.0)) -> { longitude = (d - 50.0) * 60.0; "k" } - ((d >= 55.0) && (d <= 60.0)) || ((d >= -65.0) && (d <= -60.0)) -> { longitude = (d - 55.0) * 60.0; "l" } - ((d >= 60.0) && (d <= 65.0)) || ((d >= -60.0) && (d <= -55.0)) -> { longitude = (d - 60.0) * 60.0; "m" } - ((d >= 65.0) && (d <= 70.0)) || ((d >= -55.0) && (d <= -50.0)) -> { longitude = (d - 65.0) * 60.0; "n" } - ((d >= 70.0) && (d <= 75.0)) || ((d >= -50.0) && (d <= -45.0)) -> { longitude = (d - 70.0) * 60.0; "o" } - ((d >= 75.0) && (d <= 80.0)) || ((d >= -45.0) && (d <= -40.0)) -> { longitude = (d - 75.0) * 60.0; "p" } - ((d >= 80.0) && (d <= 85.0)) || ((d >= -40.0) && (d <= -35.0)) -> { longitude = (d - 80.0) * 60.0; "q" } - ((d >= 85.0) && (d <= 90.0)) || ((d >= -35.0) && (d <= -30.0)) -> { longitude = (d - 85.0) * 60.0; "r" } - ((d >= 90.0) && (d <= 95.0)) || ((d >= -30.0) && (d <= -25.0)) -> { longitude = (d - 90.0) * 60.0; "s" } - ((d >= 95.0) && (d <= 100.0)) || ((d >= -25.0) && (d <= -20.0)) -> { longitude = (d - 95.0) * 60.0; "t" } - ((d >= 100.0) && (d <= 105.0)) || ((d >= -20.0) && (d <= -15.0)) -> { longitude = (d - 100.0) * 60.0; "u" } - ((d >= 105.0) && (d <= 110.0)) || ((d >= -15.0) && (d <= -10.0)) -> { longitude = (d - 105.0) * 60.0; "v" } - ((d >= 110.0) && (d <= 115.0)) || ((d >= -10.0) && (d <= -5.0)) -> { longitude = (d - 110.0) * 60.0; "w" } - ((d >= 115.0) && (d <= 120.0)) || ((d >= -5.0) && (d <= 0.0)) -> { longitude = (d - 115.0) * 60.0; "x" } - else -> { longitude = 0.0; " " } - } - val str4 = when { - ((longitude >= 0.0) && (longitude <= 30.0)) || ((longitude >= -300.0) && (longitude <= -270.0)) -> "0" - ((longitude >= 30.0) && (longitude <= 60.0)) || ((longitude >= -270.0) && (longitude <= -240.0)) -> "1" - ((longitude >= 60.0) && (longitude <= 90.0)) || ((longitude >= -240.0) && (longitude <= -210.0)) -> "2" - ((longitude >= 90.0) && (longitude <= 120.0)) || ((longitude >= -210.0) && (longitude <= -180.0)) -> "3" - ((longitude >= 120.0) && (longitude <= 150.0)) || ((longitude >= -180.0) && (longitude <= -150.0)) -> "4" - ((longitude >= 150.0) && (longitude <= 180.0)) || ((longitude >= -150.0) && (longitude <= -120.0)) -> "5" - ((longitude >= 180.0) && (longitude <= 210.0)) || ((longitude >= -120.0) && (longitude <= -90.0)) -> "6" - ((longitude >= 210.0) && (longitude <= 240.0)) || ((longitude >= -90.0) && (longitude <= -60.0)) -> "7" - ((longitude >= 240.0) && (longitude <= 270.0)) || ((longitude >= -60.0) && (longitude <= -30.0)) -> "8" - ((longitude >= 270.0) && (longitude <= 300.0)) || ((longitude >= -30.0) && (longitude <= 0.0)) -> "9" - else -> " " - } - return listOf(str, str2, str3, str4) -} - -// Ported lines 612-914: latitude -> (10 deg zone letter, 1 deg digit, 1' letter, 15" digit) -private fun encodeLat(inputLat: Double): List { - // Same closed-bound issue as encodeLon: nudge into the half-open cell so an - // exact boundary latitude does not fall back into the previous field. - var latitude = if (inputLat < 90.0) inputLat + 1e-10 else 90.0 - 1e-10 - var d2 = 0.0 - var d3 = 0.0 - val str5 = when { - (latitude >= -90.0) && (latitude <= -80.0) -> { d2 = latitude + 90.0; "A" } - (latitude >= -80.0) && (latitude <= -70.0) -> { d2 = latitude + 80.0; "B" } - (latitude >= -70.0) && (latitude <= -60.0) -> { d2 = latitude + 70.0; "C" } - (latitude >= -60.0) && (latitude <= -50.0) -> { d2 = latitude + 60.0; "D" } - (latitude >= -50.0) && (latitude <= -40.0) -> { d2 = latitude + 50.0; "E" } - (latitude >= -40.0) && (latitude <= -30.0) -> { d2 = latitude + 40.0; "F" } - (latitude >= -30.0) && (latitude <= -20.0) -> { d2 = latitude + 30.0; "G" } - (latitude >= -20.0) && (latitude <= -10.0) -> { d2 = latitude + 20.0; "H" } - (latitude >= -10.0) && (latitude <= 0.0) -> { d2 = latitude + 10.0; "I" } - (latitude >= 0.0) && (latitude <= 10.0) -> { d2 = latitude; "J" } - (latitude >= 10.0) && (latitude <= 20.0) -> { d2 = latitude - 10.0; "K" } - (latitude >= 20.0) && (latitude <= 30.0) -> { d2 = latitude - 20.0; "L" } - (latitude >= 30.0) && (latitude <= 40.0) -> { d2 = latitude - 30.0; "M" } - (latitude >= 40.0) && (latitude <= 50.0) -> { d2 = latitude - 40.0; "N" } - (latitude >= 50.0) && (latitude <= 60.0) -> { d2 = latitude - 50.0; "O" } - (latitude >= 60.0) && (latitude <= 70.0) -> { d2 = latitude - 60.0; "P" } - (latitude >= 70.0) && (latitude <= 80.0) -> { d2 = latitude - 70.0; "Q" } - (latitude >= 80.0) && (latitude <= 90.0) -> { d2 = latitude - 80.0; "R" } - else -> { d2 = 0.0; " " } - } - var str6 = " " - when { - ((d2 >= 0.0) && (d2 <= 1.0)) || ((d2 >= -10.0) && (d2 <= -9.0)) -> { d3 = d2 * 60.0; str6 = "0" } - ((d2 >= 1.0) && (d2 <= 2.0)) || ((d2 >= -9.0) && (d2 <= -8.0)) -> { d3 = (d2 - 1.0) * 60.0; str6 = "1" } - ((d2 >= 2.0) && (d2 <= 3.0)) || ((d2 >= -8.0) && (d2 <= -7.0)) -> { d3 = (d2 - 2.0) * 60.0; str6 = "2" } - ((d2 >= 3.0) && (d2 <= 4.0)) || ((d2 >= -7.0) && (d2 <= -6.0)) -> { d3 = (d2 - 3.0) * 60.0; str6 = "3" } - ((d2 >= 4.0) && (d2 <= 5.0)) || ((d2 >= -6.0) && (d2 <= -5.0)) -> { d3 = (d2 - 4.0) * 60.0; str6 = "4" } - ((d2 >= 5.0) && (d2 <= 6.0)) || ((d2 >= -5.0) && (d2 <= -4.0)) -> { d3 = (d2 - 5.0) * 60.0; str6 = "5" } - ((d2 >= 6.0) && (d2 <= 7.0)) || ((d2 >= -4.0) && (d2 <= -3.0)) -> { d3 = (d2 - 6.0) * 60.0; str6 = "6" } - ((d2 >= 7.0) && (d2 <= 8.0)) || ((d2 >= -3.0) && (d2 <= -2.0)) -> { d3 = (d2 - 7.0) * 60.0; str6 = "7" } - ((d2 >= 8.0) && (d2 <= 9.0)) || ((d2 >= -2.0) && (d2 <= -1.0)) -> { d3 = (d2 - 8.0) * 60.0; str6 = "8" } - ((d2 >= 9.0) && (d2 <= 10.0)) || ((d2 >= -1.0) && (d2 <= 0.0)) -> { d3 = (d2 - 9.0) * 60.0; str6 = "9" } - } - val str38 = when { - ((d3 >= 0.0) && (d3 <= 2.5)) || ((d3 >= -60.0) && (d3 <= -57.5)) -> { d2 = d3 * 60.0; "a" } - ((d3 >= 2.5) && (d3 <= 5.0)) || ((d3 >= -57.5) && (d3 <= -55.0)) -> { d2 = (d3 - 2.5) * 60.0; "b" } - ((d3 >= 5.0) && (d3 <= 7.5)) || ((d3 >= -55.0) && (d3 <= -52.5)) -> { d2 = (d3 - 5.0) * 60.0; "c" } - ((d3 >= 7.5) && (d3 <= 10.0)) || ((d3 >= -52.5) && (d3 <= -50.0)) -> { d2 = (d3 - 7.5) * 60.0; "d" } - ((d3 >= 10.0) && (d3 <= 12.5)) || ((d3 >= -50.0) && (d3 <= -47.5)) -> { d2 = (d3 - 10.0) * 60.0; "e" } - ((d3 >= 12.5) && (d3 <= 15.0)) || ((d3 >= -47.5) && (d3 <= -45.0)) -> { d2 = (d3 - 12.5) * 60.0; "f" } - ((d3 >= 15.0) && (d3 <= 17.5)) || ((d3 >= -45.0) && (d3 <= -42.5)) -> { d2 = (d3 - 15.0) * 60.0; "g" } - ((d3 >= 17.5) && (d3 <= 20.0)) || ((d3 >= -42.5) && (d3 <= -40.0)) -> { d2 = (d3 - 17.5) * 60.0; "h" } - ((d3 >= 20.0) && (d3 <= 22.5)) || ((d3 >= -40.0) && (d3 <= -37.5)) -> { d2 = (d3 - 20.0) * 60.0; "i" } - ((d3 >= 22.5) && (d3 <= 25.0)) || ((d3 >= -37.5) && (d3 <= -35.0)) -> { d2 = (d3 - 22.5) * 60.0; "j" } - ((d3 >= 25.0) && (d3 <= 27.5)) || ((d3 >= -35.0) && (d3 <= -32.5)) -> { d2 = (d3 - 25.0) * 60.0; "k" } - ((d3 >= 27.5) && (d3 <= 30.0)) || ((d3 >= -32.5) && (d3 <= -30.0)) -> { d2 = (d3 - 27.5) * 60.0; "l" } - ((d3 >= 30.0) && (d3 <= 32.5)) || ((d3 >= -30.0) && (d3 <= -27.5)) -> { d2 = (d3 - 30.0) * 60.0; "m" } - ((d3 >= 32.5) && (d3 <= 35.0)) || ((d3 >= -27.5) && (d3 <= -25.0)) -> { d2 = (d3 - 32.5) * 60.0; "n" } - ((d3 >= 35.0) && (d3 <= 37.5)) || ((d3 >= -25.0) && (d3 <= -22.5)) -> { d2 = (d3 - 35.0) * 60.0; "o" } - ((d3 >= 37.5) && (d3 <= 40.0)) || ((d3 >= -22.5) && (d3 <= -20.0)) -> { d2 = (d3 - 37.5) * 60.0; "p" } - ((d3 >= 40.0) && (d3 <= 42.5)) || ((d3 >= -20.0) && (d3 <= -17.5)) -> { d2 = (d3 - 40.0) * 60.0; "q" } - ((d3 >= 42.5) && (d3 <= 45.0)) || ((d3 >= -17.5) && (d3 <= -15.0)) -> { d2 = (d3 - 42.5) * 60.0; "r" } - ((d3 >= 45.0) && (d3 <= 47.5)) || ((d3 >= -15.0) && (d3 <= -12.5)) -> { d2 = (d3 - 45.0) * 60.0; "s" } - ((d3 >= 47.5) && (d3 <= 50.0)) || ((d3 >= -12.5) && (d3 <= -10.0)) -> { d2 = (d3 - 47.5) * 60.0; "t" } - ((d3 >= 50.0) && (d3 <= 52.5)) || ((d3 >= -10.0) && (d3 <= -7.5)) -> { d2 = (d3 - 50.0) * 60.0; "u" } - ((d3 >= 52.5) && (d3 <= 55.0)) || ((d3 >= -7.5) && (d3 <= -5.0)) -> { d2 = (d3 - 52.5) * 60.0; "v" } - ((d3 >= 55.0) && (d3 <= 57.5)) || ((d3 >= -5.0) && (d3 <= -2.5)) -> { d2 = (d3 - 55.0) * 60.0; "w" } - ((d3 >= 57.5) && (d3 <= 60.0)) || ((d3 >= -2.5) && (d3 <= 0.0)) -> { d2 = (d3 - 57.5) * 60.0; "x" } - else -> { d2 = 0.0; " " } - } - val str9 = when { - ((d2 >= 0.0) && (d2 <= 15.0)) || ((d2 >= -150.0) && (d2 <= -135.0)) -> "0" - ((d2 >= 15.0) && (d2 <= 30.0)) || ((d2 >= -135.0) && (d2 <= -120.0)) -> "1" - ((d2 >= 30.0) && (d2 <= 45.0)) || ((d2 >= -120.0) && (d2 <= -105.0)) -> "2" - ((d2 >= 45.0) && (d2 <= 60.0)) || ((d2 >= -105.0) && (d2 <= -90.0)) -> "3" - ((d2 >= 60.0) && (d2 <= 75.0)) || ((d2 >= -90.0) && (d2 <= -75.0)) -> "4" - ((d2 >= 75.0) && (d2 <= 90.0)) || ((d2 >= -75.0) && (d2 <= -60.0)) -> "5" - ((d2 >= 90.0) && (d2 <= 105.0)) || ((d2 >= -60.0) && (d2 <= -45.0)) -> "6" - ((d2 >= 105.0) && (d2 <= 120.0)) || ((d2 >= -45.0) && (d2 <= -30.0)) -> "7" - ((d2 >= 120.0) && (d2 <= 135.0)) || ((d2 >= -30.0) && (d2 <= -15.0)) -> "8" - ((d2 >= 135.0) && (d2 <= 150.0)) || ((d2 >= -15.0) && (d2 <= 0.0)) -> "9" - else -> " " - } - return listOf(str5, str6, str38, str9) -} -