Added pass direction arrow to radar view #52

pull/65/head
Arty Bishop 2021-05-16 19:12:07 +01:00
rodzic 1da35e901a
commit 2d45fd614e
4 zmienionych plików z 105 dodań i 95 usunięć

Wyświetl plik

@ -61,8 +61,7 @@ dependencies {
implementation "com.jakewharton.timber:timber:$timber_version"
testImplementation "junit:junit:$junit_version"
testImplementation "org.mockito:mockito-core:$mockito_version"
testImplementation "org.mockito:mockito-android:$mockito_version"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
androidTestImplementation "org.mockito:mockito-android:$mockito_version"
debugImplementation "com.squareup.leakcanary:leakcanary-android:$leak_canary_version"
}

Wyświetl plik

@ -20,17 +20,16 @@ package com.rtbishop.look4sat.presentation.satMapScreen
import androidx.lifecycle.*
import com.rtbishop.look4sat.data.PreferencesSource
import com.rtbishop.look4sat.data.SatDataRepository
import com.rtbishop.look4sat.injection.DefaultDispatcher
import com.rtbishop.look4sat.domain.predict4kotlin.Position
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
import com.rtbishop.look4sat.domain.predict4kotlin.StationPosition
import com.rtbishop.look4sat.framework.model.SatData
import com.rtbishop.look4sat.injection.DefaultDispatcher
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.*
import org.osmdroid.views.MapView
import java.util.*
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sqrt
@ -47,8 +46,8 @@ class SatMapViewModel @Inject constructor(
private lateinit var selectedSat: Satellite
val stationPos = liveData {
val osmLat = getOsmLat(gsp.latitude)
val osmLon = getOsmLon(gsp.longitude)
val osmLat = clipLat(gsp.latitude)
val osmLon = clipLon(gsp.longitude)
emit(Position(osmLat, osmLon))
}
@ -115,8 +114,8 @@ class SatMapViewModel @Inject constructor(
val satPositions = mutableMapOf<Satellite, Position>()
list.forEach { satellite ->
val satPos = satellite.getPredictor(gsp).getSatPos(date)
val osmLat = getOsmLat(Math.toDegrees(satPos.latitude))
val osmLon = getOsmLon(Math.toDegrees(satPos.longitude))
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
satPositions[satellite] = Position(osmLat, osmLon)
}
_allSatPositions.postValue(satPositions)
@ -129,8 +128,8 @@ class SatMapViewModel @Inject constructor(
val currentTrack = mutableListOf<Position>()
var oldLongitude = 0.0
sat.getPredictor(gsp).getPositions(date, 15, 0, 2.4).forEach { satPos ->
val osmLat = getOsmLat(Math.toDegrees(satPos.latitude))
val osmLon = getOsmLon(Math.toDegrees(satPos.longitude))
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
val currentPosition = Position(osmLat, osmLon)
if (oldLongitude < -170.0 && currentPosition.longitude > 170.0) {
// adding left terminal position
@ -156,8 +155,8 @@ class SatMapViewModel @Inject constructor(
private suspend fun setSelectedSatFootprint(sat: Satellite, gsp: StationPosition, date: Date) {
withContext(defaultDispatcher) {
val satFootprint = sat.getPosition(gsp, date).getRangeCircle().map { rangePos ->
val osmLat = getOsmLat(rangePos.latitude)
val osmLon = getOsmLon(rangePos.longitude)
val osmLat = clipLat(rangePos.latitude)
val osmLon = clipLon(rangePos.longitude)
Position(osmLat, osmLon)
}
_satFootprint.postValue(satFootprint)
@ -167,8 +166,8 @@ class SatMapViewModel @Inject constructor(
private suspend fun setSelectedSatData(sat: Satellite, gsp: StationPosition, date: Date) {
withContext(defaultDispatcher) {
val satPos = sat.getPredictor(gsp).getSatPos(date)
val osmLat = getOsmLat(Math.toDegrees(satPos.latitude))
val osmLon = getOsmLon(Math.toDegrees(satPos.longitude))
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
val osmPos = Position(osmLat, osmLon)
val qthLoc =
preferencesSource.positionToQTH(osmPos.latitude, osmPos.longitude) ?: "-- --"
@ -181,17 +180,12 @@ class SatMapViewModel @Inject constructor(
}
}
private fun getOsmLat(latitude: Double): Double {
return min(max(latitude, -85.0), 85.0)
private fun clipLat(latitude: Double): Double {
return MapView.getTileSystem().cleanLatitude(latitude)
}
private fun getOsmLon(longitude: Double): Double {
val newLongitude = when {
longitude < -180.0 -> longitude + 360.0
longitude > 180.0 -> longitude - 360.0
else -> longitude
}
return min(max(newLongitude, -180.0), 180.0)
private fun clipLon(longitude: Double): Double {
return MapView.getTileSystem().cleanLongitude(longitude)
}
private fun getOrbitalVelocity(altitude: Double): Double {

Wyświetl plik

@ -18,10 +18,7 @@
package com.rtbishop.look4sat.presentation.satPassInfoScreen
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.graphics.*
import android.view.View
import androidx.core.content.ContextCompat
import com.rtbishop.look4sat.R
@ -33,49 +30,49 @@ import kotlin.math.sin
class PassInfoView(context: Context) : View(context) {
private lateinit var satPass: SatPass
private val polarWidth = resources.displayMetrics.widthPixels
private val polarCenter = polarWidth / 2f
private val scale = resources.displayMetrics.density
private val radius = polarWidth * 0.48f
private val txtSize = scale * 16f
private val satTrack: Path = Path()
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)
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 val 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 {
color = ContextCompat.getColor(context, R.color.themeLight)
style = Paint.Style.FILL
strokeWidth = strokeSize
}
private var azimuth: Float = 0f
private var pitch: Float = 0f
private var roll: Float = 0f
private val radarPaint = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.greyLight)
style = Paint.Style.STROKE
strokeWidth = scale * 2f
}
private val txtPaint = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.themeLight)
textSize = txtSize
}
private val trackPaint = Paint().apply {
isAntiAlias = true
color = Color.RED
style = Paint.Style.STROKE
strokeWidth = scale * 2f
}
private val satPaint = Paint().apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.themeLight)
style = Paint.Style.FILL
}
private val orientPaint = Paint().apply {
isAntiAlias = true
color = Color.RED
style = Paint.Style.STROKE
strokeWidth = scale * 2f
}
fun setPass(satPass: SatPass) {
this.satPass = satPass
createPassTrajectory(satPass)
if (!satPass.isDeepSpace) {
createPassTrajectory(satPass)
createPassTrajectoryArrow()
}
}
fun setOrientation(azimuth: Float, pitch: Float, roll: Float) {
@ -88,63 +85,83 @@ class PassInfoView(context: Context) : View(context) {
override fun onDraw(canvas: Canvas) {
canvas.drawColor(ContextCompat.getColor(context, R.color.greyDark))
canvas.translate(polarCenter, polarCenter)
canvas.translate(radarCenter, radarCenter)
drawRadarView(canvas)
drawRadarText(canvas)
if (!satPass.isDeepSpace) canvas.drawPath(satTrack, trackPaint)
if (!satPass.isDeepSpace) {
canvas.drawPath(satTrack, trackPaint)
canvas.drawPath(satTrack, arrowPaint)
}
drawSatellite(canvas, satPass)
drawOrientation(canvas, azimuth, pitch)
drawCrosshair(canvas, azimuth, pitch)
}
private fun createPassTrajectory(satPass: SatPass) {
val startTime = satPass.aosDate
val endTime = satPass.losDate
while (startTime.before(endTime)) {
val satPos = satPass.predictor.getSatPos(startTime)
val passX = sph2CartX(satPos.azimuth, satPos.elevation, radius.toDouble())
val passY = sph2CartY(satPos.azimuth, satPos.elevation, radius.toDouble())
if (startTime.compareTo(satPass.aosDate) == 0) {
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)
}
startTime.time += 15000
currentTime.time += 15000
}
}
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)
}
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(-radius, 0f, radius, 0f, radarPaint)
canvas.drawLine(0f, -radius, 0f, radius, radarPaint)
canvas.drawCircle(0f, 0f, radius, radarPaint)
canvas.drawCircle(0f, 0f, (radius / 3) * 2, radarPaint)
canvas.drawCircle(0f, 0f, radius / 3, radarPaint)
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, -radius + txtSize - scale * 2, txtPaint)
canvas.drawText("30°", scale, -((radius / 3) * 2) - scale * 2, txtPaint)
canvas.drawText("60°", scale, -(radius / 3) - scale * 2, txtPaint)
canvas.drawText("90°", scale, -scale * 2, txtPaint)
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) {
val satPos = satPass.predictor.getSatPos(Date())
if (satPos.elevation > 0) {
val x = sph2CartX(satPos.azimuth, satPos.elevation, radius.toDouble())
val y = sph2CartY(satPos.azimuth, satPos.elevation, radius.toDouble())
canvas.drawCircle(x, -y, txtSize / 2.4f, satPaint)
val satX = sph2CartX(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
val satY = sph2CartY(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
canvas.drawCircle(satX, -satY, satSize, satPaint)
}
}
private fun drawOrientation(canvas: Canvas, azimuth: Float, pitch: Float) {
private fun drawCrosshair(canvas: Canvas, azimuth: Float, pitch: Float) {
val azimuthRad = Math.toRadians(azimuth.toDouble())
val tmpElevation = Math.toRadians(pitch.toDouble())
val elevationRad = if (tmpElevation > 0.0) 0.0 else tmpElevation
val orientX = sph2CartX(azimuthRad, -elevationRad, radius.toDouble())
val orientY = sph2CartY(azimuthRad, -elevationRad, radius.toDouble())
canvas.drawLine(orientX - txtSize, -orientY, orientX + txtSize, -orientY, orientPaint)
canvas.drawLine(orientX, -orientY - txtSize, orientX, -orientY + txtSize, orientPaint)
canvas.drawCircle(orientX, -orientY, txtSize / 2, orientPaint)
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)
}
private fun sph2CartX(azimuth: Double, elevation: Double, r: Double): Float {

Wyświetl plik

@ -1,8 +1,8 @@
buildscript {
ext {
gradle_version = '4.2.0'
gradle_version = '4.2.1'
kotlin_version = '1.5.0'
coroutines_version = '1.5.0-RC-native-mt'
coroutines_version = '1.5.0'
material_version = '1.3.0'
constraint_layout_version = '2.0.4'
lifecycle_version = '2.3.1'
@ -14,7 +14,7 @@ buildscript {
osmdroid_version = '6.1.10'
timber_version = '4.7.1'
junit_version = '4.13.2'
mockito_version = '3.9.0'
mockito_version = '3.10.0'
leak_canary_version = '2.7'
}
repositories {