Further refactor of MapFragment.kt and MapViewModel.kt

This commit is contained in:
Arty Bishop committed 2020-11-28 13:56:03 +00:00
1 parent 62f8a4a534
commit 4556d2d975
4 files changed
+152 -138

No files matched your search

@@ -0,0 +1,18 @@
package com.rtbishop.look4sat.data
import com.github.amsacode.predict4java.Position
import org.osmdroid.views.overlay.Overlay
data class SelectedSat(
val pass: SatPass,
val catNum: Int,
val name: String,
val range: Double,
val altitude: Double,
val velocity: Double,
val qthLoc: String,
val osmPos: Position,
val coverage: Double,
val footprint: Overlay,
val groundTrack: Overlay
)
@@ -4,7 +4,6 @@ import android.content.SharedPreferences
import android.graphics.Color import android.graphics.Color
import android.graphics.ColorMatrix import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@@ -17,8 +16,8 @@ import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.SharedViewModel import com.rtbishop.look4sat.SharedViewModel
import com.rtbishop.look4sat.data.Result import com.rtbishop.look4sat.data.Result
import com.rtbishop.look4sat.data.SatPass import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.data.SelectedSat
import com.rtbishop.look4sat.databinding.FragmentMapBinding import com.rtbishop.look4sat.databinding.FragmentMapBinding
import com.rtbishop.look4sat.utility.Utilities
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.osmdroid.config.Configuration import org.osmdroid.config.Configuration
import org.osmdroid.tileprovider.tilesource.ITileSource import org.osmdroid.tileprovider.tilesource.ITileSource
@@ -36,8 +35,7 @@ class MapFragment : Fragment(R.layout.fragment_map) {
@Inject @Inject
lateinit var preferences: SharedPreferences lateinit var preferences: SharedPreferences
private lateinit var trackPaint: Paint private lateinit var binding: FragmentMapBinding
private lateinit var footprintPaint: Paint
private lateinit var nameFormat: String private lateinit var nameFormat: String
private lateinit var qthFormat: String private lateinit var qthFormat: String
private lateinit var altFormat: String private lateinit var altFormat: String
@@ -45,46 +43,18 @@ class MapFragment : Fragment(R.layout.fragment_map) {
private lateinit var velFormat: String private lateinit var velFormat: String
private lateinit var latLonFormat: String private lateinit var latLonFormat: String
private lateinit var coverageFormat: String private lateinit var coverageFormat: String
private lateinit var selectedSat: SatPass
private val dateNow = Date()
private val viewModel: MapViewModel by viewModels() private val viewModel: MapViewModel by viewModels()
private val sharedViewModel: SharedViewModel by activityViewModels() private val sharedViewModel: SharedViewModel by activityViewModels()
private var binding: FragmentMapBinding? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupComponents(view) setupComponents(view)
setupMapView()
setupObservers() setupObservers()
} }
private fun setupComponents(view: View) { private fun setupComponents(view: View) {
Configuration.getInstance().load(requireContext().applicationContext, preferences) Configuration.getInstance().load(requireContext().applicationContext, preferences)
binding = FragmentMapBinding.bind(view) binding = FragmentMapBinding.bind(view).apply {
trackPaint = Paint().apply {
strokeWidth = 2f
style = Paint.Style.STROKE
color = Color.RED
strokeCap = Paint.Cap.ROUND
strokeJoin = Paint.Join.ROUND
isAntiAlias = true
}
footprintPaint = Paint().apply {
style = Paint.Style.FILL_AND_STROKE
color = ContextCompat.getColor(requireContext(), R.color.satFootprint)
isAntiAlias = true
}
nameFormat = getString(R.string.pat_osm_idName)
qthFormat = getString(R.string.map_qth)
altFormat = getString(R.string.pat_altitude)
distFormat = getString(R.string.pat_distance)
velFormat = getString(R.string.pat_osm_vel)
latLonFormat = getString(R.string.pat_osm_latLon)
coverageFormat = getString(R.string.map_coverage)
}
private fun setupMapView() {
binding?.apply {
mapView.apply { mapView.apply {
setMultiTouchControls(true) setMultiTouchControls(true)
setTileSource(getTileSource()) setTileSource(getTileSource())
@@ -100,6 +70,13 @@ class MapFragment : Fragment(R.layout.fragment_map) {
overlays.addAll(Array(4) { FolderOverlay() }) overlays.addAll(Array(4) { FolderOverlay() })
} }
} }
nameFormat = getString(R.string.pat_osm_idName)
qthFormat = getString(R.string.map_qth)
altFormat = getString(R.string.pat_altitude)
distFormat = getString(R.string.pat_distance)
velFormat = getString(R.string.pat_osm_vel)
latLonFormat = getString(R.string.pat_osm_latLon)
coverageFormat = getString(R.string.map_coverage)
} }
private fun setupObservers() { private fun setupObservers() {
@@ -107,23 +84,16 @@ class MapFragment : Fragment(R.layout.fragment_map) {
sharedViewModel.getPasses().observe(viewLifecycleOwner, { sharedViewModel.getPasses().observe(viewLifecycleOwner, {
if (it is Result.Success && it.data.isNotEmpty()) { if (it is Result.Success && it.data.isNotEmpty()) {
viewModel.setPasses(it.data) viewModel.setPasses(it.data)
binding?.fabPrev?.setOnClickListener { viewModel.changeSelection(true) } binding.fabPrev.setOnClickListener { viewModel.changeSelection(true) }
binding?.fabNext?.setOnClickListener { viewModel.changeSelection(false) } binding.fabNext.setOnClickListener { viewModel.changeSelection(false) }
} }
}) })
viewModel.getSelectedPass().observe(viewLifecycleOwner, { satPass -> viewModel.getSelectedSat().observe(viewLifecycleOwner, { setSelectedSatDetails(it) })
selectedSat = satPass viewModel.getSatMarkers().observe(viewLifecycleOwner, { setMarkers(it) })
setSelectedSatDetails(satPass)
scrollToSat(satPass)
})
viewModel.getSatMarkers().observe(viewLifecycleOwner, {
setMarkers(it)
setSelectedSatDetails(selectedSat)
})
} }
private fun setupPosOverlay(gsp: GroundStationPosition) { private fun setupPosOverlay(gsp: GroundStationPosition) {
binding?.apply { binding.apply {
Marker(mapView).apply { Marker(mapView).apply {
setInfoWindow(null) setInfoWindow(null)
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
@@ -135,33 +105,23 @@ class MapFragment : Fragment(R.layout.fragment_map) {
} }
} }
private fun setSelectedSatDetails(satPass: SatPass) { private fun setSelectedSatDetails(sat: SelectedSat) {
val satPos = satPass.predictor.getSatPos(dateNow) binding.apply {
val osmPos = viewModel.getOsmPosition(satPos.latitude, satPos.longitude, true) idName.text = String.format(nameFormat, sat.catNum, sat.name)
val qthLoc = Utilities.locToQTH(osmPos.lat, osmPos.lon) qthLocator.text = String.format(qthFormat, sat.qthLoc)
binding?.apply { altitude.text = String.format(altFormat, sat.altitude)
idName.text = String.format(nameFormat, satPass.tle.catnum, satPass.tle.name) distance.text = String.format(distFormat, sat.range)
qthLocator.text = String.format(qthFormat, qthLoc) velocity.text = String.format(velFormat, sat.velocity)
altitude.text = String.format(altFormat, satPos.altitude) latLon.text = String.format(latLonFormat, sat.osmPos.lat, sat.osmPos.lon)
distance.text = String.format(distFormat, satPos.range) mapCoverage.text = String.format(coverageFormat, sat.coverage)
velocity.text = String.format(velFormat, viewModel.getSatVelocity(satPos.altitude)) mapView.overlays[1] = sat.groundTrack
latLon.text = String.format(latLonFormat, osmPos.lat, osmPos.lon) mapView.overlays[2] = sat.footprint
mapCoverage.text = String.format(coverageFormat, satPos.rangeCircleRadiusKm * 2)
mapView.overlays[1] = getSatTrack(satPass)
mapView.overlays[2] = getSatFootprint(satPass)
mapView.invalidate() mapView.invalidate()
} }
} }
private fun scrollToSat(satPass: SatPass) {
val satPos = satPass.predictor.getSatPos(dateNow)
val osmPos = viewModel.getOsmPosition(satPos.latitude, satPos.longitude, true)
binding?.mapView?.controller?.animateTo(GeoPoint(osmPos.lat, osmPos.lon))
}
private fun setMarkers(map: Map<SatPass, Position>) { private fun setMarkers(map: Map<SatPass, Position>) {
dateNow.time = System.currentTimeMillis() binding.apply {
binding?.apply {
val markers = FolderOverlay() val markers = FolderOverlay()
map.entries.forEach { map.entries.forEach {
Marker(mapView).apply { Marker(mapView).apply {
@@ -169,7 +129,10 @@ class MapFragment : Fragment(R.layout.fragment_map) {
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER) setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_map_sat) icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_map_sat)
position = GeoPoint(it.value.lat, it.value.lon) position = GeoPoint(it.value.lat, it.value.lon)
setOnMarkerClickListener { _, _ -> viewModel.setSelectedSat(it.key) } setOnMarkerClickListener { _, _ ->
viewModel.setSelectedSat(it.key)
return@setOnMarkerClickListener true
}
markers.add(this) markers.add(this)
} }
} }
@@ -178,48 +141,6 @@ class MapFragment : Fragment(R.layout.fragment_map) {
} }
} }
private fun getSatTrack(pass: SatPass): Overlay {
val period = (24 * 60 / pass.tle.meanmo).toInt()
val positions = pass.predictor.getPositions(dateNow, 20, 0, period * 3)
val trackOverlay = FolderOverlay()
val trackPoints = mutableListOf<GeoPoint>()
var oldLon = 0.0
positions.forEach {
val osmPos = viewModel.getOsmPosition(it.latitude, it.longitude, true)
if (oldLon < -170.0 && osmPos.lon > 170.0 || oldLon > 170.0 && osmPos.lon < -170.0) {
val currentPoints = mutableListOf<GeoPoint>()
currentPoints.addAll(trackPoints)
Polyline().apply {
outlinePaint.set(trackPaint)
setPoints(currentPoints)
trackOverlay.add(this)
}
trackPoints.clear()
}
oldLon = osmPos.lon
trackPoints.add(GeoPoint(osmPos.lat, osmPos.lon))
}
Polyline().apply {
outlinePaint.set(trackPaint)
setPoints(trackPoints)
trackOverlay.add(this)
}
return trackOverlay
}
private fun getSatFootprint(pass: SatPass): Overlay {
val rangePoints = mutableListOf<GeoPoint>()
pass.predictor.getSatPos(dateNow).rangeCircle.withIndex().forEach {
val osmPos = viewModel.getOsmPosition(it.value.lat, it.value.lon, false)
rangePoints.add(GeoPoint(osmPos.lat, osmPos.lon))
}
return Polygon().apply {
fillPaint.set(footprintPaint)
outlinePaint.set(footprintPaint)
points = rangePoints
}
}
private fun getColorFilter(): ColorMatrixColorFilter { private fun getColorFilter(): ColorMatrixColorFilter {
val targetColor = Color.RED val targetColor = Color.RED
val newR = Color.red(targetColor) / 255f val newR = Color.red(targetColor) / 255f
@@ -254,9 +175,4 @@ class MapFragment : Fragment(R.layout.fragment_map) {
) )
return XYTileSource("wikimedia", 2, 6, 256, ".png", sources, copyright, policy) return XYTileSource("wikimedia", 2, 6, 256, ".png", sources, copyright, policy)
} }
override fun onDestroyView() {
binding = null
super.onDestroyView()
}
} }
@@ -1,5 +1,7 @@
package com.rtbishop.look4sat.ui.mainScreen package com.rtbishop.look4sat.ui.mainScreen
import android.graphics.Color
import android.graphics.Paint
import androidx.hilt.lifecycle.ViewModelInject import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@@ -8,10 +10,17 @@ import androidx.lifecycle.viewModelScope
import com.github.amsacode.predict4java.GroundStationPosition import com.github.amsacode.predict4java.GroundStationPosition
import com.github.amsacode.predict4java.Position import com.github.amsacode.predict4java.Position
import com.rtbishop.look4sat.data.SatPass import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.data.SelectedSat
import com.rtbishop.look4sat.utility.PrefsManager import com.rtbishop.look4sat.utility.PrefsManager
import com.rtbishop.look4sat.utility.Utilities
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.overlay.FolderOverlay
import org.osmdroid.views.overlay.Overlay
import org.osmdroid.views.overlay.Polygon
import org.osmdroid.views.overlay.Polyline
import java.util.* import java.util.*
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.sqrt import kotlin.math.sqrt
@@ -19,60 +28,133 @@ import kotlin.math.sqrt
class MapViewModel @ViewModelInject constructor(prefsManager: PrefsManager) : ViewModel() { class MapViewModel @ViewModelInject constructor(prefsManager: PrefsManager) : ViewModel() {
private val dateNow = Date() private val dateNow = Date()
private val trackPaint = Paint().apply {
strokeWidth = 2f
style = Paint.Style.STROKE
color = Color.RED
strokeCap = Paint.Cap.ROUND
strokeJoin = Paint.Join.ROUND
isAntiAlias = true
}
private val footprintPaint = Paint().apply {
style = Paint.Style.FILL_AND_STROKE
color = Color.parseColor("#26FFE082")
isAntiAlias = true
}
private var filteredPasses = listOf<SatPass>() private var filteredPasses = listOf<SatPass>()
private val _gsp = MutableLiveData(prefsManager.getStationPosition()) private val _gsp = MutableLiveData(prefsManager.getStationPosition())
fun getGSP(): LiveData<GroundStationPosition> = _gsp fun getGSP(): LiveData<GroundStationPosition> = _gsp
private val _selectedPass = MutableLiveData<SatPass>() private val _selectedSat = MutableLiveData<SelectedSat>()
fun getSelectedPass(): LiveData<SatPass> = _selectedPass fun getSelectedSat(): LiveData<SelectedSat> = _selectedSat
private val _satMarkers = MutableLiveData<Map<SatPass, Position>>() private val _satMarkers = MutableLiveData<Map<SatPass, Position>>()
fun getSatMarkers(): LiveData<Map<SatPass, Position>> = _satMarkers fun getSatMarkers(): LiveData<Map<SatPass, Position>> = _satMarkers
fun setPasses(passesList: List<SatPass>) { fun setPasses(passesList: List<SatPass>) {
filteredPasses = passesList.distinctBy { it.tle } filteredPasses = passesList.distinctBy { it.tle }
_selectedPass.value = filteredPasses[0] setSelectedSat(filteredPasses[0])
calculateSatPos(filteredPasses) calculatePositions(filteredPasses)
} }
private fun calculateSatPos(passes: List<SatPass>) { fun changeSelection(decrement: Boolean) {
viewModelScope.launch(Dispatchers.Default) { val index = filteredPasses.indexOf(_selectedSat.value?.pass)
if (decrement) {
if (index > 0) setSelectedSat(filteredPasses[index - 1])
else setSelectedSat(filteredPasses[filteredPasses.size - 1])
} else {
if (index < filteredPasses.size - 1) setSelectedSat(filteredPasses[index + 1])
else setSelectedSat(filteredPasses[0])
}
}
fun setSelectedSat(pass: SatPass?) {
pass?.let {
viewModelScope.launch(Dispatchers.Default) {
val satPos = pass.predictor.getSatPos(dateNow)
val osmPos = getOsmPosition(satPos.latitude, satPos.longitude, true)
val qthLoc = Utilities.locToQTH(osmPos.lat, osmPos.lon)
val velocity = getSatVelocity(satPos.altitude)
val coverage = satPos.rangeCircleRadiusKm * 2
val footprint = getSatFootprint(pass)
val track = getSatTrack(pass)
_selectedSat.postValue(
SelectedSat(
pass, pass.tle.catnum, pass.tle.name, satPos.range,
satPos.altitude, velocity, qthLoc, osmPos, coverage, footprint, track
)
)
}
}
}
private fun calculatePositions(passes: List<SatPass>) {
val passesMap = mutableMapOf<SatPass, Position>()
viewModelScope.launch {
while (true) { while (true) {
val passesMap = mutableMapOf<SatPass, Position>()
dateNow.time = System.currentTimeMillis() dateNow.time = System.currentTimeMillis()
passes.forEach { passes.forEach {
val satPos = it.predictor.getSatPos(dateNow) val satPos = it.predictor.getSatPos(dateNow)
passesMap[it] = getOsmPosition(satPos.latitude, satPos.longitude, true) passesMap[it] = getOsmPosition(satPos.latitude, satPos.longitude, true)
} }
setSelectedSat(_selectedSat.value?.pass)
_satMarkers.postValue(passesMap) _satMarkers.postValue(passesMap)
delay(2000) delay(2000)
} }
} }
} }
fun setSelectedSat(satPass: SatPass): Boolean { private fun getSatFootprint(pass: SatPass): Overlay {
_selectedPass.value = satPass val rangePoints = mutableListOf<GeoPoint>()
return true pass.predictor.getSatPos(dateNow).rangeCircle.withIndex().forEach {
} val osmPos = getOsmPosition(it.value.lat, it.value.lon, false)
rangePoints.add(GeoPoint(osmPos.lat, osmPos.lon))
fun changeSelection(decrement: Boolean) { }
val index = filteredPasses.indexOf(_selectedPass.value) return Polygon().apply {
_selectedPass.value = if (decrement) { fillPaint.set(footprintPaint)
if (index > 0) filteredPasses[index - 1] else filteredPasses[filteredPasses.size - 1] outlinePaint.set(footprintPaint)
} else { points = rangePoints
if (index < filteredPasses.size - 1) filteredPasses[index + 1] else filteredPasses[0]
} }
} }
fun getSatVelocity(satAlt: Double): Double { private fun getSatTrack(pass: SatPass): Overlay {
val period = (24 * 60 / pass.tle.meanmo).toInt()
val positions = pass.predictor.getPositions(dateNow, 20, 0, period * 3)
val trackOverlay = FolderOverlay()
val trackPoints = mutableListOf<GeoPoint>()
var oldLon = 0.0
positions.forEach {
val osmPos = getOsmPosition(it.latitude, it.longitude, true)
if (oldLon < -170.0 && osmPos.lon > 170.0 || oldLon > 170.0 && osmPos.lon < -170.0) {
val currentPoints = mutableListOf<GeoPoint>()
currentPoints.addAll(trackPoints)
Polyline().apply {
outlinePaint.set(trackPaint)
setPoints(currentPoints)
trackOverlay.add(this)
}
trackPoints.clear()
}
oldLon = osmPos.lon
trackPoints.add(GeoPoint(osmPos.lat, osmPos.lon))
}
Polyline().apply {
outlinePaint.set(trackPaint)
setPoints(trackPoints)
trackOverlay.add(this)
}
return trackOverlay
}
private fun getSatVelocity(altitude: Double): Double {
val earthG = 6.674 * 10.0.pow(-11) val earthG = 6.674 * 10.0.pow(-11)
val earthM = 5.98 * 10.0.pow(24) val earthM = 5.98 * 10.0.pow(24)
val radius = 6.37 * 10.0.pow(6) + satAlt * 10.0.pow(3) val radius = 6.37 * 10.0.pow(6) + altitude * 10.0.pow(3)
return sqrt(earthG * earthM / radius) / 1000 return sqrt(earthG * earthM / radius) / 1000
} }
fun getOsmPosition(lat: Double, lon: Double, inRadians: Boolean): Position { private fun getOsmPosition(lat: Double, lon: Double, inRadians: Boolean): Position {
return if (inRadians) { return if (inRadians) {
var osmLat = Math.toDegrees(lat) var osmLat = Math.toDegrees(lat)
var osmLon = Math.toDegrees(lon) var osmLon = Math.toDegrees(lon)
-2
View File
@@ -25,7 +25,5 @@
<color name="greyLight">#D0D0D0</color> <color name="greyLight">#D0D0D0</color>
<color name="greyDivider">#808080</color> <color name="greyDivider">#808080</color>
<color name="greySurface">#1e1e1e</color> <color name="greySurface">#1e1e1e</color>
<!-- map colors -->
<color name="satFootprint">#26FFE082</color>
<color name="mapInfoLayout">#66000000</color> <color name="mapInfoLayout">#66000000</color>
</resources> </resources>