From 571145838daa9c84862323f9088f9758480069b6 Mon Sep 17 00:00:00 2001 From: atsunatsu Date: Mon, 3 Aug 2026 03:27:15 +0800 Subject: [PATCH] fix: gridToLatLon normalization bug + bidirectional grid/latlon sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: gridToLatLon used (lon+180)%360-180 and (lat+90)%180-90 for normalization, treating grid values as from prime meridian/equator when they are actually from IDL/South Pole. A 6-char grid like OL62AA (112°E, 22°N) was returning -67.96°, -67.98° (Atlantic Ocean). Also added bidirectional sync: entering a grid auto-fills lat/lon, entering lat/lon auto-fills the 6-char grid, so users can verify. --- .../feature/mutual/MutualViewModel.kt | 85 ++++++++++++++++--- 1 file changed, 75 insertions(+), 10 deletions(-) diff --git a/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualViewModel.kt b/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualViewModel.kt index 404bb358..761fcc07 100644 --- a/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualViewModel.kt +++ b/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualViewModel.kt @@ -67,16 +67,77 @@ class MutualViewModel( val pos = settingsRepo.stationPosition.value val grid = latLonToGrid(pos.latitude, pos.longitude) val minElev = settingsRepo.passesSettings.value.minElevation - _uiState.update { it.copy(stationAGrid = grid, stationAMinElev = minElev, stationBMinElev = minElev) } + _uiState.update { it.copy( + stationAGrid = grid, + stationALat = "%.4f".format(pos.latitude), + stationALon = "%.4f".format(pos.longitude), + stationAMinElev = minElev, + stationBMinElev = minElev + ) } } - fun onStationALat(value: String) = _uiState.update { it.copy(stationALat = value) } - fun onStationALon(value: String) = _uiState.update { it.copy(stationALon = value) } - fun onStationAGrid(value: String) = _uiState.update { it.copy(stationAGrid = value) } + fun onStationALat(value: String) { + _uiState.update { it.copy(stationALat = value) } + val lat = value.toDoubleOrNull() + val lon = _uiState.value.stationALon.toDoubleOrNull() + if (lat != null && lon != null) { + _uiState.update { it.copy(stationAGrid = latLonToGrid(lat, lon)) } + } + } + + fun onStationALon(value: String) { + _uiState.update { it.copy(stationALon = value) } + val lat = _uiState.value.stationALat.toDoubleOrNull() + val lon = value.toDoubleOrNull() + if (lat != null && lon != null) { + _uiState.update { it.copy(stationAGrid = latLonToGrid(lat, lon)) } + } + } + + fun onStationAGrid(value: String) { + val old = _uiState.value.stationAGrid + _uiState.update { it.copy(stationAGrid = value) } + if (value.trim().uppercase() == old.trim().uppercase()) return + val pos = gridToLatLon(value.trim().uppercase()) + if (pos != null) { + _uiState.update { it.copy( + stationALat = "%.4f".format(pos.latitude), + stationALon = "%.4f".format(pos.longitude) + )} + } + } + + fun onStationBLat(value: String) { + _uiState.update { it.copy(stationBLat = value) } + val lat = value.toDoubleOrNull() + val lon = _uiState.value.stationBLon.toDoubleOrNull() + if (lat != null && lon != null) { + _uiState.update { it.copy(stationBGrid = latLonToGrid(lat, lon)) } + } + } + + fun onStationBLon(value: String) { + _uiState.update { it.copy(stationBLon = value) } + val lat = _uiState.value.stationBLat.toDoubleOrNull() + val lon = value.toDoubleOrNull() + if (lat != null && lon != null) { + _uiState.update { it.copy(stationBGrid = latLonToGrid(lat, lon)) } + } + } + + fun onStationBGrid(value: String) { + val old = _uiState.value.stationBGrid + _uiState.update { it.copy(stationBGrid = value) } + if (value.trim().uppercase() == old.trim().uppercase()) return + val pos = gridToLatLon(value.trim().uppercase()) + if (pos != null) { + _uiState.update { it.copy( + stationBLat = "%.4f".format(pos.latitude), + stationBLon = "%.4f".format(pos.longitude) + )} + } + } fun onStationAMinElev(value: Double) = _uiState.update { it.copy(stationAMinElev = value) } - fun onStationBLat(value: String) = _uiState.update { it.copy(stationBLat = value) } - fun onStationBLon(value: String) = _uiState.update { it.copy(stationBLon = value) } - fun onStationBGrid(value: String) = _uiState.update { it.copy(stationBGrid = value) } fun onStationBMinElev(value: Double) = _uiState.update { it.copy(stationBMinElev = value) } fun onHoursAhead(value: Int) = _uiState.update { it.copy(hoursAhead = value) } fun onSelectPass(index: Int) = _uiState.update { it.copy(selectedPassIndex = index) } @@ -86,7 +147,7 @@ class MutualViewModel( // Resolve positions from lat/lon or grid var posA = resolvePosition(state.stationALat, state.stationALon, state.stationAGrid) - val posB = resolvePosition(state.stationBLat, state.stationBLon, state.stationBGrid) + var posB = resolvePosition(state.stationBLat, state.stationBLon, state.stationBGrid) if (posA == null || posB == null) { _uiState.update { it.copy(errorMessage = "请输入有效的位置坐标或网格(4/6/8位)") } @@ -101,6 +162,10 @@ class MutualViewModel( if (state.stationAGrid.trim().uppercase() == prefillGrid) { posA = stationPos } + // Same for station B: if its grid matches the user's station, use exact position. + if (state.stationBGrid.trim().uppercase() == prefillGrid) { + posB = stationPos + } val satellites = satelliteRepo.satellites.value if (satellites.isEmpty()) { @@ -217,8 +282,8 @@ class MutualViewModel( lat += 0.5 } - lon = (lon + 180.0) % 360.0 - 180.0 - lat = (lat + 90.0) % 180.0 - 90.0 + lon = lon - 180.0 + lat = lat - 90.0 return GeoPos(lat, lon) }