mirror of
https://github.com/rt-bishop/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Renamed and refactored several predict4kotlin classes
This commit is contained in:
1 parent
c38fe8b10f
commit
9a4de03cac
27 files changed
+315
-232
No files matched your search
@@ -23,7 +23,7 @@ import android.location.LocationManager
|
||||
import androidx.core.content.edit
|
||||
import com.rtbishop.look4sat.data.PreferencesSource
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.QthConverter
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.StationPosition
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.StationPos
|
||||
import com.rtbishop.look4sat.utility.round
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
@@ -89,19 +89,19 @@ class PreferencesProvider @Inject constructor(
|
||||
return qthConverter.positionToQTH(lat, lon)
|
||||
}
|
||||
|
||||
override fun loadStationPosition(): StationPosition {
|
||||
override fun loadStationPosition(): StationPos {
|
||||
val defaultSP = "0.0"
|
||||
val latitude = preferences.getString(keyLatitude, null) ?: defaultSP
|
||||
val longitude = preferences.getString(keyLongitude, null) ?: defaultSP
|
||||
val altitude = preferences.getString(keyAltitude, null) ?: defaultSP
|
||||
return StationPosition(latitude.toDouble(), longitude.toDouble(), altitude.toDouble())
|
||||
return StationPos(latitude.toDouble(), longitude.toDouble(), altitude.toDouble())
|
||||
}
|
||||
|
||||
override fun saveStationPosition(position: StationPosition) {
|
||||
override fun saveStationPosition(pos: StationPos) {
|
||||
preferences.edit {
|
||||
putString(keyLatitude, position.latitude.toString())
|
||||
putString(keyLongitude, position.longitude.toString())
|
||||
putString(keyAltitude, position.altitude.toString())
|
||||
putString(keyLatitude, pos.latitude.toString())
|
||||
putString(keyLongitude, pos.longitude.toString())
|
||||
putString(keyAltitude, pos.altitude.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ class PreferencesProvider @Inject constructor(
|
||||
val latitude = location.latitude.round(4)
|
||||
val longitude = location.longitude.round(4)
|
||||
val altitude = location.altitude.round(1)
|
||||
val stationPosition = StationPosition(latitude, longitude, altitude)
|
||||
val stationPosition = StationPos(latitude, longitude, altitude)
|
||||
saveStationPosition(stationPosition)
|
||||
return true
|
||||
}
|
||||
@@ -124,7 +124,7 @@ class PreferencesProvider @Inject constructor(
|
||||
|
||||
override fun updatePositionFromQTH(qthString: String): Boolean {
|
||||
val position = qthConverter.qthToPosition(qthString) ?: return false
|
||||
val stationPosition = StationPosition(position.latitude, position.longitude, 0.0)
|
||||
val stationPosition = StationPos(position.latitude, position.longitude, 0.0)
|
||||
saveStationPosition(stationPosition)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ package com.rtbishop.look4sat.framework.db
|
||||
import androidx.room.TypeConverter
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.TLE
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.createSat
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package com.rtbishop.look4sat.framework.model
|
||||
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.Position
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.GeoPos
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
|
||||
|
||||
data class SatData(
|
||||
@@ -28,5 +28,5 @@ data class SatData(
|
||||
val altitude: Double,
|
||||
val velocity: Double,
|
||||
val qthLoc: String,
|
||||
val osmPos: Position
|
||||
)
|
||||
val osmPos: GeoPos
|
||||
)
|
||||
@@ -29,7 +29,7 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.databinding.FragmentMapBinding
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.Position
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.GeoPos
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
|
||||
import com.rtbishop.look4sat.framework.model.SatData
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -96,7 +96,7 @@ class SatMapFragment : Fragment(R.layout.fragment_map) {
|
||||
viewModel.satData.observe(viewLifecycleOwner, { renderSatData(it, binding) })
|
||||
}
|
||||
|
||||
private fun renderStationPos(stationPos: Position, binding: FragmentMapBinding) {
|
||||
private fun renderStationPos(stationPos: GeoPos, binding: FragmentMapBinding) {
|
||||
binding.apply {
|
||||
Marker(mapView).apply {
|
||||
setInfoWindow(null)
|
||||
@@ -109,7 +109,7 @@ class SatMapFragment : Fragment(R.layout.fragment_map) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderSatPositions(posMap: Map<Satellite, Position>, binding: FragmentMapBinding) {
|
||||
private fun renderSatPositions(posMap: Map<Satellite, GeoPos>, binding: FragmentMapBinding) {
|
||||
binding.apply {
|
||||
val markers = FolderOverlay()
|
||||
posMap.entries.forEach {
|
||||
@@ -120,7 +120,7 @@ class SatMapFragment : Fragment(R.layout.fragment_map) {
|
||||
textLabelBackgroundColor = Color.TRANSPARENT
|
||||
textLabelForegroundColor =
|
||||
ContextCompat.getColor(requireContext(), R.color.themeLight)
|
||||
setTextIcon(it.key.tle.name)
|
||||
setTextIcon(it.key.params.name)
|
||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
|
||||
try {
|
||||
position = GeoPoint(it.value.latitude, it.value.longitude)
|
||||
@@ -156,7 +156,7 @@ class SatMapFragment : Fragment(R.layout.fragment_map) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderSatTrack(satTrack: List<List<Position>>, binding: FragmentMapBinding) {
|
||||
private fun renderSatTrack(satTrack: List<List<GeoPos>>, binding: FragmentMapBinding) {
|
||||
val trackOverlay = FolderOverlay()
|
||||
satTrack.forEach { track ->
|
||||
val trackPoints = track.map { GeoPoint(it.latitude, it.longitude) }
|
||||
@@ -169,7 +169,7 @@ class SatMapFragment : Fragment(R.layout.fragment_map) {
|
||||
binding.mapView.overlays[1] = trackOverlay
|
||||
}
|
||||
|
||||
private fun renderSatFootprint(satFootprint: List<Position>, binding: FragmentMapBinding) {
|
||||
private fun renderSatFootprint(satFootprint: List<GeoPos>, binding: FragmentMapBinding) {
|
||||
val footprintPoints = satFootprint.map { GeoPoint(it.latitude, it.longitude) }
|
||||
val footprintOverlay = Polygon().apply {
|
||||
fillPaint.set(footprintPaint)
|
||||
|
||||
+25
-26
@@ -20,10 +20,9 @@ package com.rtbishop.look4sat.presentation.satMapScreen
|
||||
import androidx.lifecycle.*
|
||||
import com.rtbishop.look4sat.data.PreferencesSource
|
||||
import com.rtbishop.look4sat.data.SatDataRepository
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.Position
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.GeoPos
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.StationPosition
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.getRangeCircle
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.StationPos
|
||||
import com.rtbishop.look4sat.framework.model.SatData
|
||||
import com.rtbishop.look4sat.injection.DefaultDispatcher
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
@@ -49,20 +48,20 @@ class SatMapViewModel @Inject constructor(
|
||||
val stationPos = liveData {
|
||||
val osmLat = clipLat(gsp.latitude)
|
||||
val osmLon = clipLon(gsp.longitude)
|
||||
emit(Position(osmLat, osmLon))
|
||||
emit(GeoPos(osmLat, osmLon))
|
||||
}
|
||||
|
||||
private val _satTrack = MutableLiveData<List<List<Position>>>()
|
||||
val satTrack: LiveData<List<List<Position>>> = _satTrack
|
||||
private val _satTrack = MutableLiveData<List<List<GeoPos>>>()
|
||||
val satTrack: LiveData<List<List<GeoPos>>> = _satTrack
|
||||
|
||||
private val _satFootprint = MutableLiveData<List<Position>>()
|
||||
val satFootprint: LiveData<List<Position>> = _satFootprint
|
||||
private val _satFootprint = MutableLiveData<List<GeoPos>>()
|
||||
val satFootprint: LiveData<List<GeoPos>> = _satFootprint
|
||||
|
||||
private val _satData = MutableLiveData<SatData>()
|
||||
val satData: LiveData<SatData> = this._satData
|
||||
|
||||
private val _satPositions = MutableLiveData<Map<Satellite, Position>>()
|
||||
val satPositions: LiveData<Map<Satellite, Position>> = _satPositions
|
||||
private val _satPositions = MutableLiveData<Map<Satellite, GeoPos>>()
|
||||
val satPositions: LiveData<Map<Satellite, GeoPos>> = _satPositions
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
@@ -110,38 +109,38 @@ class SatMapViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setSatPositions(list: List<Satellite>, gsp: StationPosition, date: Date) {
|
||||
private suspend fun setSatPositions(list: List<Satellite>, gsp: StationPos, date: Date) {
|
||||
withContext(defaultDispatcher) {
|
||||
val satPositions = mutableMapOf<Satellite, Position>()
|
||||
val satPositions = mutableMapOf<Satellite, GeoPos>()
|
||||
list.forEach { satellite ->
|
||||
val satPos = satellite.getPredictor(gsp).getSatPos(date)
|
||||
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
|
||||
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
|
||||
satPositions[satellite] = Position(osmLat, osmLon)
|
||||
satPositions[satellite] = GeoPos(osmLat, osmLon)
|
||||
}
|
||||
_satPositions.postValue(satPositions)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setSelectedSatTrack(sat: Satellite, gsp: StationPosition, date: Date) {
|
||||
private suspend fun setSelectedSatTrack(sat: Satellite, gsp: StationPos, date: Date) {
|
||||
withContext(defaultDispatcher) {
|
||||
val satTracks = mutableListOf<List<Position>>()
|
||||
val currentTrack = mutableListOf<Position>()
|
||||
val satTracks = mutableListOf<List<GeoPos>>()
|
||||
val currentTrack = mutableListOf<GeoPos>()
|
||||
var oldLongitude = 0.0
|
||||
sat.getPredictor(gsp).getPositions(date, 15, 0, 2.4).forEach { satPos ->
|
||||
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
|
||||
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
|
||||
val currentPosition = Position(osmLat, osmLon)
|
||||
val currentPosition = GeoPos(osmLat, osmLon)
|
||||
if (oldLongitude < -170.0 && currentPosition.longitude > 170.0) {
|
||||
// adding left terminal position
|
||||
currentTrack.add(Position(osmLat, -180.0))
|
||||
val finishedTrack = mutableListOf<Position>().apply { addAll(currentTrack) }
|
||||
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) {
|
||||
// adding right terminal position
|
||||
currentTrack.add(Position(osmLat, 180.0))
|
||||
val finishedTrack = mutableListOf<Position>().apply { addAll(currentTrack) }
|
||||
currentTrack.add(GeoPos(osmLat, 180.0))
|
||||
val finishedTrack = mutableListOf<GeoPos>().apply { addAll(currentTrack) }
|
||||
satTracks.add(finishedTrack)
|
||||
currentTrack.clear()
|
||||
}
|
||||
@@ -153,28 +152,28 @@ class SatMapViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setSelectedSatFootprint(sat: Satellite, gsp: StationPosition, date: Date) {
|
||||
private suspend fun setSelectedSatFootprint(sat: Satellite, gsp: StationPos, date: Date) {
|
||||
withContext(defaultDispatcher) {
|
||||
val satFootprint = sat.getPosition(gsp, date).getRangeCircle().map { rangePos ->
|
||||
val osmLat = clipLat(rangePos.latitude)
|
||||
val osmLon = clipLon(rangePos.longitude)
|
||||
Position(osmLat, osmLon)
|
||||
GeoPos(osmLat, osmLon)
|
||||
}
|
||||
_satFootprint.postValue(satFootprint)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setSelectedSatData(sat: Satellite, gsp: StationPosition, date: Date) {
|
||||
private suspend fun setSelectedSatData(sat: Satellite, gsp: StationPos, date: Date) {
|
||||
withContext(defaultDispatcher) {
|
||||
val satPos = sat.getPredictor(gsp).getSatPos(date)
|
||||
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
|
||||
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
|
||||
val osmPos = Position(osmLat, osmLon)
|
||||
val osmPos = GeoPos(osmLat, osmLon)
|
||||
val qthLoc =
|
||||
preferencesSource.positionToQTH(osmPos.latitude, osmPos.longitude) ?: "-- --"
|
||||
val velocity = getOrbitalVelocity(satPos.altitude)
|
||||
val satData = SatData(
|
||||
sat, sat.tle.catnum, sat.tle.name, satPos.range,
|
||||
sat, sat.params.catnum, sat.params.name, satPos.range,
|
||||
satPos.altitude, velocity, qthLoc, osmPos
|
||||
)
|
||||
this@SatMapViewModel._satData.postValue(satData)
|
||||
|
||||
+4
-4
@@ -119,13 +119,13 @@ class PassInfoFragment : Fragment(R.layout.fragment_polar) {
|
||||
binding.satName.text = satPass.name
|
||||
|
||||
if (!satPass.isDeepSpace) {
|
||||
if (dateNow.before(satPass.aosDate)) {
|
||||
val millisBeforeStart = satPass.aosDate.time.minus(timeNow)
|
||||
if (dateNow.time < satPass.aosTime) {
|
||||
val millisBeforeStart = satPass.aosTime.minus(timeNow)
|
||||
binding.polarTimer.text = millisBeforeStart.toTimerString()
|
||||
} else {
|
||||
val millisBeforeEnd = satPass.losDate.time.minus(timeNow)
|
||||
val millisBeforeEnd = satPass.losTime.minus(timeNow)
|
||||
binding.polarTimer.text = millisBeforeEnd.toTimerString()
|
||||
if (dateNow.after(satPass.losDate)) {
|
||||
if (dateNow.time > satPass.losTime) {
|
||||
binding.polarTimer.text = 0L.toTimerString()
|
||||
findNavController().navigateSafe(R.id.action_polar_to_passes)
|
||||
}
|
||||
|
||||
+5
-5
@@ -200,17 +200,17 @@ class PassInfoView(context: Context) : View(context) {
|
||||
}
|
||||
|
||||
private fun createPassTrajectory(satPass: SatPass) {
|
||||
val currentTime = satPass.aosDate
|
||||
while (currentTime.before(satPass.losDate)) {
|
||||
val satPos = satPass.predictor.getSatPos(currentTime)
|
||||
var currentTime = satPass.aosTime
|
||||
while (currentTime < satPass.losTime) {
|
||||
val satPos = satPass.predictor.getSatPos(Date(currentTime))
|
||||
val passX = sph2CartX(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
||||
val passY = sph2CartY(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
||||
if (currentTime.compareTo(satPass.aosDate) == 0) {
|
||||
if (currentTime == satPass.aosTime) {
|
||||
trackPath.moveTo(passX, -passY)
|
||||
} else {
|
||||
trackPath.lineTo(passX, -passY)
|
||||
}
|
||||
currentTime.time += 15000
|
||||
currentTime += 15000
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ class PassInfoViewModel @Inject constructor(
|
||||
|
||||
fun getPass(catNum: Int, aosTime: Long) = liveData {
|
||||
satPassRepository.passes.collect { passes ->
|
||||
val pass = passes.find { it.catNum == catNum && it.aosDate.time == aosTime }
|
||||
val pass = passes.find { it.catNum == catNum && it.aosTime == aosTime }
|
||||
pass?.let { satPass ->
|
||||
processTransmitters(satPass)
|
||||
initRotatorControl(satPass)
|
||||
|
||||
+3
-3
@@ -34,7 +34,7 @@ class SatPassAdapter(private val isUTC: Boolean, private val clickListener: Pass
|
||||
|
||||
private val diffCallback = object : DiffUtil.ItemCallback<SatPass>() {
|
||||
override fun areItemsTheSame(oldItem: SatPass, newItem: SatPass): Boolean {
|
||||
return oldItem.catNum == newItem.catNum && oldItem.aosDate == newItem.aosDate
|
||||
return oldItem.catNum == newItem.catNum && oldItem.aosTime == newItem.aosTime
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: SatPass, newItem: SatPass): Boolean {
|
||||
@@ -99,9 +99,9 @@ class SatPassAdapter(private val isUTC: Boolean, private val clickListener: Pass
|
||||
passLeoAosAz.text = String.format(aosAzFormat, satPass.aosAzimuth)
|
||||
passLeoMaxEl.text = String.format(maxElFormat, satPass.maxElevation)
|
||||
passLeoLosAz.text = String.format(losAzFormat, satPass.losAzimuth)
|
||||
passLeoStart.text = startFormat.format(satPass.aosDate)
|
||||
passLeoStart.text = startFormat.format(Date(satPass.aosTime))
|
||||
passLeoTcaAz.text = String.format(tcaAzFormat, satPass.tcaAzimuth)
|
||||
passLeoEnd.text = endFormat.format(satPass.losDate)
|
||||
passLeoEnd.text = endFormat.format(Date(satPass.losTime))
|
||||
passLeoProgress.progress = satPass.progress
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -110,12 +110,12 @@ class SatPassFragment : Fragment(R.layout.fragment_passes), SatPassAdapter.Passe
|
||||
if (passes.isNotEmpty()) {
|
||||
val timeNow = System.currentTimeMillis()
|
||||
try {
|
||||
val nextPass = passes.first { it.aosDate.time.minus(timeNow) > 0 }
|
||||
val millisBeforeStart = nextPass.aosDate.time.minus(timeNow)
|
||||
val nextPass = passes.first { it.aosTime.minus(timeNow) > 0 }
|
||||
val millisBeforeStart = nextPass.aosTime.minus(timeNow)
|
||||
binding.passesTimer.text = millisBeforeStart.toTimerString()
|
||||
} catch (e: NoSuchElementException) {
|
||||
val lastPass = passes.last()
|
||||
val millisBeforeEnd = lastPass.losDate.time.minus(timeNow)
|
||||
val millisBeforeEnd = lastPass.losTime.minus(timeNow)
|
||||
binding.passesTimer.text = millisBeforeEnd.toTimerString()
|
||||
}
|
||||
} else {
|
||||
@@ -125,7 +125,7 @@ class SatPassFragment : Fragment(R.layout.fragment_passes), SatPassAdapter.Passe
|
||||
|
||||
override fun navigateToPass(satPass: SatPass) {
|
||||
if (satPass.progress < 100) {
|
||||
val bundle = bundleOf("catNum" to satPass.catNum, "aosTime" to satPass.aosDate.time)
|
||||
val bundle = bundleOf("catNum" to satPass.catNum, "aosTime" to satPass.aosTime)
|
||||
findNavController().navigateSafe(R.id.action_passes_to_polar, bundle)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -92,10 +92,10 @@ class SatPassViewModel @Inject constructor(
|
||||
val timeNow = System.currentTimeMillis()
|
||||
currentPasses.forEach { pass ->
|
||||
if (!pass.isDeepSpace) {
|
||||
val timeStart = pass.aosDate.time
|
||||
val timeStart = pass.aosTime
|
||||
if (timeNow > timeStart) {
|
||||
val deltaNow = timeNow.minus(timeStart).toFloat()
|
||||
val deltaTotal = pass.losDate.time.minus(timeStart).toFloat()
|
||||
val deltaTotal = pass.losTime.minus(timeStart).toFloat()
|
||||
pass.progress = ((deltaNow / deltaTotal) * 100).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
*/
|
||||
package com.rtbishop.look4sat.data
|
||||
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.StationPosition
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.StationPos
|
||||
|
||||
interface PreferencesSource {
|
||||
|
||||
fun positionToQTH(lat: Double, lon: Double): String?
|
||||
|
||||
fun loadStationPosition(): StationPosition
|
||||
fun loadStationPosition(): StationPos
|
||||
|
||||
fun saveStationPosition(position: StationPosition)
|
||||
fun saveStationPosition(pos: StationPos)
|
||||
|
||||
fun updatePositionFromGPS(): Boolean
|
||||
|
||||
|
||||
@@ -91,6 +91,6 @@ class SatDataRepository(
|
||||
}
|
||||
|
||||
private fun importSatEntries(stream: InputStream): List<SatEntry> {
|
||||
return TLE.importElements(stream).map { tle -> SatEntry(tle) }
|
||||
return TLE.parseStream(stream).map { tle -> SatEntry(tle) }
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,8 @@ class SatPassRepository(
|
||||
if (satellites.isEmpty()) {
|
||||
_passes.emit(emptyList())
|
||||
} else {
|
||||
val oldCatNums = selectedSatellites.map { it.tle.catnum }
|
||||
val newCatNums = satellites.map { it.tle.catnum }
|
||||
val oldCatNums = selectedSatellites.map { it.params.catnum }
|
||||
val newCatNums = satellites.map { it.params.catnum }
|
||||
if (oldCatNums != newCatNums) forceCalculation(satellites, refDate)
|
||||
}
|
||||
}
|
||||
@@ -62,10 +62,10 @@ class SatPassRepository(
|
||||
}
|
||||
|
||||
private fun filterPasses(passes: List<SatPass>, refDate: Date): List<SatPass> {
|
||||
val timeFuture = Date(refDate.time + (preferencesSource.getHoursAhead() * 3600 * 1000))
|
||||
return passes.filter { it.losDate.after(refDate) }
|
||||
.filter { it.aosDate.before(timeFuture) }
|
||||
val timeFuture = refDate.time + (preferencesSource.getHoursAhead() * 3600 * 1000)
|
||||
return passes.filter { it.losTime > refDate.time }
|
||||
.filter { it.aosTime < timeFuture }
|
||||
.filter { it.maxElevation > preferencesSource.getMinElevation() }
|
||||
.sortedBy { it.aosDate }
|
||||
.sortedBy { it.aosTime }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.rtbishop.look4sat.data
|
||||
|
||||
import com.rtbishop.look4sat.domain.predict4kotlin.*
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
|
||||
class SatPredictor(
|
||||
private val preferencesSource: PreferencesSource,
|
||||
private val defaultDispatcher: CoroutineDispatcher
|
||||
) {
|
||||
// Multi Sat passes
|
||||
private val _passes = MutableSharedFlow<List<SatPass>>(replay = 1)
|
||||
private var selectedSatellites = emptyList<Satellite>()
|
||||
val passes: SharedFlow<List<SatPass>> = _passes
|
||||
|
||||
suspend fun triggerCalculation(satellites: List<Satellite>, refDate: Date = Date()) {
|
||||
if (satellites.isEmpty()) {
|
||||
_passes.emit(emptyList())
|
||||
} else {
|
||||
val oldCatNums = selectedSatellites.map { it.params.catnum }
|
||||
val newCatNums = satellites.map { it.params.catnum }
|
||||
if (oldCatNums != newCatNums) forceCalculation(satellites, refDate)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun forceCalculation(satellites: List<Satellite>, refDate: Date = Date()) {
|
||||
if (satellites.isEmpty()) {
|
||||
_passes.emit(emptyList())
|
||||
} else {
|
||||
withContext(defaultDispatcher) {
|
||||
val allPasses = mutableListOf<SatPass>()
|
||||
selectedSatellites = satellites
|
||||
satellites.forEach { satellite -> allPasses.addAll(getPasses(satellite, refDate)) }
|
||||
_passes.emit(filterPasses(allPasses, refDate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPasses(satellite: Satellite, refDate: Date): List<SatPass> {
|
||||
val predictor = satellite.getPredictor(preferencesSource.loadStationPosition())
|
||||
return predictor.getPasses(refDate, preferencesSource.getHoursAhead(), true)
|
||||
}
|
||||
|
||||
private fun filterPasses(passes: List<SatPass>, refDate: Date): List<SatPass> {
|
||||
val timeFuture = refDate.time + (preferencesSource.getHoursAhead() * 3600 * 1000)
|
||||
return passes.filter { it.losTime > refDate.time }
|
||||
.filter { it.aosTime < timeFuture }
|
||||
.filter { it.maxElevation > preferencesSource.getMinElevation() }
|
||||
.sortedBy { it.aosTime }
|
||||
}
|
||||
|
||||
// Single Sat radar
|
||||
|
||||
// Constant calculation of beacon Az/El point
|
||||
private val _beaconPoint = MutableSharedFlow<SkyPos>(replay = 1)
|
||||
val beaconPoint: SharedFlow<SkyPos> = _beaconPoint
|
||||
|
||||
suspend fun calculateSatBeacon(satPass: SatPass, stationPos: StationPos, date: Date) {
|
||||
withContext(defaultDispatcher) {
|
||||
val satPos = satPass.satellite.getPosition(stationPos, date)
|
||||
_beaconPoint.emit(SkyPos(satPos.azimuth, satPos.elevation))
|
||||
}
|
||||
}
|
||||
|
||||
// Single calculation of beacon Az/El points list
|
||||
private val _trajectoryPoints = MutableSharedFlow<List<SkyPos>>(replay = 1)
|
||||
val trajectoryPoints: SharedFlow<List<SkyPos>> = _trajectoryPoints
|
||||
|
||||
suspend fun calculateSatTrajectory(satPass: SatPass, stationPos: StationPos) {
|
||||
withContext(defaultDispatcher) {
|
||||
val trajectoryPoints = mutableListOf<SkyPos>()
|
||||
var currentTime = satPass.aosTime
|
||||
while (currentTime < satPass.losTime) {
|
||||
val satPos = satPass.satellite.getPosition(stationPos, Date(currentTime))
|
||||
trajectoryPoints.add(SkyPos(satPos.azimuth, satPos.elevation))
|
||||
currentTime += 15000
|
||||
}
|
||||
_trajectoryPoints.emit(trajectoryPoints)
|
||||
}
|
||||
}
|
||||
|
||||
// Multi Sat map
|
||||
}
|
||||
@@ -19,7 +19,7 @@ package com.rtbishop.look4sat.domain.predict4kotlin
|
||||
|
||||
import kotlin.math.*
|
||||
|
||||
class DeepSpaceSat(tle: TLE) : Satellite(tle) {
|
||||
class DeepSpaceSat(params: TLE) : Satellite(params) {
|
||||
|
||||
private val c1: Double
|
||||
private val c4: Double
|
||||
@@ -35,41 +35,41 @@ class DeepSpaceSat(tle: TLE) : Satellite(tle) {
|
||||
|
||||
init {
|
||||
// Recover original mean motion (xnodp) and semimajor axis (aodp) from input elements
|
||||
val a1 = (xke / super.tle.xno).pow(twoThirds)
|
||||
dsv.cosio = cos(super.tle.xincl)
|
||||
val a1 = (xke / super.params.xno).pow(twoThirds)
|
||||
dsv.cosio = cos(super.params.xincl)
|
||||
dsv.theta2 = dsv.cosio * dsv.cosio
|
||||
x3thm1 = 3.0 * dsv.theta2 - 1
|
||||
dsv.eosq = super.tle.eccn * super.tle.eccn
|
||||
dsv.eosq = super.params.eccn * super.params.eccn
|
||||
dsv.betao2 = 1.0 - dsv.eosq
|
||||
dsv.betao = sqrt(dsv.betao2)
|
||||
val del1 = 1.5 * ck2 * x3thm1 / (a1 * a1 * dsv.betao * dsv.betao2)
|
||||
val ao = a1 * (1.0 - del1 * (0.5 * twoThirds + del1 * (1.0 + 134.0 / 81.0 * del1)))
|
||||
val delo = 1.5 * ck2 * x3thm1 / (ao * ao * dsv.betao * dsv.betao2)
|
||||
dsv.xnodp = super.tle.xno / (1.0 + delo)
|
||||
dsv.xnodp = super.params.xno / (1.0 + delo)
|
||||
dsv.aodp = ao / (1.0 - delo)
|
||||
// For perigee below 156 km, the values of S and QOMS2T are altered
|
||||
setPerigee((dsv.aodp * (1.0 - super.tle.eccn) - 1.0) * earthRadius)
|
||||
setPerigee((dsv.aodp * (1.0 - super.params.eccn) - 1.0) * earthRadius)
|
||||
val pinvsq = invert(dsv.aodp * dsv.aodp * dsv.betao2 * dsv.betao2)
|
||||
dsv.sing = sin(super.tle.omegao)
|
||||
dsv.cosg = cos(super.tle.omegao)
|
||||
dsv.sing = sin(super.params.omegao)
|
||||
dsv.cosg = cos(super.params.omegao)
|
||||
val tsi = invert(dsv.aodp - s4)
|
||||
val eta = dsv.aodp * super.tle.eccn * tsi
|
||||
val eta = dsv.aodp * super.params.eccn * tsi
|
||||
val etasq = eta * eta
|
||||
val eeta = super.tle.eccn * eta
|
||||
val eeta = super.params.eccn * eta
|
||||
val psisq = abs(1.0 - etasq)
|
||||
val coef = qoms24 * tsi.pow(4.0)
|
||||
val coef1 = coef / psisq.pow(3.5)
|
||||
val c2 = coef1 * dsv.xnodp * (dsv.aodp * (1.0 + 1.5 * etasq + eeta * (4.0 + etasq))
|
||||
+ 0.75 * ck2 * tsi / psisq * x3thm1 * (8.0 + 3.0 * etasq * (8.0 + etasq)))
|
||||
c1 = super.tle.bstar * c2
|
||||
dsv.sinio = sin(super.tle.xincl)
|
||||
c1 = super.params.bstar * c2
|
||||
dsv.sinio = sin(super.params.xincl)
|
||||
val a3ovk2 = -j3Harmonic / ck2
|
||||
x1mth2 = 1.0 - dsv.theta2
|
||||
c4 =
|
||||
2 * dsv.xnodp * coef1 * dsv.aodp * dsv.betao2 * (eta * (2.0 + 0.5 * etasq) + super.tle.eccn
|
||||
2 * dsv.xnodp * coef1 * dsv.aodp * dsv.betao2 * (eta * (2.0 + 0.5 * etasq) + super.params.eccn
|
||||
* (0.5 + 2 * etasq) - 2 * ck2 * tsi / (dsv.aodp * psisq)
|
||||
* (-3 * x3thm1 * (1.0 - 2 * eeta + etasq * (1.5 - 0.5 * eeta)) + (0.75 * x1mth2
|
||||
* (2.0 * etasq - eeta * (1.0 + etasq)) * cos(2.0 * super.tle.omegao))))
|
||||
* (2.0 * etasq - eeta * (1.0 + etasq)) * cos(2.0 * super.params.omegao))))
|
||||
val theta4 = dsv.theta2 * dsv.theta2
|
||||
val temp1 = 3.0 * ck2 * pinvsq * dsv.xnodp
|
||||
val temp2 = temp1 * ck2 * pinvsq
|
||||
@@ -93,18 +93,18 @@ class DeepSpaceSat(tle: TLE) : Satellite(tle) {
|
||||
fun calculateSDP4(tSince: Double) {
|
||||
synchronized(this) {
|
||||
val temp = DoubleArray(12)
|
||||
val xmdf = tle.xmo + dsv.xmdot * tSince
|
||||
val xmdf = params.xmo + dsv.xmdot * tSince
|
||||
val tsq = tSince * tSince
|
||||
val templ = t2cof * tsq
|
||||
dsv.xll = xmdf + dsv.xnodp * templ
|
||||
dsv.omgadf = tle.omegao + dsv.omgdot * tSince
|
||||
val xnoddf = tle.xnodeo + dsv.xnodot * tSince
|
||||
dsv.omgadf = params.omegao + dsv.omgdot * tSince
|
||||
val xnoddf = params.xnodeo + dsv.xnodot * tSince
|
||||
dsv.xnode = xnoddf + xnodcf * tsq
|
||||
val tempa = 1.0 - c1 * tSince
|
||||
val tempe = tle.bstar * c4 * tSince
|
||||
val tempe = params.bstar * c4 * tSince
|
||||
dsv.xn = dsv.xnodp
|
||||
dsv.t = tSince
|
||||
deep.dpsec(tle)
|
||||
deep.dpsec(params)
|
||||
val a = (xke / dsv.xn).pow(twoThirds) * tempa * tempa
|
||||
dsv.em = dsv.em - tempe
|
||||
deep.dpper()
|
||||
@@ -425,16 +425,16 @@ class DeepSpaceSat(tle: TLE) : Satellite(tle) {
|
||||
private var epochRestart = false
|
||||
|
||||
init {
|
||||
thgr = thetaG(tle.epoch)
|
||||
eq = tle.eccn
|
||||
thgr = thetaG(params.epoch)
|
||||
eq = params.eccn
|
||||
xnq = dsv.xnodp
|
||||
aqnv = invert(dsv.aodp)
|
||||
xqncl = tle.xincl
|
||||
xmao = tle.xmo
|
||||
xqncl = params.xincl
|
||||
xmao = params.xmo
|
||||
xpidot = dsv.omgdot + dsv.xnodot
|
||||
sinq = sin(tle.xnodeo)
|
||||
cosq = cos(tle.xnodeo)
|
||||
omegaq = tle.omegao
|
||||
sinq = sin(params.xnodeo)
|
||||
cosq = cos(params.xnodeo)
|
||||
omegaq = params.omegao
|
||||
// Initialize lunar solar terms, days since 1900 Jan 0.5
|
||||
day = dsv.ds50 + 18261.5
|
||||
if (abs(day - preep) > 1.0E-6) {
|
||||
@@ -534,7 +534,7 @@ class DeepSpaceSat(tle: TLE) : Satellite(tle) {
|
||||
temp = 2.0 * temp1 * root54
|
||||
d5421 = temp * f542 * g521
|
||||
d5433 = temp * f543 * g533
|
||||
xlamo = xmao + tle.xnodeo + tle.xnodeo - thgr - thgr
|
||||
xlamo = xmao + params.xnodeo + params.xnodeo - thgr - thgr
|
||||
bfact = dsv.xmdot + dsv.xnodot + dsv.xnodot - tHdt - tHdt
|
||||
bfact += ssl + ssh + ssh
|
||||
} else {
|
||||
@@ -556,7 +556,7 @@ class DeepSpaceSat(tle: TLE) : Satellite(tle) {
|
||||
fasx2 = 0.13130908
|
||||
fasx4 = 2.8843198
|
||||
fasx6 = 0.37448087
|
||||
xlamo = xmao + tle.xnodeo + tle.omegao - thgr
|
||||
xlamo = xmao + params.xnodeo + params.omegao - thgr
|
||||
bfact = dsv.xmdot + xpidot - tHdt
|
||||
bfact += ssl + ssg + ssh
|
||||
}
|
||||
@@ -623,12 +623,12 @@ class DeepSpaceSat(tle: TLE) : Satellite(tle) {
|
||||
}
|
||||
|
||||
// Entrance for deep space secular effects
|
||||
fun dpsec(tle: TLE) {
|
||||
fun dpsec(params: TLE) {
|
||||
dsv.xll = dsv.xll + ssl * dsv.t
|
||||
dsv.omgadf = dsv.omgadf + ssg * dsv.t
|
||||
dsv.xnode = dsv.xnode + ssh * dsv.t
|
||||
dsv.em = tle.eccn + sse * dsv.t
|
||||
dsv.xinc = tle.xincl + ssi * dsv.t
|
||||
dsv.em = params.eccn + sse * dsv.t
|
||||
dsv.xinc = params.xincl + ssi * dsv.t
|
||||
if (dsv.xinc < 0) {
|
||||
dsv.xinc = -dsv.xinc
|
||||
dsv.xnode = dsv.xnode + Math.PI
|
||||
|
||||
+1
-1
@@ -17,4 +17,4 @@
|
||||
*/
|
||||
package com.rtbishop.look4sat.domain.predict4kotlin
|
||||
|
||||
data class Position(val latitude: Double, val longitude: Double)
|
||||
data class GeoPos(val latitude: Double, val longitude: Double)
|
||||
@@ -19,7 +19,7 @@ package com.rtbishop.look4sat.domain.predict4kotlin
|
||||
|
||||
import kotlin.math.*
|
||||
|
||||
class NearEarthSat(tle: TLE) : Satellite(tle) {
|
||||
class NearEarthSat(params: TLE) : Satellite(params) {
|
||||
|
||||
private val aodp: Double
|
||||
private val aycof: Double
|
||||
@@ -53,18 +53,18 @@ class NearEarthSat(tle: TLE) : Satellite(tle) {
|
||||
|
||||
init {
|
||||
// Recover original mean motion (xnodp) and semimajor axis (aodp) from input elements
|
||||
val a1 = (xke / super.tle.xno).pow(twoThirds)
|
||||
cosio = cos(super.tle.xincl)
|
||||
val a1 = (xke / super.params.xno).pow(twoThirds)
|
||||
cosio = cos(super.params.xincl)
|
||||
val theta2 = sqr(cosio)
|
||||
x3thm1 = 3.0 * theta2 - 1.0
|
||||
val eo = super.tle.eccn
|
||||
val eo = super.params.eccn
|
||||
val eosq = sqr(eo)
|
||||
val betao2 = 1.0 - eosq
|
||||
val betao = sqrt(betao2)
|
||||
val del1 = 1.5 * ck2 * x3thm1 / (sqr(a1) * betao * betao2)
|
||||
val ao = a1 * (1.0 - del1 * (0.5 * twoThirds + del1 * (1.0 + 134.0 / 81.0 * del1)))
|
||||
val delo = 1.5 * ck2 * x3thm1 / (sqr(ao) * betao * betao2)
|
||||
xnodp = super.tle.xno / (1.0 + delo)
|
||||
xnodp = super.params.xno / (1.0 + delo)
|
||||
aodp = ao / (1.0 - delo)
|
||||
|
||||
// For perigee less than 220 kilometers, the "simple" flag is set
|
||||
@@ -80,15 +80,15 @@ class NearEarthSat(tle: TLE) : Satellite(tle) {
|
||||
val psisq = abs(1.0 - etasq)
|
||||
val coef = qoms24 * tsi.pow(4.0)
|
||||
val coef1 = coef / psisq.pow(3.5)
|
||||
val bstar = super.tle.bstar
|
||||
val bstar = super.params.bstar
|
||||
val c2 = coef1 * xnodp * (aodp * (1.0 + 1.5 * etasq + eeta * (4.0 + etasq)) + 0.75
|
||||
* ck2 * tsi / psisq * x3thm1 * (8.0 + 3.0 * etasq * (8.0 + etasq)))
|
||||
c1 = bstar * c2
|
||||
sinio = sin(super.tle.xincl)
|
||||
sinio = sin(super.params.xincl)
|
||||
val a3ovk2 = -j3Harmonic / ck2
|
||||
val c3 = coef * tsi * a3ovk2 * xnodp * sinio / eo
|
||||
x1mth2 = 1.0 - theta2
|
||||
val omegao = super.tle.omegao
|
||||
val omegao = super.params.omegao
|
||||
c4 = 2 * xnodp * coef1 * aodp * betao2 * (eta * (2.0 + 0.5 * etasq) + eo * (0.5 + 2 * etasq)
|
||||
- 2 * ck2 * tsi / (aodp * psisq) * (-3 * x3thm1 * (1.0 - 2 * eeta + etasq
|
||||
* (1.5 - 0.5 * eeta)) + 0.75 * x1mth2 * (2.0 * etasq - eeta * (1.0 + etasq))
|
||||
@@ -112,7 +112,7 @@ class NearEarthSat(tle: TLE) : Satellite(tle) {
|
||||
t2cof = 1.5 * c1
|
||||
xlcof = 0.125 * a3ovk2 * sinio * (3.0 + 5 * cosio) / (1.0 + cosio)
|
||||
aycof = 0.25 * a3ovk2 * sinio
|
||||
val xmo = super.tle.xmo
|
||||
val xmo = super.params.xmo
|
||||
delmo = (1.0 + eta * cos(xmo)).pow(3.0)
|
||||
sinmo = sin(xmo)
|
||||
x7thm1 = 7.0 * theta2 - 1
|
||||
@@ -138,14 +138,14 @@ class NearEarthSat(tle: TLE) : Satellite(tle) {
|
||||
fun calculateSGP4(tSince: Double) {
|
||||
synchronized(this) {
|
||||
val temp = DoubleArray(9)
|
||||
val xmdf = tle.xmo + xmdot * tSince
|
||||
val omgadf = tle.omegao + omgdot * tSince
|
||||
val xnoddf = tle.xnodeo + xnodot * tSince
|
||||
val xmdf = params.xmo + xmdot * tSince
|
||||
val omgadf = params.omegao + omgdot * tSince
|
||||
val xnoddf = params.xnodeo + xnodot * tSince
|
||||
var omega = omgadf
|
||||
var xmp = xmdf
|
||||
val tsq = sqr(tSince)
|
||||
val xnode = xnoddf + xnodcf * tsq
|
||||
val bstar = tle.bstar
|
||||
val bstar = params.bstar
|
||||
var tempa = 1.0 - c1 * tSince
|
||||
var tempe = bstar * c4 * tSince
|
||||
var templ = t2cof * tsq
|
||||
@@ -162,7 +162,7 @@ class NearEarthSat(tle: TLE) : Satellite(tle) {
|
||||
templ += t3cof * tcube + tfour * (t4cof + tSince * t5cof)
|
||||
}
|
||||
val a = aodp * tempa.pow(2.0)
|
||||
val eo = tle.eccn
|
||||
val eo = params.eccn
|
||||
val e = eo - tempe
|
||||
val xl = xmp + omega + xnode + xnodp * templ
|
||||
val beta = sqrt(1.0 - e * e)
|
||||
@@ -213,7 +213,7 @@ class NearEarthSat(tle: TLE) : Satellite(tle) {
|
||||
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
|
||||
val xnodek = xnode + 1.5 * temp[2] * cosio * sin2u
|
||||
val xinck = tle.xincl + 1.5 * temp[2] * cosio * sinio * cos2u
|
||||
val xinck = params.xincl + 1.5 * temp[2] * cosio * sinio * cos2u
|
||||
val rdotk = rdot - xn * temp[1] * x1mth2 * sin2u
|
||||
val rfdotk = rfdot + xn * temp[1] * (x1mth2 * cos2u + 1.5 * x3thm1)
|
||||
super.calculatePosAndVel(rk, uk, xnodek, xinck, rdotk, rfdotk)
|
||||
|
||||
@@ -18,11 +18,10 @@
|
||||
package com.rtbishop.look4sat.domain.predict4kotlin
|
||||
|
||||
import java.util.*
|
||||
import kotlin.math.*
|
||||
|
||||
class PassPredictor(private val satellite: Satellite, private val stationPos: StationPosition) {
|
||||
class PassPredictor(private val satellite: Satellite, private val stationPos: StationPos) {
|
||||
|
||||
private val oneQuarterOrbitMin = (24.0 * 60.0 / satellite.tle.meanmo / 4.0).toInt()
|
||||
private val oneQuarterOrbitMin = (24.0 * 60.0 / satellite.params.meanmo / 4.0).toInt()
|
||||
private val speedOfLight = 2.99792458E8
|
||||
|
||||
fun getDownlinkFreq(freq: Long, date: Date): Long {
|
||||
@@ -41,7 +40,7 @@ class PassPredictor(private val satellite: Satellite, private val stationPos: St
|
||||
|
||||
fun getPositions(refDate: Date, stepSec: Int, minBefore: Int, orbits: Double): List<SatPos> {
|
||||
val positions = mutableListOf<SatPos>()
|
||||
val orbitalPeriod = 24 * 60 / satellite.tle.meanmo
|
||||
val orbitalPeriod = 24 * 60 / satellite.params.meanmo
|
||||
val endDate = Date(refDate.time + (orbitalPeriod * orbits * 60L * 1000L).toLong())
|
||||
val startDate = Date(refDate.time - minBefore * 60L * 1000L)
|
||||
var currentDate = startDate
|
||||
@@ -60,16 +59,15 @@ class PassPredictor(private val satellite: Satellite, private val stationPos: St
|
||||
var lastAosDate: Date
|
||||
var count = 0
|
||||
if (satellite.willBeSeen(stationPos)) {
|
||||
if (satellite.tle.isDeepspace) {
|
||||
if (satellite.params.isDeepspace) {
|
||||
passes.add(nextDeepSpacePass(refDate))
|
||||
} else {
|
||||
do {
|
||||
if (count > 0) shouldWindBack = false
|
||||
val pass = nextNearEarthPass(startDate, shouldWindBack)
|
||||
lastAosDate = pass.aosDate
|
||||
lastAosDate = Date(pass.aosTime)
|
||||
passes.add(pass)
|
||||
startDate =
|
||||
Date(pass.losDate.time + (oneQuarterOrbitMin * 3) * 60L * 1000L)
|
||||
startDate = Date(pass.losTime + (oneQuarterOrbitMin * 3) * 60L * 1000L)
|
||||
count++
|
||||
} while (lastAosDate < endDate)
|
||||
}
|
||||
@@ -79,16 +77,13 @@ class PassPredictor(private val satellite: Satellite, private val stationPos: St
|
||||
|
||||
private fun nextDeepSpacePass(refDate: Date): SatPass {
|
||||
val satPos = getSatPos(refDate)
|
||||
val id = satellite.tle.catnum
|
||||
val name = satellite.tle.name
|
||||
val isDeep = satellite.tle.isDeepspace
|
||||
val aos = Date(refDate.time - 24 * 60L * 60L * 1000L).time
|
||||
val los = Date(refDate.time + 24 * 60L * 60L * 1000L).time
|
||||
val tca = Date((aos + los) / 2).time
|
||||
val az = Math.toDegrees(satPos.azimuth)
|
||||
val elev = Math.toDegrees(satPos.elevation)
|
||||
val alt = satPos.altitude
|
||||
return SatPass(id, name, isDeep, aos, az, los, az, tca, az, alt, elev, this)
|
||||
return SatPass(aos, az, los, az, tca, az, alt, elev, satellite, this)
|
||||
}
|
||||
|
||||
private fun nextNearEarthPass(refDate: Date, windBack: Boolean = false): SatPass {
|
||||
@@ -96,9 +91,6 @@ class PassPredictor(private val satellite: Satellite, private val stationPos: St
|
||||
clear()
|
||||
timeInMillis = refDate.time
|
||||
}
|
||||
val id = satellite.tle.catnum
|
||||
val name = satellite.tle.name
|
||||
val isDeep = satellite.tle.isDeepspace
|
||||
|
||||
var elevation: Double
|
||||
var maxElevation = 0.0
|
||||
@@ -176,6 +168,6 @@ class PassPredictor(private val satellite: Satellite, private val stationPos: St
|
||||
val losAz = Math.toDegrees(satPos.azimuth)
|
||||
val tca = Date((aos + los) / 2).time
|
||||
val elev = Math.toDegrees(maxElevation)
|
||||
return SatPass(id, name, isDeep, aos, aosAz, los, losAz, tca, tcaAz, alt, elev, this)
|
||||
return SatPass(aos, aosAz, los, losAz, tca, tcaAz, alt, elev, satellite, this)
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import kotlin.math.round
|
||||
|
||||
class QthConverter {
|
||||
|
||||
fun qthToPosition(qthString: String): Position? {
|
||||
fun qthToPosition(qthString: String): GeoPos? {
|
||||
val trimmedQth = qthString.take(6)
|
||||
if (!isValidQTH(trimmedQth)) return null
|
||||
val lonFirst = (trimmedQth[0].uppercaseChar().code - 65) * 20
|
||||
@@ -32,7 +32,7 @@ class QthConverter {
|
||||
val latThird = (((trimmedQth[5].lowercaseChar().code - 97) / 24.0) + (1.0 / 48.0)) - 90
|
||||
val longitude = (lonFirst + lonSecond + lonThird).roundToDecimals(4)
|
||||
val latitude = (latFirst + latSecond + latThird).roundToDecimals(4)
|
||||
return Position(latitude, longitude)
|
||||
return GeoPos(latitude, longitude)
|
||||
}
|
||||
|
||||
fun positionToQTH(lat: Double, lon: Double): String? {
|
||||
|
||||
@@ -17,29 +17,20 @@
|
||||
*/
|
||||
package com.rtbishop.look4sat.domain.predict4kotlin
|
||||
|
||||
import java.util.*
|
||||
|
||||
data class SatPass(
|
||||
val catNum: Int,
|
||||
val name: String,
|
||||
val isDeepSpace: Boolean,
|
||||
private val aosTime: Long,
|
||||
val aosTime: Long,
|
||||
val aosAzimuth: Double,
|
||||
private val losTime: Long,
|
||||
val losTime: Long,
|
||||
val losAzimuth: Double,
|
||||
private val tcaTime: Long,
|
||||
val tcaTime: Long,
|
||||
val tcaAzimuth: Double,
|
||||
val altitude: Double,
|
||||
val maxElevation: Double,
|
||||
val satellite: Satellite,
|
||||
val predictor: PassPredictor,
|
||||
var progress: Int = 0
|
||||
) {
|
||||
val aosDate: Date
|
||||
get() = Date(aosTime)
|
||||
|
||||
val losDate: Date
|
||||
get() = Date(losTime)
|
||||
|
||||
val tcaDate: Date
|
||||
get() = Date(tcaTime)
|
||||
val catNum: Int = satellite.params.catnum
|
||||
val name: String = satellite.params.name
|
||||
val isDeepSpace: Boolean = satellite.params.isDeepspace
|
||||
var progress: Int = 0
|
||||
}
|
||||
@@ -20,9 +20,6 @@ package com.rtbishop.look4sat.domain.predict4kotlin
|
||||
import java.util.*
|
||||
import kotlin.math.*
|
||||
|
||||
const val earthRadiusKm = 6378.16
|
||||
const val speedOfLight = 2.99792458E8
|
||||
|
||||
data class SatPos(
|
||||
var azimuth: Double = 0.0,
|
||||
var elevation: Double = 0.0,
|
||||
@@ -33,48 +30,51 @@ data class SatPos(
|
||||
var rangeRate: Double = 0.0,
|
||||
var theta: Double = 0.0,
|
||||
var time: Date = Date()
|
||||
)
|
||||
) {
|
||||
private val earthRadiusKm = 6378.16
|
||||
private val speedOfLight = 2.99792458E8
|
||||
|
||||
fun SatPos.getDownlinkFreq(freq: Long): Long {
|
||||
return (freq.toDouble() * (speedOfLight - this.rangeRate * 1000.0) / speedOfLight).toLong()
|
||||
}
|
||||
|
||||
fun SatPos.getUplinkFreq(freq: Long): Long {
|
||||
return (freq.toDouble() * (speedOfLight + this.rangeRate * 1000.0) / speedOfLight).toLong()
|
||||
}
|
||||
|
||||
fun SatPos.getRangeCircle(): List<Position> {
|
||||
val positions = mutableListOf<Position>()
|
||||
val lat = this.latitude
|
||||
val lon = this.longitude
|
||||
// rangeCircleRadiusKm
|
||||
// earthRadiusKm * acos(earthRadiusKm / (earthRadiusKm + satPos.altitude))
|
||||
val beta = acos(earthRadiusKm / (earthRadiusKm + this.altitude))
|
||||
var tempAzimuth = 0
|
||||
while (tempAzimuth < 360) {
|
||||
val azimuth = tempAzimuth / 360.0 * 2.0 * Math.PI
|
||||
var rangelat = asin(sin(lat) * cos(beta) + cos(azimuth) * sin(beta) * cos(lat))
|
||||
val num = (cos(beta) - (sin(lat) * sin(rangelat)))
|
||||
val den = cos(lat) * cos(rangelat)
|
||||
var rangelon = if (tempAzimuth == 0 && (beta > ((Math.PI / 2.0) - lat))) {
|
||||
lon + Math.PI
|
||||
} else if (tempAzimuth == 180 && (beta > ((Math.PI / 2.0) - lat))) {
|
||||
lon + Math.PI
|
||||
} else if (abs(num / den) > 1.0) {
|
||||
lon
|
||||
} else {
|
||||
if ((180 - tempAzimuth) >= 0) {
|
||||
lon - acos(num / den)
|
||||
} else {
|
||||
lon + acos(num / den)
|
||||
}
|
||||
}
|
||||
while (rangelon < 0.0) rangelon += Math.PI * 2.0
|
||||
while (rangelon > Math.PI * 2.0) rangelon -= Math.PI * 2.0
|
||||
rangelat = Math.toDegrees(rangelat)
|
||||
rangelon = Math.toDegrees(rangelon)
|
||||
positions.add(Position(rangelat, rangelon))
|
||||
tempAzimuth += 1
|
||||
fun getDownlinkFreq(freq: Long): Long {
|
||||
return (freq.toDouble() * (speedOfLight - this.rangeRate * 1000.0) / speedOfLight).toLong()
|
||||
}
|
||||
|
||||
fun getUplinkFreq(freq: Long): Long {
|
||||
return (freq.toDouble() * (speedOfLight + this.rangeRate * 1000.0) / speedOfLight).toLong()
|
||||
}
|
||||
|
||||
fun getRangeCircle(): List<GeoPos> {
|
||||
val positions = mutableListOf<GeoPos>()
|
||||
val lat = this.latitude
|
||||
val lon = this.longitude
|
||||
// rangeCircleRadiusKm
|
||||
// earthRadiusKm * acos(earthRadiusKm / (earthRadiusKm + satPos.altitude))
|
||||
val beta = acos(earthRadiusKm / (earthRadiusKm + this.altitude))
|
||||
var tempAzimuth = 0
|
||||
while (tempAzimuth < 360) {
|
||||
val azimuth = tempAzimuth / 360.0 * 2.0 * Math.PI
|
||||
var rangelat = asin(sin(lat) * cos(beta) + cos(azimuth) * sin(beta) * cos(lat))
|
||||
val num = (cos(beta) - (sin(lat) * sin(rangelat)))
|
||||
val den = cos(lat) * cos(rangelat)
|
||||
var rangelon = if (tempAzimuth == 0 && (beta > ((Math.PI / 2.0) - lat))) {
|
||||
lon + Math.PI
|
||||
} else if (tempAzimuth == 180 && (beta > ((Math.PI / 2.0) - lat))) {
|
||||
lon + Math.PI
|
||||
} else if (abs(num / den) > 1.0) {
|
||||
lon
|
||||
} else {
|
||||
if ((180 - tempAzimuth) >= 0) {
|
||||
lon - acos(num / den)
|
||||
} else {
|
||||
lon + acos(num / den)
|
||||
}
|
||||
}
|
||||
while (rangelon < 0.0) rangelon += Math.PI * 2.0
|
||||
while (rangelon > Math.PI * 2.0) rangelon -= Math.PI * 2.0
|
||||
rangelat = Math.toDegrees(rangelat)
|
||||
rangelon = Math.toDegrees(rangelon)
|
||||
positions.add(GeoPos(rangelat, rangelon))
|
||||
tempAzimuth += 1
|
||||
}
|
||||
return positions
|
||||
}
|
||||
return positions
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.math.*
|
||||
|
||||
abstract class Satellite(val tle: TLE) {
|
||||
abstract class Satellite(val params: TLE) {
|
||||
|
||||
private val flatFactor = 3.35281066474748E-3
|
||||
private val deg2Rad = 1.745329251994330E-2
|
||||
@@ -41,26 +41,26 @@ abstract class Satellite(val tle: TLE) {
|
||||
var s4 = 0.0
|
||||
val xke = 7.43669161E-2
|
||||
|
||||
fun willBeSeen(pos: StationPosition): Boolean {
|
||||
return if (tle.meanmo < 1e-8) false else {
|
||||
var lin = tle.incl
|
||||
fun willBeSeen(pos: StationPos): Boolean {
|
||||
return if (params.meanmo < 1e-8) false else {
|
||||
var lin = params.incl
|
||||
if (lin >= 90.0) lin = 180.0 - lin
|
||||
val sma = 331.25 * exp(ln(1440.0 / tle.meanmo) * (2.0 / 3.0))
|
||||
val apogee = sma * (1.0 + tle.eccn) - earthRadius
|
||||
val sma = 331.25 * exp(ln(1440.0 / params.meanmo) * (2.0 / 3.0))
|
||||
val apogee = sma * (1.0 + params.eccn) - earthRadius
|
||||
acos(earthRadius / (apogee + earthRadius)) + lin * deg2Rad > abs(pos.latitude * deg2Rad)
|
||||
}
|
||||
}
|
||||
|
||||
fun getPredictor(pos: StationPosition): PassPredictor {
|
||||
fun getPredictor(pos: StationPos): PassPredictor {
|
||||
return PassPredictor(this, pos)
|
||||
}
|
||||
|
||||
fun getPosition(pos: StationPosition, time: Date): SatPos {
|
||||
fun getPosition(pos: StationPos, time: Date): SatPos {
|
||||
val satPos = SatPos()
|
||||
// Date/time at which the position and velocity were calculated
|
||||
val julUTC = calcCurrentDaynum(time) + 2444238.5
|
||||
// Convert satellite's epoch time to Julian and calculate time since epoch in minutes
|
||||
val julEpoch = juliandDateOfEpoch(tle.epoch)
|
||||
val julEpoch = juliandDateOfEpoch(params.epoch)
|
||||
val tsince = (julUTC - julEpoch) * minPerDay
|
||||
calculateSDP4orSGP4(tsince)
|
||||
// Scale position and velocity vectors to km and km/sec
|
||||
@@ -105,7 +105,7 @@ abstract class Satellite(val tle: TLE) {
|
||||
}
|
||||
|
||||
private fun calculateSDP4orSGP4(tsince: Double) {
|
||||
if (tle.isDeepspace) (this as DeepSpaceSat).calculateSDP4(tsince)
|
||||
if (params.isDeepspace) (this as DeepSpaceSat).calculateSDP4(tsince)
|
||||
else (this as NearEarthSat).calculateSGP4(tsince)
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ abstract class Satellite(val tle: TLE) {
|
||||
julianUTC: Double,
|
||||
positionVector: Vector4,
|
||||
velocityVector: Vector4,
|
||||
gsPos: StationPosition,
|
||||
gsPos: StationPos,
|
||||
squintVector: Vector4,
|
||||
satPos: SatPos
|
||||
) {
|
||||
@@ -162,7 +162,7 @@ abstract class Satellite(val tle: TLE) {
|
||||
// Returns the ECI position and velocity of the observer
|
||||
private fun calculateUserPosVel(
|
||||
time: Double,
|
||||
gsPos: StationPosition,
|
||||
gsPos: StationPos,
|
||||
gsPosTheta: AtomicReference<Double>,
|
||||
obsPos: Vector4,
|
||||
obsVel: Vector4
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.rtbishop.look4sat.domain.predict4kotlin
|
||||
|
||||
data class SkyPos(val azimuth: Double, val elevation: Double)
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package com.rtbishop.look4sat.domain.predict4kotlin
|
||||
|
||||
data class StationPosition(
|
||||
data class StationPos(
|
||||
val latitude: Double,
|
||||
val longitude: Double,
|
||||
val altitude: Double,
|
||||
@@ -39,13 +39,16 @@ data class TLE(
|
||||
val xno: Double = meanmo * Math.PI * 2.0 / 1440
|
||||
val isDeepspace: Boolean = meanmo < 6.4
|
||||
|
||||
fun createSat(): Satellite {
|
||||
return when {
|
||||
this.isDeepspace -> DeepSpaceSat(this)
|
||||
else -> NearEarthSat(this)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun createSat(array: Array<String>): Satellite? {
|
||||
return importElement(array)?.createSat()
|
||||
}
|
||||
|
||||
fun importElement(array: Array<String>): TLE? {
|
||||
fun parseArray(array: Array<String>): TLE? {
|
||||
if (array.size != 3) return null
|
||||
try {
|
||||
val name: String = array[0].trim()
|
||||
@@ -65,7 +68,7 @@ data class TLE(
|
||||
}
|
||||
}
|
||||
|
||||
fun importElements(stream: InputStream): List<TLE> {
|
||||
fun parseStream(stream: InputStream): List<TLE> {
|
||||
val elementArray = arrayOf(String(), String(), String())
|
||||
val importedElements = mutableListOf<TLE>()
|
||||
var line = 0
|
||||
@@ -75,7 +78,7 @@ data class TLE(
|
||||
line++
|
||||
} else {
|
||||
elementArray[line] = it
|
||||
importElement(elementArray)?.let { tle -> importedElements.add(tle) }
|
||||
parseArray(elementArray)?.let { tle -> importedElements.add(tle) }
|
||||
line = 0
|
||||
}
|
||||
}
|
||||
@@ -83,10 +86,3 @@ data class TLE(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun TLE.createSat(): Satellite {
|
||||
return when {
|
||||
this.isDeepspace -> DeepSpaceSat(this)
|
||||
else -> NearEarthSat(this)
|
||||
}
|
||||
}
|
||||
@@ -7,20 +7,20 @@ class Look4SatTest {
|
||||
|
||||
@Test
|
||||
fun `Given correct TLE array returns Satellite`() {
|
||||
val elementArray = arrayOf(
|
||||
val tleArray = arrayOf(
|
||||
"ISS (ZARYA)",
|
||||
"1 25544U 98067A 21242.56000419 .00070558 00000-0 12956-2 0 9996",
|
||||
"2 25544 51.6433 334.9559 0003020 334.9496 106.9882 15.48593918300128"
|
||||
)
|
||||
assert(TLE.createSat(elementArray) != null)
|
||||
assert(TLE.parseArray(tleArray)?.createSat() != null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given incorrect TLE array returns null`() {
|
||||
val elementArray = arrayOf(
|
||||
val tleArray = arrayOf(
|
||||
"1 25544U 98067A 21242.56000419 .00070558 00000-0 12956-2 0 9996",
|
||||
"2 25544 51.6433 334.9559 0003020 334.9496 106.9882 15.48593918300128"
|
||||
)
|
||||
assert(TLE.createSat(elementArray) == null)
|
||||
assert(TLE.parseArray(tleArray)?.createSat() == null)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user