mirror of
https://github.com/rt-bishop/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Added PassInfoView.kt radar sweep animation
This commit is contained in:
1 parent
187791f895
commit
5460ed2a6c
2 files changed
+167
-85
No files matched your search
+9
-2
@@ -25,17 +25,21 @@ import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.data.PreferencesSource
|
||||
import com.rtbishop.look4sat.databinding.FragmentPolarBinding
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.SatPass
|
||||
import com.rtbishop.look4sat.utility.RecyclerDivider
|
||||
import com.rtbishop.look4sat.utility.toTimerString
|
||||
import com.rtbishop.look4sat.utility.navigateSafe
|
||||
import com.rtbishop.look4sat.utility.toTimerString
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class PassInfoFragment : Fragment(R.layout.fragment_polar) {
|
||||
|
||||
@Inject
|
||||
lateinit var preferencesSource: PreferencesSource
|
||||
private val viewModel: PassInfoViewModel by viewModels()
|
||||
private var passInfoView: PassInfoView? = null
|
||||
|
||||
@@ -69,7 +73,10 @@ class PassInfoFragment : Fragment(R.layout.fragment_polar) {
|
||||
val catNum = requireArguments().getInt("catNum")
|
||||
val aosTime = requireArguments().getLong("aosTime")
|
||||
viewModel.getPass(catNum, aosTime).observe(viewLifecycleOwner) { pass ->
|
||||
passInfoView = PassInfoView(requireContext()).apply { setPass(pass) }
|
||||
passInfoView = PassInfoView(requireContext()).apply {
|
||||
setShowAim(preferencesSource.shouldUseCompass())
|
||||
setPass(pass)
|
||||
}
|
||||
binding.frame.addView(passInfoView)
|
||||
observeTransmitters(pass, satTransAdapter, binding)
|
||||
}
|
||||
|
||||
+158
-83
@@ -22,134 +22,151 @@ import android.graphics.*
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.data.PreferencesSource
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.SatPass
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.min
|
||||
import kotlin.math.sin
|
||||
|
||||
@AndroidEntryPoint
|
||||
class PassInfoView(context: Context) : View(context) {
|
||||
|
||||
@Inject lateinit var preferencesSource: PreferencesSource
|
||||
|
||||
private lateinit var satPass: SatPass
|
||||
private val defaultColor = ContextCompat.getColor(context, R.color.themeLight)
|
||||
private val scale = resources.displayMetrics.density
|
||||
private val radarWidth = resources.displayMetrics.widthPixels
|
||||
private val radarCenter = radarWidth / 2f
|
||||
private val radarRadius = radarWidth * 0.48f
|
||||
private val piDiv2 = Math.PI / 2.0
|
||||
private val arrowPath = Path()
|
||||
private val satTrack: Path = Path()
|
||||
private val strokeSize = scale * 2f
|
||||
private val satSize = scale * 8f
|
||||
private val txtSize = scale * 16f
|
||||
private val radarPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = ContextCompat.getColor(context, R.color.greyLight)
|
||||
|
||||
private var radarColor = ContextCompat.getColor(context, R.color.greyLight)
|
||||
private var radarCircleNum = 3
|
||||
private var radarPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = radarColor
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = strokeSize
|
||||
}
|
||||
private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = ContextCompat.getColor(context, R.color.themeLight)
|
||||
textSize = txtSize
|
||||
|
||||
private var shouldShowSweep = true
|
||||
private var sweepColor = defaultColor
|
||||
private var sweepDegrees = 0f
|
||||
private var sweepSpeed = 30.0f
|
||||
private var sweepPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = sweepColor
|
||||
}
|
||||
private val trackPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
|
||||
private var shouldShowBeacons = true
|
||||
private var beaconColor = defaultColor
|
||||
private var beaconSize = scale * 7f
|
||||
private var beaconPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = beaconColor
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
|
||||
private var radarTextSize = scale * 16f
|
||||
private var radarTextColor = defaultColor
|
||||
private var radarTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = radarTextColor
|
||||
textSize = radarTextSize
|
||||
}
|
||||
|
||||
private var isTrackCreated = false
|
||||
private val trackPath: Path = Path()
|
||||
private var trackPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.RED
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = strokeSize
|
||||
}
|
||||
private val satPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = ContextCompat.getColor(context, R.color.themeLight)
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
private val arrowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
|
||||
private val arrowPath = Path()
|
||||
private var arrowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = ContextCompat.getColor(context, R.color.themeLight)
|
||||
style = Paint.Style.FILL
|
||||
strokeWidth = strokeSize
|
||||
}
|
||||
|
||||
private var shouldShowAim = true
|
||||
private var aimColor = Color.RED
|
||||
private var aimPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = aimColor
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = strokeSize
|
||||
}
|
||||
|
||||
private var shouldRotateRadar = true
|
||||
private var azimuth: Float = 0f
|
||||
private var pitch: Float = 0f
|
||||
private var roll: Float = 0f
|
||||
|
||||
fun setPass(satPass: SatPass) {
|
||||
this.satPass = satPass
|
||||
if (!satPass.isDeepSpace) {
|
||||
createPassTrajectory(satPass)
|
||||
createPassTrajectoryArrow()
|
||||
}
|
||||
}
|
||||
|
||||
fun setShowAim(showAim: Boolean) {
|
||||
shouldShowAim = showAim
|
||||
}
|
||||
|
||||
fun setOrientation(azimuth: Float, pitch: Float, roll: Float) {
|
||||
this.azimuth = azimuth
|
||||
this.pitch = pitch
|
||||
this.roll = roll
|
||||
this.rotation = -azimuth
|
||||
invalidate()
|
||||
if (shouldRotateRadar) {
|
||||
this.rotation = -azimuth
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
canvas.drawColor(ContextCompat.getColor(context, R.color.greyDark))
|
||||
canvas.translate(radarCenter, radarCenter)
|
||||
drawRadarView(canvas)
|
||||
drawRadarText(canvas)
|
||||
if (!satPass.isDeepSpace) {
|
||||
canvas.drawPath(satTrack, trackPaint)
|
||||
canvas.drawPath(satTrack, arrowPaint)
|
||||
val radarWidth = width - paddingLeft - paddingRight
|
||||
val radarHeight = height - paddingTop - paddingBottom
|
||||
val radarRadius = min(width, height) * 0.49f
|
||||
val cx = paddingLeft + radarWidth / 2f
|
||||
val cy = paddingTop + radarHeight / 2f
|
||||
|
||||
if (!satPass.isDeepSpace && !isTrackCreated) {
|
||||
createPassTrajectory(satPass)
|
||||
createPassTrajectoryArrow()
|
||||
isTrackCreated = true
|
||||
}
|
||||
drawSatellite(canvas, satPass)
|
||||
if (preferencesSource.shouldUseCompass()) {
|
||||
|
||||
canvas.drawColor(ContextCompat.getColor(context, R.color.greyDark))
|
||||
drawRadarCircle(canvas, cx, cy, radarRadius)
|
||||
drawRadarCross(canvas, cx, cy, radarRadius)
|
||||
drawRadarText(canvas, cx, radarRadius)
|
||||
|
||||
canvas.translate(cx, cy)
|
||||
if (!satPass.isDeepSpace) {
|
||||
canvas.drawPath(trackPath, trackPaint)
|
||||
canvas.drawPath(trackPath, arrowPaint)
|
||||
}
|
||||
if (shouldShowBeacons) {
|
||||
drawSatellite(canvas, satPass)
|
||||
}
|
||||
if (shouldShowAim) {
|
||||
drawCrosshair(canvas, azimuth, pitch)
|
||||
}
|
||||
if (shouldShowSweep) {
|
||||
canvas.translate(-cx, -cy)
|
||||
drawRadarSweep(canvas, cx, cy, radarRadius)
|
||||
sweepDegrees = (sweepDegrees + 360 / sweepSpeed / 60) % 360
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private fun createPassTrajectory(satPass: SatPass) {
|
||||
val currentTime = satPass.aosDate
|
||||
while (currentTime.before(satPass.losDate)) {
|
||||
val satPos = satPass.predictor.getSatPos(currentTime)
|
||||
val passX = sph2CartX(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
||||
val passY = sph2CartY(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
||||
if (currentTime.compareTo(satPass.aosDate) == 0) {
|
||||
satTrack.moveTo(passX, -passY)
|
||||
} else {
|
||||
satTrack.lineTo(passX, -passY)
|
||||
}
|
||||
currentTime.time += 15000
|
||||
private fun drawRadarCircle(canvas: Canvas, cx: Float, cy: Float, radius: Float) {
|
||||
for (i in 0 until radarCircleNum) {
|
||||
canvas.drawCircle(cx, cy, radius - radius / radarCircleNum * i, radarPaint)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPassTrajectoryArrow() {
|
||||
val radius = satSize
|
||||
val sides = 3
|
||||
val angle = 2.0 * Math.PI / sides
|
||||
arrowPath.moveTo((radius * cos(angle)).toFloat(), (radius * sin(angle)).toFloat())
|
||||
for (i in 1 until sides) {
|
||||
val x = (radius * cos(angle - angle * i)).toFloat()
|
||||
val y = (radius * sin(angle - angle * i)).toFloat()
|
||||
arrowPath.lineTo(x, y)
|
||||
private fun drawRadarCross(canvas: Canvas, cx: Float, cy: Float, radius: Float) {
|
||||
canvas.drawLine(cx - radius, cy, cx + radius, cy, radarPaint)
|
||||
canvas.drawLine(cx, cy - radius, cx, cy + radius, radarPaint)
|
||||
}
|
||||
|
||||
private fun drawRadarText(canvas: Canvas, cx: Float, radius: Float) {
|
||||
for (i in 0 until radarCircleNum) {
|
||||
val degText = " ${(90 / radarCircleNum) * (radarCircleNum - i)}°"
|
||||
canvas.drawText(degText, cx, radius - radius / radarCircleNum * i, radarTextPaint)
|
||||
}
|
||||
arrowPath.close()
|
||||
val trackLength = PathMeasure(satTrack, false).length
|
||||
val quarter = trackLength / 4f
|
||||
val center = trackLength / 2f
|
||||
val effect = PathDashPathEffect(arrowPath, center, quarter, PathDashPathEffect.Style.ROTATE)
|
||||
arrowPaint.pathEffect = effect
|
||||
}
|
||||
|
||||
private fun drawRadarView(canvas: Canvas) {
|
||||
canvas.drawLine(-radarRadius, 0f, radarRadius, 0f, radarPaint)
|
||||
canvas.drawLine(0f, -radarRadius, 0f, radarRadius, radarPaint)
|
||||
canvas.drawCircle(0f, 0f, radarRadius, radarPaint)
|
||||
canvas.drawCircle(0f, 0f, (radarRadius / 3) * 2, radarPaint)
|
||||
canvas.drawCircle(0f, 0f, radarRadius / 3, radarPaint)
|
||||
}
|
||||
|
||||
private fun drawRadarText(canvas: Canvas) {
|
||||
canvas.drawText("N", scale, -radarRadius + txtSize - strokeSize, textPaint)
|
||||
canvas.drawText("30°", scale, -((radarRadius / 3) * 2) - strokeSize, textPaint)
|
||||
canvas.drawText("60°", scale, -(radarRadius / 3) - strokeSize, textPaint)
|
||||
canvas.drawText("90°", scale, -strokeSize, textPaint)
|
||||
}
|
||||
|
||||
private fun drawSatellite(canvas: Canvas, satPass: SatPass) {
|
||||
@@ -157,7 +174,7 @@ class PassInfoView(context: Context) : View(context) {
|
||||
if (satPos.elevation > 0) {
|
||||
val satX = sph2CartX(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
||||
val satY = sph2CartY(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
||||
canvas.drawCircle(satX, -satY, satSize, satPaint)
|
||||
canvas.drawCircle(satX, -satY, beaconSize, beaconPaint)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,9 +184,9 @@ class PassInfoView(context: Context) : View(context) {
|
||||
val elevationRad = if (tmpElevation > 0.0) 0.0 else tmpElevation
|
||||
val crossX = sph2CartX(azimuthRad, -elevationRad, radarRadius.toDouble())
|
||||
val crossY = sph2CartY(azimuthRad, -elevationRad, radarRadius.toDouble())
|
||||
canvas.drawLine(crossX - txtSize, -crossY, crossX + txtSize, -crossY, trackPaint)
|
||||
canvas.drawLine(crossX, -crossY - txtSize, crossX, -crossY + txtSize, trackPaint)
|
||||
canvas.drawCircle(crossX, -crossY, txtSize / 2, trackPaint)
|
||||
canvas.drawLine(crossX - radarTextSize, -crossY, crossX + radarTextSize, -crossY, aimPaint)
|
||||
canvas.drawLine(crossX, -crossY - radarTextSize, crossX, -crossY + radarTextSize, aimPaint)
|
||||
canvas.drawCircle(crossX, -crossY, radarTextSize / 2, aimPaint)
|
||||
}
|
||||
|
||||
private fun sph2CartX(azimuth: Double, elevation: Double, r: Double): Float {
|
||||
@@ -181,4 +198,62 @@ class PassInfoView(context: Context) : View(context) {
|
||||
val radius = r * (piDiv2 - elevation) / piDiv2
|
||||
return (radius * sin(piDiv2 - azimuth)).toFloat()
|
||||
}
|
||||
|
||||
private fun createPassTrajectory(satPass: SatPass) {
|
||||
val currentTime = satPass.aosDate
|
||||
while (currentTime.before(satPass.losDate)) {
|
||||
val satPos = satPass.predictor.getSatPos(currentTime)
|
||||
val passX = sph2CartX(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
||||
val passY = sph2CartY(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
||||
if (currentTime.compareTo(satPass.aosDate) == 0) {
|
||||
trackPath.moveTo(passX, -passY)
|
||||
} else {
|
||||
trackPath.lineTo(passX, -passY)
|
||||
}
|
||||
currentTime.time += 15000
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPassTrajectoryArrow() {
|
||||
val radius = beaconSize
|
||||
val sides = 3
|
||||
val angle = 2.0 * Math.PI / sides
|
||||
arrowPath.moveTo((radius * cos(angle)).toFloat(), (radius * sin(angle)).toFloat())
|
||||
for (i in 1 until sides) {
|
||||
val x = (radius * cos(angle - angle * i)).toFloat()
|
||||
val y = (radius * sin(angle - angle * i)).toFloat()
|
||||
arrowPath.lineTo(x, y)
|
||||
}
|
||||
arrowPath.close()
|
||||
val trackLength = PathMeasure(trackPath, false).length
|
||||
val quarter = trackLength / 4f
|
||||
val center = trackLength / 2f
|
||||
val effect = PathDashPathEffect(arrowPath, center, quarter, PathDashPathEffect.Style.ROTATE)
|
||||
arrowPaint.pathEffect = effect
|
||||
}
|
||||
|
||||
private fun drawRadarSweep(canvas: Canvas, cx: Float, cy: Float, radius: Float) {
|
||||
val sweepGradient = SweepGradient(
|
||||
cx,
|
||||
cy,
|
||||
intArrayOf(
|
||||
Color.TRANSPARENT,
|
||||
changeAlpha(sweepColor, 0),
|
||||
changeAlpha(sweepColor, 164),
|
||||
changeAlpha(sweepColor, 255),
|
||||
changeAlpha(sweepColor, 255)
|
||||
),
|
||||
floatArrayOf(0.0f, 0.55f, 0.996f, 0.999f, 1f)
|
||||
)
|
||||
sweepPaint.shader = sweepGradient
|
||||
canvas.rotate(-90 + sweepDegrees, cx, cy)
|
||||
canvas.drawCircle(cx, cy, radius, sweepPaint)
|
||||
}
|
||||
|
||||
private fun changeAlpha(color: Int, alpha: Int): Int {
|
||||
val red = Color.red(color)
|
||||
val green = Color.green(color)
|
||||
val blue = Color.blue(color)
|
||||
return Color.argb(alpha, red, green, blue)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user