fix(roaming): derive the 3x3 grid from qthNeighbors
The decompiled per-edge branches computing the surrounding nine squares had two
independent defects.
1. Field letters stepped past the alphabet
Every branch moved a field with raw character arithmetic (`str[0] - 1`,
`str5[0] + 1`) and Maidenhead fields only run A..R, so coordinates near the
edges of the world produced squares outside the alphabet:
(-89.9, -179.9) -> [@A91, AA01, AA11, @A90, AA00, @A10, @@99, A@09, A@19]
( 89.9, 179.9) -> [RS80, RS90, SS00, RR89, RR99, SR09, RR88, RR98, SR08]
2. Some moved cells kept the old field letter
The north-edge branch advanced the latitude field for the top-centre cell only,
leaving the two top corners in the previous field:
centre AA19 -> ported [AA00, AB10, AA20, ...]
correct [AB00, AB10, AB20, ...]
Cross-checked against the shared qthNeighbors helper, which is already covered
by QthConverterTest including the AA00 and RR99 wrap cases:
before: 64,800 sampled coordinates, 6,480 disagreed (all with centre square
digits 00 or x9, i.e. the north edge and the 00 corner)
after: 64,800 sampled coordinates, 0 disagree
The ring is plain Maidenhead arithmetic with no QTH-Locator-specific behaviour,
so call qthNeighbors instead of keeping a second, wrong implementation. The
now-unreferenced buildGrids branches are removed (grep confirmed the definition
was the only remaining occurrence). The existing OL42 reference grid and the
four ported edge-case tests still pass unchanged.
Reverting the fix fails both new regression tests; with it
:feature:roaming:testDebugUnitTest and :core:domain:test are green.
This commit is contained in:
1 parent
fed9fe188e
commit
2da7127fd3
2 files changed
+47
-187
No files matched your search
+9
-187
@@ -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.qthNeighbors
|
||||
|
||||
/**
|
||||
* Roaming page - ported verbatim from QTH Locator 2.0 (com.us1pm.gridsquarelocator).
|
||||
@@ -407,8 +408,14 @@ fun roamingStateFromLocation(lat: Double, lon: Double, fixTime: Long, hourPrefix
|
||||
val (str, str2, str3, str4) = encodeLon(lon)
|
||||
val (str5, str6, str38, str9) = encodeLat(lat)
|
||||
val loc = str + str5 + str2 + str6 + str3 + str38 + str4 + str9
|
||||
// Ported: 3x3 parseInt3 five-branch logic
|
||||
val grids = buildGrids(str, str5, str2, str6, str3, str38)
|
||||
// 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
|
||||
// advanced the latitude field for the top-centre cell but not for the two
|
||||
// top corners) and stepped letters past A/R at the edges of the world,
|
||||
// producing squares such as "@A91" or "SS00". Measured against qthNeighbors
|
||||
// over 64,800 sampled coordinates, 6,480 of them disagreed.
|
||||
val grids = qthNeighbors(str + str5 + str2 + str6)
|
||||
// Ported: red-dot lookup (3rd char pair)
|
||||
val markerLeft = lonMargin[str3.firstOrNull()] ?: 0
|
||||
val markerTop = latMargin[str38.firstOrNull()] ?: 0
|
||||
@@ -617,188 +624,3 @@ private fun encodeLat(inputLat: Double): List<String> {
|
||||
return listOf(str5, str6, str38, str9)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ported lines 917-1283: 3x3 parseInt3 five-branch (interior / west / east / north / south edges + corners).
|
||||
* Returns 9 cell texts: [top-left, top-center, top-right, mid-left, center, mid-right, bottom-left, bottom-center, bottom-right].
|
||||
*/
|
||||
private fun buildGrids(str: String, str5: String, str2: String, str6: String, str3: String, str38: String): List<String> {
|
||||
val parseInt = str2.toInt()
|
||||
val parseInt2 = str6.toInt()
|
||||
val str39 = str + str5
|
||||
val str40 = str2 + str6
|
||||
val parseInt3 = str40.toInt()
|
||||
val str41 = str39 + str40
|
||||
val g = MutableList(9) { "" }
|
||||
fun fmt2(n: Int) = if (n <= 9) "0$n" else n.toString()
|
||||
if ((parseInt > 0) && (parseInt < 9) && (parseInt2 > 0) && (parseInt2 < 9)) {
|
||||
// Interior: digit pairs +-1
|
||||
val i7 = parseInt3 + 1
|
||||
val i8 = i7 - 10
|
||||
val valueOf9 = fmt2(i8)
|
||||
val str11 = i7.toString()
|
||||
val valueOf18 = (i7 + 10).toString()
|
||||
val i9 = parseInt3 - 10
|
||||
val valueOf10 = fmt2(i9)
|
||||
val valueOf19 = (parseInt3 + 10).toString()
|
||||
val i10 = parseInt3 - 1
|
||||
val i11 = i10 - 10
|
||||
val valueOf11 = fmt2(i11)
|
||||
val str14 = i10.toString()
|
||||
val str15 = (i10 + 10).toString()
|
||||
g[0] = str39 + valueOf9
|
||||
g[1] = str39 + str11
|
||||
g[2] = str39 + valueOf18
|
||||
g[3] = str39 + valueOf10
|
||||
g[4] = str41
|
||||
g[5] = str39 + valueOf19
|
||||
g[6] = str39 + valueOf11
|
||||
g[7] = str39 + str14
|
||||
g[8] = str39 + str15
|
||||
} else if ((parseInt == 0) && (parseInt2 > 0) && (parseInt2 < 9)) {
|
||||
// West edge: lon digit 0 -> 9, lon letter -1
|
||||
val str43 = (str[0] - 1).toString() + str5
|
||||
val str44 = "9" + (parseInt2 + 1)
|
||||
val i12 = parseInt3 + 1
|
||||
val valueOf7 = fmt2(i12)
|
||||
val str12 = (i12 + 10).toString()
|
||||
val sb4 = "9" + parseInt2
|
||||
val valueOf20 = (parseInt3 + 10).toString()
|
||||
val str46 = "9" + (parseInt2 - 1)
|
||||
val i13 = parseInt3 - 1
|
||||
val valueOf8 = fmt2(i13)
|
||||
val str15 = (i13 + 10).toString()
|
||||
g[0] = str43 + str44
|
||||
g[1] = str39 + valueOf7
|
||||
g[2] = str39 + str12
|
||||
g[3] = str43 + sb4
|
||||
g[4] = str41
|
||||
g[5] = str39 + valueOf20
|
||||
g[6] = str43 + str46
|
||||
g[7] = str39 + valueOf8
|
||||
g[8] = str39 + str15
|
||||
} else if ((parseInt == 9) && (parseInt2 > 0) && (parseInt2 < 9)) {
|
||||
// East edge: lon digit 9 -> 0, lon letter +1
|
||||
val str47 = (str[0] + 1).toString() + str5
|
||||
val i14 = parseInt3 + 1
|
||||
val valueOf21 = (i14 - 10).toString()
|
||||
val valueOf22 = i14.toString()
|
||||
val str48 = "0" + (parseInt2 + 1)
|
||||
val valueOf23 = (parseInt3 - 10).toString()
|
||||
val str49 = "0" + parseInt2
|
||||
val i15 = parseInt3 - 1
|
||||
val valueOf24 = (i15 - 10).toString()
|
||||
val valueOf25 = i15.toString()
|
||||
val str15 = "0" + (parseInt2 - 1)
|
||||
g[0] = str39 + valueOf21
|
||||
g[1] = str39 + valueOf22
|
||||
g[2] = str47 + str48
|
||||
g[3] = str39 + valueOf23
|
||||
g[4] = str41
|
||||
g[5] = str47 + str49
|
||||
g[6] = str39 + valueOf24
|
||||
g[7] = str39 + valueOf25
|
||||
g[8] = str47 + str15
|
||||
} else if ((parseInt < 9) && (parseInt > 0) && (parseInt2 == 9)) {
|
||||
// North edge: lat digit 9 -> 0, lat letter +1
|
||||
val str50 = str + (str5[0] + 1).toString()
|
||||
val i16 = parseInt3 - 9
|
||||
val i17 = i16 - 10
|
||||
val valueOf4 = fmt2(i17)
|
||||
val valueOf26 = i16.toString()
|
||||
val valueOf27 = (i16 + 10).toString()
|
||||
val i18 = parseInt3 - 10
|
||||
val valueOf5 = fmt2(i18)
|
||||
val valueOf28 = (parseInt3 + 10).toString()
|
||||
val i19 = parseInt3 - 1
|
||||
val i20 = i19 - 10
|
||||
val valueOf6 = fmt2(i20)
|
||||
val str14 = i19.toString()
|
||||
val str15 = (i19 + 10).toString()
|
||||
g[0] = str39 + valueOf4
|
||||
g[1] = str50 + valueOf26
|
||||
g[2] = str39 + valueOf27
|
||||
g[3] = str39 + valueOf5
|
||||
g[4] = str41
|
||||
g[5] = str39 + valueOf28
|
||||
g[6] = str39 + valueOf6
|
||||
g[7] = str39 + str14
|
||||
g[8] = str39 + str15
|
||||
} else if ((parseInt < 9) && (parseInt > 0) && (parseInt2 == 0)) {
|
||||
// South edge: lat digit 0 -> 9, lat letter -1
|
||||
val str52 = str + (str5[0] - 1).toString()
|
||||
val i21 = parseInt3 + 1
|
||||
val i22 = i21 - 10
|
||||
val valueOf = fmt2(i22)
|
||||
val valueOf29 = i21.toString()
|
||||
val valueOf30 = (i21 + 10).toString()
|
||||
val i23 = parseInt3 - 10
|
||||
val valueOf2 = fmt2(i23)
|
||||
val valueOf31 = (parseInt3 + 10).toString()
|
||||
val i24 = parseInt3 - 1
|
||||
val valueOf3 = fmt2(i24)
|
||||
val str14 = (i24 + 10).toString()
|
||||
val str15 = (i24 + 20).toString()
|
||||
g[0] = str39 + valueOf
|
||||
g[1] = str39 + valueOf29
|
||||
g[2] = str39 + valueOf30
|
||||
g[3] = str39 + valueOf2
|
||||
g[4] = str41
|
||||
g[5] = str39 + valueOf31
|
||||
g[6] = str52 + valueOf3
|
||||
g[7] = str52 + str14
|
||||
g[8] = str52 + str15
|
||||
} else if (parseInt3 == 0) {
|
||||
// Corner 00: both digits -1 with carry
|
||||
val v32 = (str[0] - 1).toString()
|
||||
val v33 = (str5[0] - 1).toString()
|
||||
g[0] = v32 + str5 + "91"
|
||||
g[1] = str39 + "01"
|
||||
g[2] = str39 + "11"
|
||||
g[3] = v32 + str5 + "90"
|
||||
g[4] = str41
|
||||
g[5] = v32 + str5 + "10"
|
||||
g[6] = v32 + v33 + "99"
|
||||
g[7] = str + v33 + "09"
|
||||
g[8] = str + v33 + "19"
|
||||
} else if (parseInt3 == 9) {
|
||||
// Corner 09: lon -1, lat +1
|
||||
val v34 = (str[0] - 1).toString()
|
||||
val v35 = (str5[0] + 1).toString()
|
||||
g[0] = v34 + v35 + "90"
|
||||
g[1] = str + v35 + "00"
|
||||
g[2] = str + v35 + "10"
|
||||
g[3] = v34 + str5 + "99"
|
||||
g[4] = str41
|
||||
g[5] = v34 + str5 + "19"
|
||||
g[6] = v34 + str5 + "98"
|
||||
g[7] = v34 + str5 + "08"
|
||||
g[8] = v34 + str5 + "18"
|
||||
} else if (parseInt3 == 99) {
|
||||
// Corner 99: both digits +1 with carry
|
||||
val v36 = (str[0] + 1).toString()
|
||||
val v37 = (str5[0] + 1).toString()
|
||||
g[0] = str + v37 + "80"
|
||||
g[1] = str + v37 + "90"
|
||||
g[2] = v36 + v37 + "00"
|
||||
g[3] = str39 + "89"
|
||||
g[4] = str41
|
||||
g[5] = v36 + str5 + "09"
|
||||
g[6] = str39 + "88"
|
||||
g[7] = str39 + "98"
|
||||
g[8] = v36 + str5 + "08"
|
||||
} else if (parseInt3 == 90) {
|
||||
// Corner 90: lon +1, lat -1
|
||||
val v38 = (str[0] + 1).toString()
|
||||
val v39 = (str5[0] - 1).toString()
|
||||
g[0] = str39 + "81"
|
||||
g[1] = str39 + "91"
|
||||
g[2] = v38 + str5 + "01"
|
||||
g[3] = str39 + "80"
|
||||
g[4] = str41
|
||||
g[5] = v38 + str5 + "00"
|
||||
g[6] = str + v39 + "89"
|
||||
g[7] = str + v39 + "99"
|
||||
g[8] = v38 + v39 + "09"
|
||||
}
|
||||
return g
|
||||
}
|
||||
+38
@@ -141,6 +141,44 @@ class RoamingStateTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `neighbour grid stays inside the A to R alphabet worldwide`() {
|
||||
// The ported edge branches stepped field letters with raw character
|
||||
// arithmetic and produced cells like "@A91" (A-1) or "SS00" (R+1).
|
||||
var lat = -89.5
|
||||
while (lat <= 89.5) {
|
||||
var lon = -179.5
|
||||
while (lon <= 179.5) {
|
||||
stateOf(lat, lon).grids.forEach { cell ->
|
||||
assertEquals("cell length at ($lat, $lon): $cell", 4, cell.length)
|
||||
assert(cell[0] in 'A'..'R' && cell[1] in 'A'..'R') {
|
||||
"field letters out of range at ($lat, $lon): $cell"
|
||||
}
|
||||
assert(cell[2].isDigit() && cell[3].isDigit()) {
|
||||
"square digits out of range at ($lat, $lon): $cell"
|
||||
}
|
||||
}
|
||||
lon += 7.0
|
||||
}
|
||||
lat += 7.0
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `neighbour grid carries the field letter on every moved cell`() {
|
||||
// North edge: latitude square 9 -> 0 must advance the latitude field for
|
||||
// all three cells of the top row, not just the middle one.
|
||||
assertEquals(
|
||||
listOf("AB00", "AB10", "AB20", "AA09", "AA19", "AA29", "AA08", "AA18", "AA28"),
|
||||
stateOf(-80.5, -177.5).grids
|
||||
)
|
||||
// South-west corner of the world wraps in both axes.
|
||||
assertEquals(
|
||||
listOf("RA91", "AA01", "AA11", "RA90", "AA00", "AA10", "RR99", "AR09", "AR19"),
|
||||
stateOf(-89.5, -179.5).grids
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `gps state after fix matches showLocation`() {
|
||||
val state = stateOf(22.314066, 108.706575)
|
||||
|
||||
Reference in new issue
Block a user