From ae08c7c937ca0722891f0913d96994e86892e914 Mon Sep 17 00:00:00 2001 From: Arty Bishop Date: Mon, 7 Mar 2022 14:31:28 +0000 Subject: [PATCH] Added satellite visibility parameter to SatPos.kt #62 --- .../look4sat/framework/SettingsManager.kt | 6 +- .../presentation/mapScreen/MapFragment.kt | 19 +-- .../presentation/mapScreen/MapViewModel.kt | 25 +-- .../presentation/passesScreen/FilterDialog.kt | 2 +- .../presentation/radarScreen/RadarFragment.kt | 5 +- .../presentation/radarScreen/RadarView.kt | 18 +- .../radarScreen/RadarViewModel.kt | 9 +- .../settingsScreen/PositionDialog.kt | 4 +- .../settingsScreen/SettingsFragment.kt | 4 +- .../look4sat/domain/predict/DeepSpaceSat.kt | 9 +- .../look4sat/domain/predict/GeoPos.kt | 2 +- .../look4sat/domain/predict/NearEarthSat.kt | 2 +- .../look4sat/domain/predict/SatPos.kt | 49 ++---- .../look4sat/domain/predict/Satellite.kt | 154 +++++++++++++----- .../domain/predict/SatelliteManager.kt | 19 ++- .../rtbishop/look4sat/utility/Extensions.kt | 6 + .../com/rtbishop/look4sat/QthConverterTest.kt | 6 +- 17 files changed, 202 insertions(+), 137 deletions(-) diff --git a/app/src/main/java/com/rtbishop/look4sat/framework/SettingsManager.kt b/app/src/main/java/com/rtbishop/look4sat/framework/SettingsManager.kt index ed04678e..d353c008 100644 --- a/app/src/main/java/com/rtbishop/look4sat/framework/SettingsManager.kt +++ b/app/src/main/java/com/rtbishop/look4sat/framework/SettingsManager.kt @@ -61,8 +61,8 @@ class SettingsManager @Inject constructor(private val prefs: SharedPreferences) override fun saveStationPosition(position: GeoPos) { prefs.edit { - putString(keyLatitude, position.latitude.toString()) - putString(keyLongitude, position.longitude.toString()) + putString(keyLatitude, position.lat.toString()) + putString(keyLongitude, position.lon.toString()) } } @@ -77,7 +77,7 @@ class SettingsManager @Inject constructor(private val prefs: SharedPreferences) } override fun getHoursAhead(): Int { - return prefs.getInt(keyHoursAhead, 8) + return prefs.getInt(keyHoursAhead, 24) } override fun setHoursAhead(hoursAhead: Int) { diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapFragment.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapFragment.kt index cc772d96..ed77d6e6 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapFragment.kt @@ -119,7 +119,7 @@ class MapFragment : Fragment(R.layout.fragment_map) { setInfoWindow(null) setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_position) - position = GeoPoint(stationPos.latitude, stationPos.longitude) + position = GeoPoint(stationPos.lat, stationPos.lon) mapView.overlays[0] = this mapView.invalidate() } @@ -135,7 +135,7 @@ class MapFragment : Fragment(R.layout.fragment_map) { setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER) icon = getCustomTextIcon(it.key.data.name) try { - position = GeoPoint(it.value.latitude, it.value.longitude) + position = GeoPoint(it.value.lat, it.value.lon) } catch (exception: IllegalArgumentException) { println(exception.stackTraceToString()) } @@ -165,10 +165,10 @@ class MapFragment : Fragment(R.layout.fragment_map) { } private fun handleTrack(satTrack: List>) { - val center = GeoPoint(satTrack[0][0].latitude, satTrack[0][0].longitude) + val center = GeoPoint(satTrack[0][0].lat, satTrack[0][0].lon) val trackOverlay = FolderOverlay() satTrack.forEach { track -> - val trackPoints = track.map { GeoPoint(it.latitude, it.longitude) } + val trackPoints = track.map { GeoPoint(it.lat, it.lon) } Polyline().apply { try { setPoints(trackPoints) @@ -184,12 +184,7 @@ class MapFragment : Fragment(R.layout.fragment_map) { } private fun handleFootprint(satPos: SatPos) { - val center = GeoPoint(Math.toDegrees(satPos.latitude), Math.toDegrees(satPos.longitude)) - val radiusM = satPos.getRangeCircleRadiusKm() * 1000 - val footprintPoints = mutableListOf() - for (i in 0..720) { - footprintPoints.add(center.destinationPoint(radiusM, i.toDouble())) - } + val footprintPoints = satPos.getRangeCircle().map { GeoPoint(it.lat, it.lon) } val footprintOverlay = Polyline().apply { outlinePaint.set(footprintPaint) try { @@ -210,9 +205,9 @@ class MapFragment : Fragment(R.layout.fragment_map) { mapDataDst.text = String.format(getString(R.string.map_distance), mapData.range) mapDataVel.text = String.format(getString(R.string.map_velocity), mapData.velocity) mapDataLat.text = - String.format(getString(R.string.map_latitude), mapData.osmPos.latitude) + String.format(getString(R.string.map_latitude), mapData.osmPos.lat) mapDataLon.text = - String.format(getString(R.string.map_longitude), mapData.osmPos.longitude) + String.format(getString(R.string.map_longitude), mapData.osmPos.lon) } binding.mapView.invalidate() } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapViewModel.kt index 2fd4feff..5d47b8df 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapViewModel.kt @@ -25,6 +25,7 @@ import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.domain.predict.SatPos import com.rtbishop.look4sat.domain.predict.Satellite import com.rtbishop.look4sat.utility.QthConverter +import com.rtbishop.look4sat.utility.toDegrees import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.* import java.util.* @@ -46,8 +47,8 @@ class MapViewModel @Inject constructor( private lateinit var selectedSatellite: Satellite val stationPos = liveData { - val osmLat = clipLat(stationPosition.latitude) - val osmLon = clipLon(stationPosition.longitude) + val osmLat = clipLat(stationPosition.lat) + val osmLon = clipLon(stationPosition.lon) emit(GeoPos(osmLat, osmLon)) } @@ -116,23 +117,23 @@ class MapViewModel @Inject constructor( val endDate = Date(date.time + (satellite.orbitalPeriod * 2.4 * 60000L).toLong()) var oldLongitude = 0.0 satelliteManager.getTrack(satellite, pos, date.time, endDate.time).forEach { satPos -> - val osmLat = clipLat(Math.toDegrees(satPos.latitude)) - val osmLon = clipLon(Math.toDegrees(satPos.longitude)) + val osmLat = clipLat(satPos.latitude.toDegrees()) + val osmLon = clipLon(satPos.longitude.toDegrees()) val currentPosition = GeoPos(osmLat, osmLon) - if (oldLongitude < -170.0 && currentPosition.longitude > 170.0) { + if (oldLongitude < -170.0 && currentPosition.lon > 170.0) { // adding left terminal position currentTrack.add(GeoPos(osmLat, -180.0)) val finishedTrack = mutableListOf().apply { addAll(currentTrack) } satTracks.add(finishedTrack) currentTrack.clear() - } else if (oldLongitude > 170.0 && currentPosition.longitude < -170.0) { + } else if (oldLongitude > 170.0 && currentPosition.lon < -170.0) { // adding right terminal position currentTrack.add(GeoPos(osmLat, 180.0)) val finishedTrack = mutableListOf().apply { addAll(currentTrack) } satTracks.add(finishedTrack) currentTrack.clear() } - oldLongitude = currentPosition.longitude + oldLongitude = currentPosition.lon currentTrack.add(currentPosition) } satTracks.add(currentTrack) @@ -143,8 +144,8 @@ class MapViewModel @Inject constructor( val positions = mutableMapOf() satellites.forEach { satellite -> val satPos = satelliteManager.getPosition(satellite, pos, date.time) - val osmLat = clipLat(Math.toDegrees(satPos.latitude)) - val osmLon = clipLon(Math.toDegrees(satPos.longitude)) + val osmLat = clipLat(satPos.latitude.toDegrees()) + val osmLon = clipLon(satPos.longitude.toDegrees()) positions[satellite] = GeoPos(osmLat, osmLon) } _positions.postValue(positions) @@ -157,10 +158,10 @@ class MapViewModel @Inject constructor( private suspend fun getSatData(satellite: Satellite, pos: GeoPos, date: Date) { val satPos = satelliteManager.getPosition(satellite, pos, date.time) - val osmLat = clipLat(Math.toDegrees(satPos.latitude)) - val osmLon = clipLon(Math.toDegrees(satPos.longitude)) + val osmLat = clipLat(satPos.latitude.toDegrees()) + val osmLon = clipLon(satPos.longitude.toDegrees()) val osmPos = GeoPos(osmLat, osmLon) - val qthLoc = QthConverter.positionToQth(osmPos.latitude, osmPos.longitude) ?: "-- --" + val qthLoc = QthConverter.positionToQth(osmPos.lat, osmPos.lon) ?: "-- --" val satData = MapData( satellite, satellite.data.catnum, satellite.data.name, satPos.distance, satPos.altitude, satPos.getOrbitalVelocity(), qthLoc, osmPos diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/FilterDialog.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/FilterDialog.kt index 3a389d54..ec0035c7 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/FilterDialog.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/FilterDialog.kt @@ -54,7 +54,7 @@ class FilterDialog : AppCompatDialogFragment() { val hoursAhead = try { filterHoursEdit.text.toString().toInt() } catch (exception: Exception) { - 8 + 24 } val minElevation = try { filterElevEdit.text.toString().toDouble() diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarFragment.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarFragment.kt index 6884bb64..3edcfb34 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarFragment.kt @@ -30,6 +30,7 @@ import com.rtbishop.look4sat.R import com.rtbishop.look4sat.databinding.FragmentRadarBinding import com.rtbishop.look4sat.domain.predict.SatPass import com.rtbishop.look4sat.domain.predict.SatPos +import com.rtbishop.look4sat.utility.toDegrees import com.rtbishop.look4sat.utility.toTimerString import dagger.hilt.android.AndroidEntryPoint @@ -121,8 +122,8 @@ class RadarFragment : Fragment(R.layout.fragment_radar) { val radarAlt = getString(R.string.radar_alt_value) val radarDist = getString(R.string.radar_dist_value) val radarId = getString(R.string.radar_sat_id) - radarAzValue.text = String.format(radarAzim, Math.toDegrees(satPos.azimuth)) - radarElValue.text = String.format(radarElev, Math.toDegrees(satPos.elevation)) + radarAzValue.text = String.format(radarAzim, satPos.azimuth.toDegrees()) + radarElValue.text = String.format(radarElev, satPos.elevation.toDegrees()) radarAltValue.text = String.format(radarAlt, satPos.altitude) radarDstValue.text = String.format(radarDist, satPos.distance) radarSatId.text = String.format(radarId, satPass.catNum) diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarView.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarView.kt index 976c6d6a..e51ccffa 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarView.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarView.kt @@ -22,7 +22,10 @@ import android.graphics.* import android.view.View import androidx.core.content.ContextCompat import com.rtbishop.look4sat.R +import com.rtbishop.look4sat.domain.predict.PI_2 import com.rtbishop.look4sat.domain.predict.SatPos +import com.rtbishop.look4sat.domain.predict.TWO_PI +import com.rtbishop.look4sat.utility.toRadians import kotlin.math.cos import kotlin.math.min import kotlin.math.sin @@ -31,7 +34,6 @@ class RadarView(context: Context) : View(context) { private val defaultColor = ContextCompat.getColor(context, R.color.accent) private val scale = resources.displayMetrics.density - private val piDiv2 = Math.PI / 2.0 private val strokeSize = scale * 2f private var position: SatPos? = null private var positions: List = emptyList() @@ -186,8 +188,8 @@ class RadarView(context: Context) : View(context) { } private fun drawCrosshair(canvas: Canvas, azimuth: Float, pitch: Float, radarRadius: Float) { - val azimuthRad = Math.toRadians(azimuth.toDouble()) - val tmpElevation = Math.toRadians(pitch.toDouble()) + val azimuthRad = azimuth.toDouble().toRadians() + val tmpElevation = pitch.toDouble().toRadians() val elevationRad = if (tmpElevation > 0.0) 0.0 else tmpElevation val crossX = sph2CartX(azimuthRad, -elevationRad, radarRadius.toDouble()) val crossY = sph2CartY(azimuthRad, -elevationRad, radarRadius.toDouble()) @@ -197,13 +199,13 @@ class RadarView(context: Context) : View(context) { } private fun sph2CartX(azimuth: Double, elevation: Double, r: Double): Float { - val radius = r * (piDiv2 - elevation) / piDiv2 - return (radius * cos(piDiv2 - azimuth)).toFloat() + val radius = r * (PI_2 - elevation) / PI_2 + return (radius * cos(PI_2 - azimuth)).toFloat() } private fun sph2CartY(azimuth: Double, elevation: Double, r: Double): Float { - val radius = r * (piDiv2 - elevation) / piDiv2 - return (radius * sin(piDiv2 - azimuth)).toFloat() + val radius = r * (PI_2 - elevation) / PI_2 + return (radius * sin(PI_2 - azimuth)).toFloat() } private fun createPassTrajectory(radarRadius: Float) { @@ -221,7 +223,7 @@ class RadarView(context: Context) : View(context) { private fun createPassTrajectoryArrow() { val radius = beaconSize val sides = 3 - val angle = 2.0 * Math.PI / sides + val angle = TWO_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() diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarViewModel.kt index 33b2106f..2cf6af1b 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarViewModel.kt @@ -29,6 +29,7 @@ import com.rtbishop.look4sat.domain.predict.SatPos import com.rtbishop.look4sat.framework.OrientationManager import com.rtbishop.look4sat.utility.DataReporter import com.rtbishop.look4sat.utility.round +import com.rtbishop.look4sat.utility.toDegrees import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.isActive @@ -81,8 +82,8 @@ class RadarViewModel @Inject constructor( } private fun getMagDeclination(geoPos: GeoPos, time: Long = System.currentTimeMillis()): Float { - val latitude = geoPos.latitude.toFloat() - val longitude = geoPos.longitude.toFloat() + val latitude = geoPos.lat.toFloat() + val longitude = geoPos.lon.toFloat() return GeomagneticField(latitude, longitude, 0f, time).declination } @@ -99,8 +100,8 @@ class RadarViewModel @Inject constructor( if (settings.getRotatorEnabled()) { val server = settings.getRotatorServer() val port = settings.getRotatorPort().toInt() - val azimuth = Math.toDegrees(satPos.azimuth).round(1) - val elevation = Math.toDegrees(satPos.elevation).round(1) + val azimuth = satPos.azimuth.toDegrees().round(1) + val elevation = satPos.elevation.toDegrees().round(1) reporter.reportRotation(server, port, azimuth, elevation) } _passData.postValue(RadarData(satPos, satTrack)) diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/PositionDialog.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/PositionDialog.kt index b10aeb81..aec4b3a9 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/PositionDialog.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/PositionDialog.kt @@ -49,8 +49,8 @@ class PositionDialog : AppCompatDialogFragment() { WindowManager.LayoutParams.WRAP_CONTENT ) val location = preferences.loadStationPosition() - positionLatEdit.setText(location.latitude.toString()) - positionLonEdit.setText(location.longitude.toString()) + positionLatEdit.setText(location.lat.toString()) + positionLonEdit.setText(location.lon.toString()) positionBtnPos.setOnClickListener { val latitude = try { positionLatEdit.text.toString().toDouble() diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsFragment.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsFragment.kt index 4158e399..91666705 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsFragment.kt @@ -209,8 +209,8 @@ class SettingsFragment : Fragment(R.layout.fragment_settings) { binding.run { val latFormat = getString(R.string.location_lat) val lonFormat = getString(R.string.location_lon) - settingsLocation.locationLat.text = String.format(latFormat, geoPos.latitude) - settingsLocation.locationLon.text = String.format(lonFormat, geoPos.longitude) + settingsLocation.locationLat.text = String.format(latFormat, geoPos.lat) + settingsLocation.locationLon.text = String.format(lonFormat, geoPos.lon) } } diff --git a/base/src/main/java/com/rtbishop/look4sat/domain/predict/DeepSpaceSat.kt b/base/src/main/java/com/rtbishop/look4sat/domain/predict/DeepSpaceSat.kt index 98fb8dab..a425e9a2 100644 --- a/base/src/main/java/com/rtbishop/look4sat/domain/predict/DeepSpaceSat.kt +++ b/base/src/main/java/com/rtbishop/look4sat/domain/predict/DeepSpaceSat.kt @@ -111,7 +111,6 @@ class DeepSpaceSat(data: OrbitalData) : Satellite(data) { val xl = dsv.xll + dsv.omgadf + dsv.xnode val beta = sqrt(1.0 - dsv.em * dsv.em) dsv.xn = XKE / a.pow(1.5) - // Long period periodics val axn = dsv.em * cos(dsv.omgadf) temp[0] = invert(a * beta * beta) @@ -119,12 +118,12 @@ class DeepSpaceSat(data: OrbitalData) : Satellite(data) { val aynl = temp[0] * aycof val xlt = xl + xll val ayn = dsv.em * sin(dsv.omgadf) + aynl - // Solve Kepler's equation val capu = mod2PI(xlt - dsv.xnode) temp[2] = capu converge(temp, axn, ayn, capu) calculatePosAndVel(temp, a, axn, ayn) + calculatePhase(xlt, dsv.xnode, dsv.omgadf) } } @@ -149,7 +148,6 @@ class DeepSpaceSat(data: OrbitalData) : Satellite(data) { temp[0] = invert(pl) temp[1] = CK2 * temp[0] temp[2] = temp[1] * temp[0] - // Update for short periodics val rk = temp[9] * (1.0 - 1.5 * temp[2] * betal * x3thm1) + 0.5 * temp[1] * x1mth2 * cos2u val uk = u - 0.25 * temp[2] * x7thm1 * sin2u @@ -161,6 +159,7 @@ class DeepSpaceSat(data: OrbitalData) : Satellite(data) { } inner class DeepSpaceValueObject { + var eosq = 0.0 var sinio = 0.0 var cosio = 0.0 @@ -174,7 +173,6 @@ class DeepSpaceSat(data: OrbitalData) : Satellite(data) { var omgdot = 0.0 var xnodot = 0.0 var xnodp = 0.0 - // Used by dpsec and dpper parts of Deep() var xll = 0.0 var omgadf = 0.0 @@ -183,7 +181,6 @@ class DeepSpaceSat(data: OrbitalData) : Satellite(data) { var xinc = 0.0 var xn = 0.0 var t = 0.0 - // Used by thetg and Deep() var ds50 = 0.0 } @@ -212,14 +209,12 @@ class DeepSpaceSat(data: OrbitalData) : Satellite(data) { private val g44 = 1.8014998 private val g52 = 1.0508330 private val g54 = 4.4108898 - private val thgr: Double private val xnq: Double private val xqncl: Double private val omegaq: Double private var zmol = 0.0 private var zmos = 0.0 - // Many fields below cannot be final because they are iteratively refined private var savtsn = 0.0 private var ee2 = 0.0 diff --git a/base/src/main/java/com/rtbishop/look4sat/domain/predict/GeoPos.kt b/base/src/main/java/com/rtbishop/look4sat/domain/predict/GeoPos.kt index bb18e5cd..c21889e5 100644 --- a/base/src/main/java/com/rtbishop/look4sat/domain/predict/GeoPos.kt +++ b/base/src/main/java/com/rtbishop/look4sat/domain/predict/GeoPos.kt @@ -17,4 +17,4 @@ */ package com.rtbishop.look4sat.domain.predict -data class GeoPos(val latitude: Double, val longitude: Double, val name: String? = null) +data class GeoPos(val lat: Double, val lon: Double, val alt: Double = 0.0, val name: String? = null) diff --git a/base/src/main/java/com/rtbishop/look4sat/domain/predict/NearEarthSat.kt b/base/src/main/java/com/rtbishop/look4sat/domain/predict/NearEarthSat.kt index 6c64dfdf..e195c602 100644 --- a/base/src/main/java/com/rtbishop/look4sat/domain/predict/NearEarthSat.kt +++ b/base/src/main/java/com/rtbishop/look4sat/domain/predict/NearEarthSat.kt @@ -181,6 +181,7 @@ class NearEarthSat(data: OrbitalData) : Satellite(data) { temp[2] = capu converge(temp, axn, ayn, capu) calculatePosAndVel(temp, xnode, a, xn, axn, ayn) + calculatePhase(xlt, xnode, omgadf) } } @@ -208,7 +209,6 @@ class NearEarthSat(data: OrbitalData) : Satellite(data) { temp[0] = invert(pl) temp[1] = CK2 * temp[0] temp[2] = temp[1] * temp[0] - // Update for short periodics val rk = r * (1.0 - 1.5 * temp[2] * betal * x3thm1) + 0.5 * temp[1] * x1mth2 * cos2u val uk = u - 0.25 * temp[2] * x7thm1 * sin2u diff --git a/base/src/main/java/com/rtbishop/look4sat/domain/predict/SatPos.kt b/base/src/main/java/com/rtbishop/look4sat/domain/predict/SatPos.kt index e54c1230..fd40a870 100644 --- a/base/src/main/java/com/rtbishop/look4sat/domain/predict/SatPos.kt +++ b/base/src/main/java/com/rtbishop/look4sat/domain/predict/SatPos.kt @@ -28,7 +28,11 @@ data class SatPos( var distance: Double = 0.0, var distanceRate: Double = 0.0, var theta: Double = 0.0, - var time: Long = 0L + var time: Long = 0L, + var phase: Double = 0.0, + var eclipseDepth: Double = 0.0, + var eclipsed: Boolean = false, + var aboveHorizon: Boolean = false ) { fun getDownlinkFreq(freq: Long): Long { @@ -47,38 +51,17 @@ data class SatPos( } fun getRangeCircle(): List { - val positions = mutableListOf() - val beta = acos(EARTH_RADIUS / (EARTH_RADIUS + altitude)) - var tempAzimuth = 0 - while (tempAzimuth < 360) { - val azimuth = tempAzimuth / 360.0 * 2.0 * Math.PI - var lat = asin(sin(latitude) * cos(beta) + cos(azimuth) * sin(beta) * cos(latitude)) - val num = (cos(beta) - (sin(latitude) * sin(lat))) - val den = cos(latitude) * cos(lat) - var lon = if (tempAzimuth == 0 && (beta > ((Math.PI / 2.0) - latitude))) { - longitude + Math.PI - } else if (tempAzimuth == 180 && (beta > ((Math.PI / 2.0) - latitude))) { - longitude + Math.PI - } else if (abs(num / den) > 1.0) { - longitude - } else { - if ((180 - tempAzimuth) >= 0) { - longitude - acos(num / den) - } else { - longitude + acos(num / den) - } - } - while (lon < 0.0) lon += Math.PI * 2.0 - while (lon > Math.PI * 2.0) lon -= Math.PI * 2.0 - lat = Math.toDegrees(lat) - lon = Math.toDegrees(lon) - positions.add(GeoPos(lat, lon)) - tempAzimuth += 1 + val rangeCirclePoints = mutableListOf() + val beta = acos(EARTH_RADIUS / (EARTH_RADIUS + altitude)) // * EARTH_RADIUS = radiusKm + for (azimuth in 0..720) { + val rads = azimuth * DEG2RAD + val lat = asin(sin(latitude) * cos(beta) + (cos(latitude) * sin(beta) * cos(rads))) + val lon = (longitude + atan2( + sin(rads) * sin(beta) * cos(latitude), + cos(beta) - sin(latitude) * sin(lat) + )) + rangeCirclePoints.add(GeoPos(lat * RAD2DEG, lon * RAD2DEG)) } - return positions - } - - fun getRangeCircleRadiusKm(): Double { - return EARTH_RADIUS * acos(EARTH_RADIUS / (EARTH_RADIUS + altitude)) + return rangeCirclePoints } } diff --git a/base/src/main/java/com/rtbishop/look4sat/domain/predict/Satellite.kt b/base/src/main/java/com/rtbishop/look4sat/domain/predict/Satellite.kt index 2e7e6035..b99a6110 100644 --- a/base/src/main/java/com/rtbishop/look4sat/domain/predict/Satellite.kt +++ b/base/src/main/java/com/rtbishop/look4sat/domain/predict/Satellite.kt @@ -19,16 +19,19 @@ package com.rtbishop.look4sat.domain.predict import kotlin.math.* -const val PI = 3.141592653589793 +const val ASTRONOMICAL_UNIT = 1.49597870691E8 const val DEG2RAD = 0.017453292519943295 const val RAD2DEG = 57.29577951308232 const val EARTH_RADIUS = 6378.137 const val EPSILON = 1.0E-12 -const val FLAT_FACTOR = 3.35281066474748E-3 +const val FLAT_FACT = 3.35281066474748E-3 const val J3_HARMONIC = -2.53881E-6 const val MIN_PER_DAY = 1.44E3 const val SEC_PER_DAY = 8.6400E4 +const val SOLAR_RADIUS = 6.96000E5 const val SPEED_OF_LIGHT = 2.99792458E8 +const val PI = 3.141592653589793 +const val PI_2 = PI / 2.0 const val TWO_PI = PI * 2.0 const val TWO_THIRDS = 2.0 / 3.0 const val CK2 = 5.413079E-4 @@ -39,7 +42,10 @@ abstract class Satellite(val data: OrbitalData) { private val position = Vector4() private val velocity = Vector4() + private var satPos = SatPos() + private var eclipseDepth = 0.0 private var gsPosTheta = 0.0 + private var julUTC = 0.0 private var perigee = 0.0 val orbitalPeriod = 24 * 60 / data.meanmo var qoms24 = 0.0 @@ -52,14 +58,14 @@ abstract class Satellite(val data: OrbitalData) { val apogee = sma * (1.0 + data.eccn) - EARTH_RADIUS var lin = data.incl if (lin >= 90.0) lin = 180.0 - lin - acos(EARTH_RADIUS / (apogee + EARTH_RADIUS)) + lin * DEG2RAD > abs(pos.latitude * DEG2RAD) + acos(EARTH_RADIUS / (apogee + EARTH_RADIUS)) + lin * DEG2RAD > abs(pos.lat * DEG2RAD) } } internal fun getPosition(pos: GeoPos, time: Long): SatPos { - val satPos = SatPos() + satPos = SatPos() // Date/time at which the position and velocity were calculated - val julUTC = calcCurrentDaynum(time) + 2444238.5 + julUTC = calcCurrentDaynum(time) + 2444238.5 // Convert satellite's epoch time to Julian and calculate time since epoch in minutes val julEpoch = juliandDateOfEpoch(data.epoch) val tsince = (julUTC - julEpoch) * MIN_PER_DAY @@ -70,16 +76,17 @@ abstract class Satellite(val data: OrbitalData) { magnitude(velocity) val squintVector = Vector4() // Angles in rads, dist in km, vel in km/S. Calculate sat Az, El, Range and Range-rate. - calculateObs(julUTC, position, velocity, pos, squintVector, satPos) - calculateLatLonAlt(julUTC, satPos, position) - return satPos.apply { this.time = time } + calculateObs(julUTC, position, velocity, pos, squintVector) + calculateLatLonAlt(julUTC) + satPos.time = time + satPos.eclipsed = isEclipsed() + satPos.eclipseDepth = eclipseDepth + return satPos } - // Read the system clock and return the number of days since 31Dec79 00:00:00 UTC (daynum 0) private fun calcCurrentDaynum(now: Long): Double { val then = 315446400000 // time in millis on 31Dec79 00:00:00 UTC (daynum 0) - val millis = now - then - return millis / 1000.0 / 60.0 / 60.0 / 24.0 + return (now - then) / 1000.0 / 60.0 / 60.0 / 24.0 } private fun juliandDateOfEpoch(epoch: Double): Double { @@ -117,8 +124,7 @@ abstract class Satellite(val data: OrbitalData) { positionVector: Vector4, velocityVector: Vector4, gsPos: GeoPos, - squintVector: Vector4, - satPos: SatPos + squintVector: Vector4 ) { val obsPos = Vector4() val obsVel = Vector4() @@ -138,8 +144,8 @@ abstract class Satellite(val data: OrbitalData) { velocityVector.z - obsVel.z ) magnitude(range) - val sinLat = sin(DEG2RAD * gsPos.latitude) - val cosLat = cos(DEG2RAD * gsPos.latitude) + val sinLat = sin(DEG2RAD * gsPos.lat) + val cosLat = cos(DEG2RAD * gsPos.lat) val sinTheta = sin(gsPosTheta) val cosTheta = cos(gsPosTheta) val topS = sinLat * cosTheta * range.x + sinLat * sinTheta * range.y - cosLat * range.z @@ -152,6 +158,9 @@ abstract class Satellite(val data: OrbitalData) { satPos.elevation = asin(topZ / range.w) satPos.distance = range.w satPos.distanceRate = dot(range, rgvel) / range.w + var elevation = satPos.elevation / TWO_PI * 360.0 + if (elevation > 90) elevation = 180 - elevation + satPos.aboveHorizon = elevation - 0 > EPSILON } // Returns the ECI position and velocity of the observer @@ -162,14 +171,13 @@ abstract class Satellite(val data: OrbitalData) { obsVel: Vector4 ) { val mFactor = 7.292115E-5 - gsPosTheta = mod2PI(thetaGJD(time) + DEG2RAD * gsPos.longitude) - val c = - invert(sqrt(1.0 + FLAT_FACTOR * (FLAT_FACTOR - 2) * sqr(sin(DEG2RAD * gsPos.latitude)))) - val sq = sqr(1.0 - FLAT_FACTOR) * c - val achcp = (EARTH_RADIUS * c) * cos(DEG2RAD * gsPos.latitude) + gsPosTheta = mod2PI(thetaGJD(time) + DEG2RAD * gsPos.lon) + val c = invert(sqrt(1.0 + FLAT_FACT * (FLAT_FACT - 2) * sqr(sin(DEG2RAD * gsPos.lat)))) + val sq = sqr(1.0 - FLAT_FACT) * c + val achcp = (EARTH_RADIUS * c + gsPos.alt / 1000.0) * cos(DEG2RAD * gsPos.lat) obsPos.setXYZ( achcp * cos(gsPosTheta), achcp * sin(gsPosTheta), - (EARTH_RADIUS * sq) * sin(DEG2RAD * gsPos.latitude) + (EARTH_RADIUS * sq + gsPos.alt / 1000.0) * sin(DEG2RAD * gsPos.lat) ) obsVel.setXYZ(-mFactor * obsPos.y, mFactor * obsPos.x, 0.0) magnitude(obsPos) @@ -177,15 +185,11 @@ abstract class Satellite(val data: OrbitalData) { } // Calculate the geodetic position of an object given its ECI pos and time - private fun calculateLatLonAlt( - time: Double, - satPos: SatPos, - position: Vector4 = this.position - ) { + private fun calculateLatLonAlt(time: Double) { satPos.theta = atan2(position.y, position.x) satPos.longitude = mod2PI(satPos.theta - thetaGJD(time)) val r = sqrt(sqr(position.x) + sqr(position.y)) - val e2 = FLAT_FACTOR * (2.0 - FLAT_FACTOR) + val e2 = FLAT_FACT * (2.0 - FLAT_FACT) satPos.latitude = atan2(position.z, r) var phi: Double var c: Double @@ -199,7 +203,7 @@ abstract class Satellite(val data: OrbitalData) { } while (i++ < 10 && !converged) satPos.altitude = r / cos(satPos.latitude) - EARTH_RADIUS * c var temp = satPos.latitude - if (temp > Math.PI / 2.0) { + if (temp > PI_2) { temp -= TWO_PI satPos.latitude = temp } @@ -232,11 +236,12 @@ abstract class Satellite(val data: OrbitalData) { velocity.z = rdotk * uz + rfdotk * vz } - internal class Vector4 { - var w = 0.0 - var x = 0.0 - var y = 0.0 - var z = 0.0 + internal class Vector4( + var w: Double = 0.0, + var x: Double = 0.0, + var y: Double = 0.0, + var z: Double = 0.0 + ) { fun multiply(multiplier: Double) { x *= multiplier @@ -284,6 +289,12 @@ abstract class Satellite(val data: OrbitalData) { } while (i++ < 10 && !converged) } + internal fun calculatePhase(xlt: Double, xnode: Double, omgadf: Double) { + var phaseValue = xlt - xnode - omgadf + TWO_PI + if (phaseValue < 0.0) phaseValue += TWO_PI + satPos.phase = mod2PI(phaseValue) + } + // Sets perigee and checks and adjusts the calculation if the perigee is less tan 156KM internal fun setPerigee(perigee: Double) { this.perigee = perigee @@ -301,6 +312,75 @@ abstract class Satellite(val data: OrbitalData) { } } + // Checks if the satellite is in sunlight + private fun isEclipsed(): Boolean { + val sunVector = calculateSunVector() + val sdEarth = asin(EARTH_RADIUS / position.w) + val rho = subtract(sunVector, position) + val sdSun = asin(SOLAR_RADIUS / rho.w) + val earth = scalarNegMultiply(position) + val delta = angle(sunVector, earth) + eclipseDepth = sdEarth - sdSun - delta + return if (sdEarth < sdSun) false else eclipseDepth >= 0 + } + + private fun calculateSunVector(): Vector4 { + val mjd = julUTC - 2415020.0 + val year = 1900 + mjd / 365.25 + val solTime = (mjd + deltaEt(year) / SEC_PER_DAY) / 36525.0 + val mTemp = modulus(35999.04975 * solTime, 360.0) + val m = radians( + modulus(358.47583 + mTemp - (0.000150 + 0.0000033 * solTime) * sqr(solTime), 360.0) + ) + val lTemp = modulus(36000.76892 * solTime, 360.0) + val l = radians( + modulus(279.69668 + lTemp + 0.0003025 * sqr(solTime), 360.0) + ) + val e = 0.01675104 - (0.0000418 + 0.000000126 * solTime) * solTime + val c = radians( + ((1.919460 - (0.004789 + 0.000014 * solTime) * solTime) * sin(m)) + + ((0.020094 - 0.000100 * solTime) * sin(2 * m)) + 0.000293 * sin(3 * m) + ) + val o = radians(modulus(259.18 - 1934.142 * solTime, 360.0)) + val lsa = modulus(l + c - radians(0.00569 - 0.00479 * sin(o)), TWO_PI) + val nu = modulus(m + c, TWO_PI) + var r = (1.0000002 * (1.0 - sqr(e)) / (1.0 + e * cos(nu))) + val eps = radians( + 23.452294 - (0.0130125 + (0.00000164 - 0.000000503 * solTime) * solTime) + * solTime + 0.00256 * cos(o) + ) + r *= ASTRONOMICAL_UNIT + return Vector4(r, r * cos(lsa), r * sin(lsa) * cos(eps), r * sin(lsa) * sin(eps)) + } + + private fun subtract(v1: Vector4, v2: Vector4): Vector4 { + val v3 = Vector4() + v3.x = v1.x - v2.x + v3.y = v1.y - v2.y + v3.z = v1.z - v2.z + magnitude(v3) + return v3 + } + + private fun scalarNegMultiply(vector: Vector4): Vector4 { + val neg = -1.0 + return Vector4(vector.w * abs(neg), vector.x * neg, vector.y * neg, vector.z * neg) + } + + private fun angle(v1: Vector4, v2: Vector4): Double { + magnitude(v1) + magnitude(v2) + return acos(dot(v1, v2) / (v1.w * v2.w)) + } + + private fun deltaEt(year: Double): Double { + return 26.465 + 0.747622 * (year - 1950) + (1.886913 * sin(TWO_PI * (year - 1975) / 33)) + } + + private fun radians(degrees: Double): Double { + return degrees * DEG2RAD + } + // Calculates the dot product of two vectors private fun dot(v1: Vector4, v2: Vector4): Double { return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z @@ -316,11 +396,11 @@ abstract class Satellite(val data: OrbitalData) { v.w = sqrt(sqr(v.x) + sqr(v.y) + sqr(v.z)) } - private fun modulus(arg1: Double): Double { + private fun modulus(arg1: Double, arg2: Double = SEC_PER_DAY): Double { var returnValue = arg1 - val i = floor(returnValue / SEC_PER_DAY).toInt() - returnValue -= i * SEC_PER_DAY - if (returnValue < 0.0) returnValue += SEC_PER_DAY + val i = floor(returnValue / arg2).toInt() + returnValue -= i * arg2 + if (returnValue < 0.0) returnValue += arg2 return returnValue } diff --git a/base/src/main/java/com/rtbishop/look4sat/domain/predict/SatelliteManager.kt b/base/src/main/java/com/rtbishop/look4sat/domain/predict/SatelliteManager.kt index 623fe251..62db594f 100644 --- a/base/src/main/java/com/rtbishop/look4sat/domain/predict/SatelliteManager.kt +++ b/base/src/main/java/com/rtbishop/look4sat/domain/predict/SatelliteManager.kt @@ -19,6 +19,7 @@ package com.rtbishop.look4sat.domain.predict import com.rtbishop.look4sat.domain.ISatelliteManager import com.rtbishop.look4sat.domain.model.SatRadio +import com.rtbishop.look4sat.utility.toDegrees import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow @@ -145,8 +146,8 @@ class SatelliteManager(private val defaultDispatcher: CoroutineDispatcher) : ISa val aos = time - 24 * 60L * 60L * 1000L val los = time + 24 * 60L * 60L * 1000L val tca = (aos + los) / 2 - val az = Math.toDegrees(satPos.azimuth) - val elev = Math.toDegrees(satPos.elevation) + val az = satPos.azimuth.toDegrees() + val elev = satPos.elevation.toDegrees() val alt = satPos.altitude return SatPass(aos, az, los, az, tca, az, alt, elev, sat) } @@ -180,7 +181,7 @@ class SatelliteManager(private val defaultDispatcher: CoroutineDispatcher) : ISa if (elevation > maxElevation) { maxElevation = elevation alt = satPos.altitude - tcaAz = Math.toDegrees(satPos.azimuth) + tcaAz = satPos.azimuth.toDegrees() } } while (satPos.elevation < 0.0) @@ -193,12 +194,12 @@ class SatelliteManager(private val defaultDispatcher: CoroutineDispatcher) : ISa if (elevation > maxElevation) { maxElevation = elevation alt = satPos.altitude - tcaAz = Math.toDegrees(satPos.azimuth) + tcaAz = satPos.azimuth.toDegrees() } } while (satPos.elevation < 0.0) val aos = satPos.time - val aosAz = Math.toDegrees(satPos.azimuth) + val aosAz = satPos.azimuth.toDegrees() // find when sat goes below do { @@ -208,7 +209,7 @@ class SatelliteManager(private val defaultDispatcher: CoroutineDispatcher) : ISa if (elevation > maxElevation) { maxElevation = elevation alt = satPos.altitude - tcaAz = Math.toDegrees(satPos.azimuth) + tcaAz = satPos.azimuth.toDegrees() } } while (satPos.elevation > 0.0) @@ -221,14 +222,14 @@ class SatelliteManager(private val defaultDispatcher: CoroutineDispatcher) : ISa if (elevation > maxElevation) { maxElevation = elevation alt = satPos.altitude - tcaAz = Math.toDegrees(satPos.azimuth) + tcaAz = satPos.azimuth.toDegrees() } } while (satPos.elevation > 0.0) val los = satPos.time - val losAz = Math.toDegrees(satPos.azimuth) + val losAz = satPos.azimuth.toDegrees() val tca = (aos + los) / 2 - val elev = Math.toDegrees(maxElevation) + val elev = maxElevation.toDegrees() return SatPass(aos, aosAz, los, losAz, tca, tcaAz, alt, elev, sat) } } diff --git a/base/src/main/java/com/rtbishop/look4sat/utility/Extensions.kt b/base/src/main/java/com/rtbishop/look4sat/utility/Extensions.kt index 3c26b063..75f3928c 100644 --- a/base/src/main/java/com/rtbishop/look4sat/utility/Extensions.kt +++ b/base/src/main/java/com/rtbishop/look4sat/utility/Extensions.kt @@ -17,6 +17,8 @@ */ package com.rtbishop.look4sat.utility +import com.rtbishop.look4sat.domain.predict.DEG2RAD +import com.rtbishop.look4sat.domain.predict.RAD2DEG import java.net.InetSocketAddress import java.net.Socket import java.security.MessageDigest @@ -37,6 +39,10 @@ fun Double.round(decimals: Int): Double { return kotlin.math.round(this * multiplier) / multiplier } +fun Double.toDegrees(): Double = this * RAD2DEG + +fun Double.toRadians(): Double = this * DEG2RAD + fun String.getHash(type: String = "SHA-256"): String { val hexChars = "0123456789ABCDEF" val bytes = MessageDigest.getInstance(type).digest(this.toByteArray()) diff --git a/base/src/test/java/com/rtbishop/look4sat/QthConverterTest.kt b/base/src/test/java/com/rtbishop/look4sat/QthConverterTest.kt index e5f0317d..f720490a 100644 --- a/base/src/test/java/com/rtbishop/look4sat/QthConverterTest.kt +++ b/base/src/test/java/com/rtbishop/look4sat/QthConverterTest.kt @@ -25,11 +25,11 @@ class QthConverterTest { @Test fun `Given valid QTH returns correct POS`() { var result = QthConverter.qthToPosition("io91VL39FX") - assert(result?.latitude == 51.4792 && result.longitude == -0.2083) + assert(result?.lat == 51.4792 && result.lon == -0.2083) result = QthConverter.qthToPosition("JN58TD") - assert(result?.latitude == 48.1458 && result.longitude == 11.6250) + assert(result?.lat == 48.1458 && result.lon == 11.6250) result = QthConverter.qthToPosition("gf15vc") - assert(result?.latitude == -34.8958 && result.longitude == -56.2083) + assert(result?.lat == -34.8958 && result.lon == -56.2083) } @Test