From 1a3f1bb34c6a87dc57e3189d6481ba733403eb09 Mon Sep 17 00:00:00 2001 From: atsunatsu Date: Mon, 3 Aug 2026 01:53:37 +0800 Subject: [PATCH] fix: radar track rendering bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Elevation ring labels were inverted (90/60/30 from outer to inner); now 30/60/90 correctly from outer ring to center - Split track paths at the 0/360° azimuth wrap in both MutualRadarView and main RadarView, so passes crossing due north no longer draw a line straight across the plot --- .../feature/mutual/MutualRadarView.kt | 31 +++++++++++++------ .../look4sat/feature/radar/RadarView.kt | 16 +++++++++- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualRadarView.kt b/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualRadarView.kt index a5ae3248..ba9c770a 100644 --- a/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualRadarView.kt +++ b/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualRadarView.kt @@ -103,29 +103,35 @@ fun MutualRadarView( // Grid: elevation rings + azimuth spokes + cardinal labels drawRadarGrid(center, radius, gridColor, textColor, measurer) - // Track paths (only the above-horizon arc) + // Track paths (only the above-horizon arc, split at the 0/360° azimuth wrap) val pathA = Path() val pathB = Path() var firstVisibleA = true var firstVisibleB = true + var lastAzimA: Double? = null + var lastAzimB: Double? = null trackSamples.forEach { sample -> if (sample.elevationA > 0f) { val p = sph2Cart(center, sample.azimuthA, sample.elevationA, radius) - if (firstVisibleA) { + val wrapA = lastAzimA != null && kotlin.math.abs(azimuthDelta(sample.azimuthA - lastAzimA!!)) > 180.0 + if (firstVisibleA || wrapA) { pathA.moveTo(p.x, p.y) firstVisibleA = false } else { pathA.lineTo(p.x, p.y) } + lastAzimA = sample.azimuthA } if (sample.elevationB > 0f) { val p = sph2Cart(center, sample.azimuthB, sample.elevationB, radius) - if (firstVisibleB) { + val wrapB = lastAzimB != null && kotlin.math.abs(azimuthDelta(sample.azimuthB - lastAzimB!!)) > 180.0 + if (firstVisibleB || wrapB) { pathB.moveTo(p.x, p.y) firstVisibleB = false } else { pathB.lineTo(p.x, p.y) } + lastAzimB = sample.azimuthB } } @@ -198,13 +204,12 @@ private fun DrawScope.drawRadarGrid( Offset(center.x - diag, center.y + diag), Offset(center.x + diag, center.y - diag), 1f ) - // Elevation ring labels + // Elevation ring labels: 30° on the outer ring, 60° on the middle ring, 90° at center + // (outer edge is the 0° horizon). Labels sit just above their ring. val style = TextStyle(color = textColor, fontSize = 11.sp) - for (i in 0 until CIRCLES) { - val deg = 30 * (CIRCLES - i) - val y = (center.y - (radius - step * i)) - 24f - drawText(measurer, "$deg°", Offset(center.x + 6f, y), style = style) - } + drawText(measurer, "30°", Offset(center.x + 6f, (center.y - (radius - step)) - 24f), style = style) + drawText(measurer, "60°", Offset(center.x + 6f, (center.y - (radius - 2 * step)) - 24f), style = style) + drawText(measurer, "90°", Offset(center.x + 6f, center.y - 18f), style = style) // Cardinal labels drawText(measurer, "N", Offset(center.x - 8f, center.y - radius - 20f), style = style) drawText(measurer, "E", Offset(center.x + radius + 4f, center.y - 10f), style = style) @@ -212,6 +217,14 @@ private fun DrawScope.drawRadarGrid( drawText(measurer, "W", Offset(center.x - radius - 24f, center.y - 10f), style = style) } +/** Normalize an azimuth delta (degrees) into the [-180, 180] range. */ +private fun azimuthDelta(deltaDeg: Double): Double { + var d = deltaDeg % 360.0 + if (d > 180.0) d -= 360.0 + if (d < -180.0) d += 360.0 + return d +} + /** Convert azimuth (deg, 0=N, clockwise) and elevation (deg) to canvas offset. */ private fun sph2Cart(center: Offset, azimDeg: Double, elevDeg: Double, r: Float): Offset { val azimRad = azimDeg * PI / 180.0 diff --git a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarView.kt b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarView.kt index cf5b3dcd..efdc0df0 100644 --- a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarView.kt +++ b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarView.kt @@ -60,6 +60,8 @@ import com.rtbishop.look4sat.core.domain.predict.OrbitalPos import com.rtbishop.look4sat.core.domain.predict.PI_2 import com.rtbishop.look4sat.core.domain.utility.toRadians import com.rtbishop.look4sat.core.presentation.R +import kotlin.math.PI +import kotlin.math.abs import kotlin.math.cos import kotlin.math.sin @@ -226,13 +228,25 @@ private fun DrawScope.drawSweep(center: Offset, degrees: Float, radius: Float, b private fun createTrackPath(positions: List, radius: Float): Path { val trackPath = Path() + var lastAzim: Double? = null positions.forEachIndexed { index, pos -> val offset = sph2Cart(pos.azimuth, pos.elevation, radius.toDouble()) - if (index == 0) trackPath.moveTo(offset.x, offset.y) else trackPath.lineTo(offset.x, offset.y) + // Split the path when azimuth wraps 0°/360° to avoid a line across the plot + val wrap = lastAzim != null && abs(azimuthDeltaRad(pos.azimuth - lastAzim!!)) > PI + if (index == 0 || wrap) trackPath.moveTo(offset.x, offset.y) else trackPath.lineTo(offset.x, offset.y) + lastAzim = pos.azimuth } return trackPath } +/** Normalize an azimuth delta (radians) into the [-PI, PI] range. */ +private fun azimuthDeltaRad(deltaRad: Double): Double { + var d = deltaRad % (2 * PI) + if (d > PI) d -= 2 * PI + if (d < -PI) d += 2 * PI + return d +} + private fun createTrackEffect(trackPath: Path): PathEffect { val shapeRadius = 24f val angle = 120.0.toRadians()