Added PolarView rotation using sensors data

This commit is contained in:
Arty Bishop committed 2020-05-08 19:46:26 +01:00
1 parent 1824cd8af0
commit 80b62b4658
6 files changed
+249 -221

No files matched your search

@@ -41,6 +41,7 @@ import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.databinding.FragmentMapViewBinding
import com.rtbishop.look4sat.ui.MainActivity
import com.rtbishop.look4sat.ui.SharedViewModel
import com.rtbishop.look4sat.utility.GeneralUtils
import com.rtbishop.look4sat.utility.PassPredictor
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -182,7 +183,7 @@ class MapViewFragment : Fragment(R.layout.fragment_map_view) {
val degLon = width / 360f
val degLat = height / 180f
drawHomeLoc(canvas, degLon, degLat)
val currentTime = getDateFor(System.currentTimeMillis())
val currentTime = GeneralUtils.getDateFor(System.currentTimeMillis())
val orbitalPeriod = (24 * 60 / selectedSat.meanmo).toInt()
val positions = predictor.getPositions(currentTime, 60, 0, orbitalPeriod * 3)
setTextViewsToSelectedSatPos(positions[0])
@@ -215,8 +216,8 @@ class MapViewFragment : Fragment(R.layout.fragment_map_view) {
}
private fun setTextViewsToSelectedSatPos(position: SatPos) {
var lon = rad2Deg(position.longitude).toFloat()
val lat = rad2Deg(position.latitude).toFloat()
var lon = GeneralUtils.rad2Deg(position.longitude).toFloat()
val lat = GeneralUtils.rad2Deg(position.latitude).toFloat()
val rng = position.range
if (lon > 180f) lon -= 360f
@@ -238,8 +239,8 @@ class MapViewFragment : Fragment(R.layout.fragment_map_view) {
var lastLon = 181f
list.withIndex().forEach { (index, satPos) ->
lon = rad2Deg(satPos.longitude).toFloat()
lat = rad2Deg(satPos.latitude).toFloat() * -1
lon = GeneralUtils.rad2Deg(satPos.longitude).toFloat()
lat = GeneralUtils.rad2Deg(satPos.latitude).toFloat() * -1
if (lon > 180f) lon -= 360f
@@ -305,8 +306,8 @@ class MapViewFragment : Fragment(R.layout.fragment_map_view) {
position: SatPos,
name: String
) {
var lon = rad2Deg(position.longitude).toFloat()
val lat = rad2Deg(position.latitude).toFloat() * -1
var lon = GeneralUtils.rad2Deg(position.longitude).toFloat()
val lat = GeneralUtils.rad2Deg(position.latitude).toFloat() * -1
if (lon > 180f) lon -= 360f
@@ -318,13 +319,5 @@ class MapViewFragment : Fragment(R.layout.fragment_map_view) {
canvas.drawText(name, cx - rect.width() / 2, cy - txtPaint.textSize, outlinePaint)
canvas.drawText(name, cx - rect.width() / 2, cy - txtPaint.textSize, txtPaint)
}
private fun getDateFor(value: Long): Date {
return Date(value)
}
private fun rad2Deg(value: Double): Double {
return value * 180 / Math.PI
}
}
}
@@ -20,18 +20,18 @@
package com.rtbishop.look4sat.ui.fragments
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Bundle
import android.view.View
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import com.github.amsacode.predict4java.SatPos
import com.rtbishop.look4sat.Look4SatApp
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.dagger.ViewModelFactory
@@ -41,225 +41,136 @@ import com.rtbishop.look4sat.databinding.FragmentPolarViewBinding
import com.rtbishop.look4sat.ui.MainActivity
import com.rtbishop.look4sat.ui.SharedViewModel
import com.rtbishop.look4sat.ui.adapters.TransmitterAdapter
import com.rtbishop.look4sat.ui.views.PolarView
import com.rtbishop.look4sat.utility.GeneralUtils
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.*
import javax.inject.Inject
import kotlin.math.cos
import kotlin.math.sin
class PolarViewFragment : Fragment(R.layout.fragment_polar_view) {
class PolarViewFragment : Fragment(R.layout.fragment_polar_view), SensorEventListener {
@Inject
lateinit var modelFactory: ViewModelFactory
private val args: PolarViewFragmentArgs by navArgs()
private lateinit var mainActivity: MainActivity
private lateinit var viewModel: SharedViewModel
private lateinit var binding: FragmentPolarViewBinding
private lateinit var satPass: SatPass
private lateinit var polarView: PolarView
private lateinit var mainActivity: MainActivity
private var transmitterAdapter: TransmitterAdapter = TransmitterAdapter()
private lateinit var sensorManager: SensorManager
private val args: PolarViewFragmentArgs by navArgs()
private val transmitterAdapter = TransmitterAdapter()
private var lastAccData = FloatArray(3)
private var lastMagData = FloatArray(3)
private var refreshJob: Job? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentPolarViewBinding.bind(view)
mainActivity = activity as MainActivity
binding = FragmentPolarViewBinding.bind(view)
(mainActivity.application as Look4SatApp).appComponent.inject(this)
viewModel = ViewModelProvider(mainActivity, modelFactory).get(SharedViewModel::class.java)
val refreshRate = viewModel.getRefreshRate()
sensorManager = mainActivity.getSystemService(Context.SENSOR_SERVICE) as SensorManager
binding.recPolar.apply {
layoutManager = LinearLayoutManager(mainActivity)
adapter = transmitterAdapter
}
observePasses(refreshRate, binding)
observePasses()
}
private fun observePasses(
refreshRate: Long,
binding: FragmentPolarViewBinding
) {
viewModel.getPassList()
.observe(viewLifecycleOwner, androidx.lifecycle.Observer { result ->
when (result) {
is Result.Success -> {
satPass = result.data[args.satPassIndex]
mainActivity.supportActionBar?.title = satPass.tle.name
polarView = PolarView(mainActivity, refreshRate, binding)
binding.framePolar.addView(polarView)
observeTransmitters(binding)
refreshView()
}
override fun onResume() {
super.onResume()
sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER).also { accData ->
sensorManager.registerListener(this, accData, SensorManager.SENSOR_DELAY_GAME)
}
sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD).also { magData ->
sensorManager.registerListener(this, magData, SensorManager.SENSOR_DELAY_GAME)
}
}
override fun onPause() {
super.onPause()
sensorManager.unregisterListener(this)
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
override fun onSensorChanged(event: SensorEvent?) {
event?.let {
if (it.sensor.type == Sensor.TYPE_ACCELEROMETER) {
filterSensorData(event.values, lastAccData)
} else if (it.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) {
filterSensorData(event.values, lastMagData)
}
val rotationMatrix = FloatArray(9)
if (SensorManager.getRotationMatrix(rotationMatrix, null, lastAccData, lastMagData)) {
val orientation = FloatArray(3)
SensorManager.getOrientation(rotationMatrix, orientation)
val degree = orientation[0] * 57.2957795f
polarView.rotation = -degree
}
}
}
private fun filterSensorData(input: FloatArray, output: FloatArray) {
val filterStep = 0.05f
input.indices.forEach { output[it] = output[it] + filterStep * (input[it] - output[it]) }
}
private fun observePasses() {
viewModel.getPassList().observe(viewLifecycleOwner, Observer { result ->
when (result) {
is Result.Success -> {
val refreshRate = viewModel.getRefreshRate()
satPass = result.data[args.satPassIndex]
mainActivity.supportActionBar?.title = satPass.tle.name
polarView = PolarView(mainActivity, satPass)
binding.framePolar.addView(polarView)
observeTransmitters()
refreshText(refreshRate)
}
})
}
})
}
private fun observeTransmitters(binding: FragmentPolarViewBinding) {
viewModel.getTransmittersForSat(satPass.tle.catnum)
.observe(viewLifecycleOwner, androidx.lifecycle.Observer {
if (it.isNotEmpty()) {
transmitterAdapter.setList(it)
transmitterAdapter.notifyDataSetChanged()
binding.recPolar.visibility = View.VISIBLE
binding.tvPolarNoTrans.visibility = View.INVISIBLE
} else {
binding.recPolar.visibility = View.INVISIBLE
binding.tvPolarNoTrans.visibility = View.VISIBLE
}
})
private fun observeTransmitters() {
viewModel.getTransmittersForSat(satPass.tle.catnum).observe(viewLifecycleOwner, Observer {
if (it.isNotEmpty()) {
transmitterAdapter.setList(it)
transmitterAdapter.notifyDataSetChanged()
binding.recPolar.visibility = View.VISIBLE
binding.tvPolarNoTrans.visibility = View.INVISIBLE
} else {
binding.recPolar.visibility = View.INVISIBLE
binding.tvPolarNoTrans.visibility = View.VISIBLE
}
})
}
private fun refreshView() {
val rate = viewModel.getRefreshRate()
lifecycleScope.launch {
private fun refreshText(rate: Long) {
refreshJob?.cancel()
refreshJob = null
refreshJob = lifecycleScope.launch {
while (true) {
setPassText()
polarView.invalidate()
delay(rate)
}
}
}
inner class PolarView(
context: Context,
private val updateFreq: Long,
private val binding: FragmentPolarViewBinding
) :
View(context) {
private val radarSize = resources.displayMetrics.widthPixels
private val scale = resources.displayMetrics.density
private val startTime = satPass.pass.startTime
private val endTime = satPass.pass.endTime
private val radius = radarSize * 0.45f
private val piDiv2 = Math.PI / 2.0
private val txtSize = scale * 15
private val center = 0f
private val radarPaint = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(mainActivity, R.color.themeLight)
style = Paint.Style.STROKE
strokeWidth = scale
}
private val txtPaint = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(mainActivity, R.color.themeAccent)
textSize = txtSize
}
private val trackPaint = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(mainActivity, R.color.satTrack)
style = Paint.Style.STROKE
strokeWidth = scale
}
private val satPaint = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(mainActivity, R.color.themeAccent)
style = Paint.Style.FILL
}
private val path: Path = Path()
private lateinit var satPos: SatPos
private var satPassX = 0f
private var satPassY = 0f
override fun onDraw(canvas: Canvas) {
canvas.translate(radarSize / 2f, radarSize / 2f)
setPassText()
drawRadarView(canvas)
drawRadarText(canvas)
if (!satPass.tle.isDeepspace) drawPassTrajectory(canvas)
drawSatellite(canvas)
}
private fun setPassText() {
satPos = satPass.predictor.getSatPos(Date())
binding.tvPolarAz.text =
String.format(context.getString(R.string.pat_azimuth), rad2Deg(satPos.azimuth))
binding.tvPolarEl.text =
String.format(context.getString(R.string.pat_elevation), rad2Deg(satPos.elevation))
binding.tvPolarRng.text =
String.format(context.getString(R.string.pat_range), satPos.range)
binding.tvPolarAlt.text =
String.format(context.getString(R.string.pat_altitude), satPos.altitude)
}
private fun drawRadarView(cvs: Canvas) {
cvs.drawLine(center - radius, center, center + radius, center, radarPaint)
cvs.drawLine(center, center - radius, center, center + radius, radarPaint)
cvs.drawCircle(center, center, radius, radarPaint)
cvs.drawCircle(center, center, (radius / 3) * 2, radarPaint)
cvs.drawCircle(center, center, radius / 3, radarPaint)
}
private fun drawRadarText(cvs: Canvas) {
cvs.drawText(
context.getString(R.string.polar_north),
center - txtSize / 3,
center - radius - scale * 2,
txtPaint
)
cvs.drawText(
context.getString(R.string.polar_east),
center + radius + scale * 2,
center + txtSize / 3,
txtPaint
)
cvs.drawText(
context.getString(R.string.polar_south),
center - txtSize / 3,
center + radius + txtSize,
txtPaint
)
cvs.drawText(
context.getString(R.string.polar_west),
center - radius - txtSize,
center + txtSize / 3,
txtPaint
)
cvs.drawText("90°", center + scale, center - scale * 2, txtPaint)
cvs.drawText("60°", center + scale, center - (radius / 3) - scale * 2, txtPaint)
cvs.drawText("30°", center + scale, center - ((radius / 3) * 2) - scale * 2, txtPaint)
}
private fun drawPassTrajectory(cvs: Canvas) {
while (startTime.before(endTime)) {
satPos = satPass.predictor.getSatPos(startTime)
satPassX = center + sph2CartX(satPos.azimuth, satPos.elevation, radius.toDouble())
satPassY = center - sph2CartY(satPos.azimuth, satPos.elevation, radius.toDouble())
if (startTime.compareTo(satPass.pass.startTime) == 0) {
path.moveTo(satPassX, satPassY)
} else {
path.lineTo(satPassX, satPassY)
}
startTime.time += updateFreq
}
cvs.drawPath(path, trackPaint)
}
private fun drawSatellite(cvs: Canvas) {
satPos = satPass.predictor.getSatPos(Date())
if (satPos.elevation > 0) {
cvs.drawCircle(
center + sph2CartX(satPos.azimuth, satPos.elevation, radius.toDouble()),
center - sph2CartY(satPos.azimuth, satPos.elevation, radius.toDouble()),
txtSize / 3, satPaint
)
}
}
private fun sph2CartX(azimuth: Double, elevation: Double, r: Double): Float {
val radius = r * (piDiv2 - elevation) / piDiv2
return (radius * cos(piDiv2 - azimuth)).toFloat()
}
private fun sph2CartY(azimuth: Double, elevation: Double, r: Double): Float {
val radius = r * (piDiv2 - elevation) / piDiv2
return (radius * sin(piDiv2 - azimuth)).toFloat()
}
private fun rad2Deg(value: Double): Double {
return value * 180 / Math.PI
}
private fun setPassText() {
val satPos = satPass.predictor.getSatPos(Date())
val polarAz = mainActivity.getString(R.string.pat_azimuth)
val polarEl = mainActivity.getString(R.string.pat_elevation)
val polarRng = mainActivity.getString(R.string.pat_range)
val polarAlt = mainActivity.getString(R.string.pat_altitude)
binding.tvPolarAz.text = String.format(polarAz, GeneralUtils.rad2Deg(satPos.azimuth))
binding.tvPolarEl.text = String.format(polarEl, GeneralUtils.rad2Deg(satPos.elevation))
binding.tvPolarRng.text = String.format(polarRng, satPos.range)
binding.tvPolarAlt.text = String.format(polarAlt, satPos.altitude)
}
}
@@ -0,0 +1,106 @@
package com.rtbishop.look4sat.ui.views
import android.content.Context
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.Path
import android.view.View
import androidx.core.content.ContextCompat
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.utility.GeneralUtils
class PolarView(context: Context) : View(context) {
constructor(context: Context, satPass: SatPass) : this(context) {
pass = satPass
}
private lateinit var pass: SatPass
private val polarWidth = resources.displayMetrics.widthPixels
private val polarCenter = polarWidth / 2f
private val scale = resources.displayMetrics.density
private val radius = polarWidth * 0.49f
private val txtSize = scale * 15
private val path: Path = Path()
private val mMatrix = Matrix()
var azimuth = 0f
private val radarPaint = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.themeLight)
style = Paint.Style.STROKE
strokeWidth = scale
}
private val txtPaint = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.themeAccent)
textSize = txtSize
}
private val trackPaint = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.satTrack)
style = Paint.Style.STROKE
strokeWidth = scale
}
private val satPaint = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.themeAccent)
style = Paint.Style.FILL
}
override fun onDraw(canvas: Canvas) {
// Log.w("myTag", "$measuredWidth, $measuredHeight")
canvas.rotate(-azimuth, polarCenter, polarCenter)
canvas.translate(polarCenter, polarCenter)
drawRadarView(canvas)
drawRadarText(canvas)
if (!pass.tle.isDeepspace) {
drawPassTrajectory(canvas, pass)
}
drawSatellite(canvas, pass)
}
private fun drawRadarView(cvs: Canvas) {
cvs.drawLine(-radius, 0f, radius, 0f, radarPaint)
cvs.drawLine(0f, -radius, 0f, radius, radarPaint)
cvs.drawCircle(0f, 0f, radius, radarPaint)
cvs.drawCircle(0f, 0f, (radius / 3) * 2, radarPaint)
cvs.drawCircle(0f, 0f, radius / 3, radarPaint)
}
private fun drawRadarText(cvs: Canvas) {
cvs.drawText("N", scale, -radius + txtSize - scale * 2, txtPaint)
cvs.drawText("30°", scale, -((radius / 3) * 2) - scale * 2, txtPaint)
cvs.drawText("60°", scale, -(radius / 3) - scale * 2, txtPaint)
cvs.drawText("90°", scale, -scale * 2, txtPaint)
}
private fun drawPassTrajectory(cvs: Canvas, satPass: SatPass) {
val startTime = satPass.pass.startTime
val endTime = satPass.pass.endTime
while (startTime.before(endTime)) {
val satPos = satPass.predictor.getSatPos(startTime)
val x = GeneralUtils.sph2CartX(satPos.azimuth, satPos.elevation, radius.toDouble())
val y = GeneralUtils.sph2CartY(satPos.azimuth, satPos.elevation, radius.toDouble())
if (startTime.compareTo(satPass.pass.startTime) == 0) {
path.moveTo(x, -y)
} else {
path.lineTo(x, -y)
}
startTime.time += 15000
}
cvs.drawPath(path, trackPaint)
}
private fun drawSatellite(cvs: Canvas, satPass: SatPass) {
val date = GeneralUtils.getDateFor(System.currentTimeMillis())
val satPos = satPass.predictor.getSatPos(date)
if (satPos.elevation > 0) {
val x = GeneralUtils.sph2CartX(satPos.azimuth, satPos.elevation, radius.toDouble())
val y = GeneralUtils.sph2CartY(satPos.azimuth, satPos.elevation, radius.toDouble())
cvs.drawCircle(x, -y, txtSize / 3, satPaint)
}
}
}
@@ -0,0 +1,28 @@
package com.rtbishop.look4sat.utility
import java.util.*
import kotlin.math.cos
import kotlin.math.sin
object GeneralUtils {
private const val piDiv2 = Math.PI / 2.0
fun getDateFor(value: Long): Date {
return Date(value)
}
fun rad2Deg(value: Double): Double {
return value * 180 / Math.PI
}
fun sph2CartX(azimuth: Double, elevation: Double, r: Double): Float {
val radius = r * (piDiv2 - elevation) / piDiv2
return (radius * cos(piDiv2 - azimuth)).toFloat()
}
fun sph2CartY(azimuth: Double, elevation: Double, r: Double): Float {
val radius = r * (piDiv2 - elevation) / piDiv2
return (radius * sin(piDiv2 - azimuth)).toFloat()
}
}
-5
View File
@@ -77,11 +77,6 @@
<string name="pass_maxEl">Элевация: %.1f°</string>
<string name="pass_los_az">%2d° — КПС</string>
<string name="polar_north">С</string>
<string name="polar_west">З</string>
<string name="polar_south">Ю</string>
<string name="polar_east">В</string>
<string name="pat_azimuth">Азимут: %.1f°</string>
<string name="pat_elevation">Элевация: %.1f°</string>
<string name="pat_latitude">Широта: %.1f°</string>
+6 -11
View File
@@ -91,18 +91,13 @@
<string name="pass_los_az">%2d° — LOS</string>
<string name="pass_dateTime" translatable="false">dd MMM @ HH:mm:ss</string>
<string name="polar_north">N</string>
<string name="polar_west">W</string>
<string name="polar_south">S</string>
<string name="polar_east">E</string>
<string name="pat_location" translatable="false">%.4f</string>
<string name="pat_azimuth">Azimuth: %.1f°</string>
<string name="pat_elevation">Elevation: %.1f°</string>
<string name="pat_latitude">Latitude: %.1f°</string>
<string name="pat_longitude">Longitude: %.1f°</string>
<string name="pat_range">Distance: %.0f km</string>
<string name="pat_altitude">Altitude: %.0f km</string>
<string name="pat_azimuth">Az: %.1f°</string>
<string name="pat_elevation">El: %.1f°</string>
<string name="pat_latitude">Lat: %.1f°</string>
<string name="pat_longitude">Lon: %.1f°</string>
<string name="pat_range">Dst: %.0f km</string>
<string name="pat_altitude">Alt: %.0f km</string>
<string name="pat_timer" translatable="false">%02d:%02d:%02d</string>
<string name="trans_description" translatable="false">%s</string>