Added satellite visibility parameter to SatPos.kt #62

pull/87/head
Arty Bishop 2022-03-07 14:31:28 +00:00
rodzic 4ee74a6789
commit ae08c7c937
17 zmienionych plików z 202 dodań i 137 usunięć

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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<List<GeoPos>>) {
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<GeoPoint>()
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()
}

Wyświetl plik

@ -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<GeoPos>().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<GeoPos>().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<Satellite, GeoPos>()
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

Wyświetl plik

@ -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()

Wyświetl plik

@ -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)

Wyświetl plik

@ -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<SatPos> = 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()

Wyświetl plik

@ -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))

Wyświetl plik

@ -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()

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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)

Wyświetl plik

@ -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

Wyświetl plik

@ -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<GeoPos> {
val positions = mutableListOf<GeoPos>()
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<GeoPos>()
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
}
}

Wyświetl plik

@ -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
}

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -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())

Wyświetl plik

@ -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