kopia lustrzana https://github.com/rt-bishop/Look4Sat
Added satellite visibility parameter to SatPos.kt #62
rodzic
4ee74a6789
commit
ae08c7c937
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue