diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/fragments/MapViewFragment.kt b/app/src/main/java/com/rtbishop/look4sat/ui/fragments/MapViewFragment.kt index 6ea30c30..27b8fcfc 100644 --- a/app/src/main/java/com/rtbishop/look4sat/ui/fragments/MapViewFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/fragments/MapViewFragment.kt @@ -19,19 +19,14 @@ package com.rtbishop.look4sat.ui.fragments -import android.content.Context -import android.graphics.* import android.os.Bundle import android.view.View import android.widget.Toast import androidx.appcompat.app.AlertDialog -import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import com.github.amsacode.predict4java.GroundStationPosition -import com.github.amsacode.predict4java.Position -import com.github.amsacode.predict4java.SatPos import com.github.amsacode.predict4java.TLE import com.rtbishop.look4sat.Look4SatApp import com.rtbishop.look4sat.R @@ -41,13 +36,12 @@ 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.ui.views.MapView import com.rtbishop.look4sat.utility.GeneralUtils import com.rtbishop.look4sat.utility.PassPredictor import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import java.util.* import javax.inject.Inject -import kotlin.math.abs class MapViewFragment : Fragment(R.layout.fragment_map_view) { @@ -58,13 +52,14 @@ class MapViewFragment : Fragment(R.layout.fragment_map_view) { private lateinit var mapView: MapView private lateinit var predictor: PassPredictor private lateinit var selectedSat: TLE + private lateinit var binding: FragmentMapViewBinding private var gsp = GroundStationPosition(0.0, 0.0, 0.0) private var satPassList = emptyList() private var checkedItem = 0 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val binding = FragmentMapViewBinding.bind(view) + binding = FragmentMapViewBinding.bind(view) mainActivity = activity as MainActivity (mainActivity.application as Look4SatApp).appComponent.inject(this) viewModel = ViewModelProvider(mainActivity, modelFactory).get(SharedViewModel::class.java) @@ -97,7 +92,10 @@ class MapViewFragment : Fragment(R.layout.fragment_map_view) { binding.fabMap.setOnClickListener { showSelectSatDialog(satPassList) } selectedSat = satPassList.first().tle predictor = satPassList.first().predictor - mapView = MapView(mainActivity, binding) + mapView = MapView(mainActivity) + mapView.setList(satPassList) + mapView.setGSP(gsp) + mapView.setChecked(checkedItem) binding.frameMap.addView(mapView) refreshView() } else { @@ -115,6 +113,7 @@ class MapViewFragment : Fragment(R.layout.fragment_map_view) { lifecycleScope.launch { while (true) { mapView.invalidate() + setTextViewsToSelectedSatPos() delay(viewModel.getRefreshRate()) } } @@ -133,6 +132,9 @@ class MapViewFragment : Fragment(R.layout.fragment_map_view) { checkedItem = which selectedSat = list[which].tle predictor = list[which].predictor + mapView.setList(satPassList) + mapView.setGSP(gsp) + mapView.setChecked(checkedItem) mapView.invalidate() dialog.dismiss() } @@ -140,184 +142,18 @@ class MapViewFragment : Fragment(R.layout.fragment_map_view) { .show() } - inner class MapView(context: Context, private val binding: FragmentMapViewBinding) : - View(context) { - private val scale = resources.displayMetrics.density - private val groundTrackPaint = Paint().apply { - isAntiAlias = true - color = ContextCompat.getColor(mainActivity, R.color.satTrack) - style = Paint.Style.STROKE - strokeWidth = scale - } - private val satPaintPrim = Paint().apply { - isAntiAlias = true - color = ContextCompat.getColor(mainActivity, R.color.satPrimary) - style = Paint.Style.STROKE - strokeWidth = scale - } - private val satPaintSec = Paint().apply { - isAntiAlias = true - color = ContextCompat.getColor(mainActivity, R.color.satSecondary) - style = Paint.Style.STROKE - strokeWidth = scale - } - private val txtPaint = Paint().apply { - isAntiAlias = true - color = ContextCompat.getColor(mainActivity, R.color.satPrimary) - style = Paint.Style.FILL - strokeWidth = scale - textSize = 16f - setShadowLayer(2f, 2f, 2f, Color.BLACK) - } - private val outlinePaint = Paint().apply { - isAntiAlias = true - color = Color.BLACK - style = Paint.Style.FILL_AND_STROKE - strokeWidth = scale - textSize = 16f - } - private val rect = Rect() + private fun setTextViewsToSelectedSatPos() { + val currentTime = GeneralUtils.getDateFor(System.currentTimeMillis()) + val orbitalPeriod = (24 * 60 / selectedSat.meanmo).toInt() + val positions = predictor.getPositions(currentTime, 60, 0, orbitalPeriod * 3) + var lon = GeneralUtils.rad2Deg(positions[0].longitude).toFloat() + val lat = GeneralUtils.rad2Deg(positions[0].latitude).toFloat() + val rng = positions[0].range - override fun onDraw(canvas: Canvas) { - canvas.translate(width / 2f, height / 2f) - val degLon = width / 360f - val degLat = height / 180f - drawHomeLoc(canvas, degLon, degLat) - 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]) - drawGroundTrack(canvas, degLon, degLat, positions) - satPassList.forEach { - drawSat(canvas, degLon, degLat, it.tle, it.predictor, currentTime, satPaintSec) - } - drawSat( - canvas, - degLon, - degLat, - satPassList[checkedItem].tle, - satPassList[checkedItem].predictor, - currentTime, - satPaintPrim - ) - } + if (lon > 180f) lon -= 360f - private fun drawHomeLoc(canvas: Canvas, degLon: Float, degLat: Float) { - val lon = gsp.longitude.toFloat() - val lat = gsp.latitude.toFloat() * -1 - val cx = lon * degLon - val cy = lat * degLat - val gspName = context.getString(R.string.map_gsp) - txtPaint.getTextBounds(gspName, 0, gspName.length, rect) - canvas.drawCircle(cx, cy, scale * 2, txtPaint) - canvas.drawText(gspName, cx - rect.width() / 2, cy - txtPaint.textSize, outlinePaint) - canvas.drawText(gspName, cx - rect.width() / 2, cy - txtPaint.textSize, txtPaint) - - } - - private fun setTextViewsToSelectedSatPos(position: SatPos) { - var lon = GeneralUtils.rad2Deg(position.longitude).toFloat() - val lat = GeneralUtils.rad2Deg(position.latitude).toFloat() - val rng = position.range - - if (lon > 180f) lon -= 360f - - binding.tvMapLat.text = String.format(context.getString(R.string.pat_latitude), lat) - binding.tvMapLon.text = String.format(context.getString(R.string.pat_longitude), lon) - binding.tvMapRng.text = String.format(context.getString(R.string.pat_range), rng) - } - - private fun drawGroundTrack( - canvas: Canvas, - degLon: Float, - degLat: Float, - list: List - ) { - val path = Path() - var lon: Float - var lat: Float - var lastLon = 181f - - list.withIndex().forEach { (index, satPos) -> - lon = GeneralUtils.rad2Deg(satPos.longitude).toFloat() - lat = GeneralUtils.rad2Deg(satPos.latitude).toFloat() * -1 - - if (lon > 180f) lon -= 360f - - lon *= degLon - lat *= degLat - - if (index == 0 || abs(lon - lastLon) > 180) path.moveTo(lon, lat) - else path.lineTo(lon, lat) - - lastLon = lon - } - canvas.drawPath(path, groundTrackPaint) - } - - private fun drawSat( - canvas: Canvas, - degLon: Float, - degLat: Float, - tle: TLE, - predictor: PassPredictor, - date: Date, - paint: Paint - ) { - val satPosNow = predictor.getSatPos(date) - val footprintPosList = satPosNow.rangeCircle - drawFootprint(canvas, degLon, degLat, footprintPosList, paint) - drawName(canvas, degLon, degLat, satPosNow, tle.name) - } - - private fun drawFootprint( - canvas: Canvas, - degLon: Float, - degLat: Float, - list: List, - paint: Paint - ) { - val path = Path() - var lon: Float - var lat: Float - var lastLon = 181f - - list.withIndex().forEach { (index, position) -> - lon = position.lon.toFloat() - lat = position.lat.toFloat() * -1 - - if (lon > 180f) lon -= 360f - - lon *= degLon - lat *= degLat - - if (index == 0 || abs(lon - lastLon) > 180) path.moveTo(lon, lat) - else path.lineTo(lon, lat) - - lastLon = lon - } - canvas.drawPath(path, paint) - } - - private fun drawName( - canvas: Canvas, - degLon: Float, - degLat: Float, - position: SatPos, - name: String - ) { - var lon = GeneralUtils.rad2Deg(position.longitude).toFloat() - val lat = GeneralUtils.rad2Deg(position.latitude).toFloat() * -1 - - if (lon > 180f) lon -= 360f - - val cx = lon * degLon - val cy = lat * degLat - - canvas.drawCircle(cx, cy, scale * 2, txtPaint) - txtPaint.getTextBounds(name, 0, name.length, rect) - canvas.drawText(name, cx - rect.width() / 2, cy - txtPaint.textSize, outlinePaint) - canvas.drawText(name, cx - rect.width() / 2, cy - txtPaint.textSize, txtPaint) - } + binding.tvMapLat.text = String.format(mainActivity.getString(R.string.pat_latitude), lat) + binding.tvMapLon.text = String.format(mainActivity.getString(R.string.pat_longitude), lon) + binding.tvMapRng.text = String.format(mainActivity.getString(R.string.pat_range), rng) } } diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/views/MapView.kt b/app/src/main/java/com/rtbishop/look4sat/ui/views/MapView.kt new file mode 100644 index 00000000..3d82db8f --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/ui/views/MapView.kt @@ -0,0 +1,170 @@ +package com.rtbishop.look4sat.ui.views + +import android.content.Context +import android.graphics.* +import android.view.View +import androidx.core.content.ContextCompat +import com.github.amsacode.predict4java.GroundStationPosition +import com.github.amsacode.predict4java.Position +import com.github.amsacode.predict4java.SatPos +import com.github.amsacode.predict4java.TLE +import com.rtbishop.look4sat.R +import com.rtbishop.look4sat.data.SatPass +import com.rtbishop.look4sat.utility.GeneralUtils +import java.util.* +import kotlin.math.abs + +class MapView(context: Context) : View(context) { + + private lateinit var passList: List + private lateinit var gsp: GroundStationPosition + private lateinit var selectedSat: TLE + private val scale = resources.displayMetrics.density + private val groundTrackPaint = Paint().apply { + isAntiAlias = true + color = ContextCompat.getColor(context, R.color.satTrack) + style = Paint.Style.STROKE + strokeWidth = scale + } + private val satPaintPrim = Paint().apply { + isAntiAlias = true + color = ContextCompat.getColor(context, R.color.satPrimary) + style = Paint.Style.STROKE + strokeWidth = scale + } + private val satPaintSec = Paint().apply { + isAntiAlias = true + color = ContextCompat.getColor(context, R.color.satSecondary) + style = Paint.Style.STROKE + strokeWidth = scale + } + private val txtPaint = Paint().apply { + isAntiAlias = true + color = ContextCompat.getColor(context, R.color.satPrimary) + style = Paint.Style.FILL + strokeWidth = scale + textSize = 16f + setShadowLayer(2f, 2f, 2f, Color.BLACK) + } + private val outlinePaint = Paint().apply { + isAntiAlias = true + color = Color.BLACK + style = Paint.Style.FILL_AND_STROKE + strokeWidth = scale + textSize = 16f + } + private val rect = Rect() + private var checked = 0 + private var degLon = 0f + private var degLat = 0f + + fun setList(satPassList: List) { + passList = satPassList + } + + fun setGSP(groundStationPosition: GroundStationPosition) { + gsp = groundStationPosition + } + + fun setChecked(item: Int) { + checked = item + selectedSat = passList[checked].tle + } + + override fun onDraw(canvas: Canvas) { + degLon = width / 360f + degLat = height / 180f + canvas.translate(width / 2f, height / 2f) + drawHomeLoc(canvas) + val currentTime = GeneralUtils.getDateFor(System.currentTimeMillis()) + val orbitalPeriod = (24 * 60 / selectedSat.meanmo).toInt() + val predictor = passList[checked].predictor + val positions = predictor.getPositions(currentTime, 60, 0, orbitalPeriod * 3) + drawGroundTrack(canvas, positions) + passList.forEach { satPass -> drawSat(canvas, satPass, currentTime, satPaintSec) } + drawSat(canvas, passList[checked], currentTime, satPaintPrim) + } + + private fun drawHomeLoc(canvas: Canvas) { + val lon = gsp.longitude.toFloat() + val lat = gsp.latitude.toFloat() * -1 + val cx = lon * degLon + val cy = lat * degLat + val gspName = context.getString(R.string.map_gsp) + txtPaint.getTextBounds(gspName, 0, gspName.length, rect) + canvas.drawCircle(cx, cy, scale * 2, txtPaint) + canvas.drawText(gspName, cx - rect.width() / 2, cy - txtPaint.textSize, outlinePaint) + canvas.drawText(gspName, cx - rect.width() / 2, cy - txtPaint.textSize, txtPaint) + + } + + private fun drawGroundTrack(canvas: Canvas, list: List) { + val path = Path() + var lon: Float + var lat: Float + var lastLon = 181f + + list.withIndex().forEach { (index, satPos) -> + lon = GeneralUtils.rad2Deg(satPos.longitude).toFloat() + lat = GeneralUtils.rad2Deg(satPos.latitude).toFloat() * -1 + + if (lon > 180f) lon -= 360f + + lon *= degLon + lat *= degLat + + if (index == 0 || abs(lon - lastLon) > 180) path.moveTo(lon, lat) + else path.lineTo(lon, lat) + + lastLon = lon + } + canvas.drawPath(path, groundTrackPaint) + } + + private fun drawSat(canvas: Canvas, satPass: SatPass, date: Date, paint: Paint) { + val tle = satPass.tle + val predictor = satPass.predictor + val satPosNow = predictor.getSatPos(date) + val footprintPosList = satPosNow.rangeCircle + drawFootprint(canvas, footprintPosList, paint) + drawName(canvas, satPosNow, tle.name) + } + + private fun drawFootprint(canvas: Canvas, list: List, paint: Paint) { + val path = Path() + var lon: Float + var lat: Float + var lastLon = 181f + + list.withIndex().forEach { (index, position) -> + lon = position.lon.toFloat() + lat = position.lat.toFloat() * -1 + + if (lon > 180f) lon -= 360f + + lon *= degLon + lat *= degLat + + if (index == 0 || abs(lon - lastLon) > 180) path.moveTo(lon, lat) + else path.lineTo(lon, lat) + + lastLon = lon + } + canvas.drawPath(path, paint) + } + + private fun drawName(canvas: Canvas, position: SatPos, name: String) { + var lon = GeneralUtils.rad2Deg(position.longitude).toFloat() + val lat = GeneralUtils.rad2Deg(position.latitude).toFloat() * -1 + + if (lon > 180f) lon -= 360f + + val cx = lon * degLon + val cy = lat * degLat + + canvas.drawCircle(cx, cy, scale * 2, txtPaint) + txtPaint.getTextBounds(name, 0, name.length, rect) + canvas.drawText(name, cx - rect.width() / 2, cy - txtPaint.textSize, outlinePaint) + canvas.drawText(name, cx - rect.width() / 2, cy - txtPaint.textSize, txtPaint) + } +} \ No newline at end of file