mirror of
https://github.com/rt-bishop/Look4Sat.git
synced 2026-10-05 12:17:22 +00:00
Further refactoring of Look4Sat core module classes
This commit is contained in:
1 parent
2c757a6b35
commit
bceeb01194
46 files changed
+603
-572
No files matched your search
@@ -22,8 +22,8 @@ import android.hardware.GeomagneticField
|
|||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import com.rtbishop.look4sat.data.PreferencesSource
|
import com.rtbishop.look4sat.data.PreferencesSource
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.QthConverter
|
import com.rtbishop.look4sat.domain.QthConverter
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.StationPos
|
import com.rtbishop.look4sat.domain.StationPos
|
||||||
import com.rtbishop.look4sat.utility.round
|
import com.rtbishop.look4sat.utility.round
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.Types
|
import com.squareup.moshi.Types
|
||||||
@@ -31,7 +31,6 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
class PreferencesProvider @Inject constructor(
|
class PreferencesProvider @Inject constructor(
|
||||||
moshi: Moshi,
|
moshi: Moshi,
|
||||||
private val qthConverter: QthConverter,
|
|
||||||
private val locationManager: LocationManager,
|
private val locationManager: LocationManager,
|
||||||
private val preferences: SharedPreferences
|
private val preferences: SharedPreferences
|
||||||
) : PreferencesSource {
|
) : PreferencesSource {
|
||||||
@@ -86,7 +85,7 @@ class PreferencesProvider @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun positionToQTH(lat: Double, lon: Double): String? {
|
override fun positionToQTH(lat: Double, lon: Double): String? {
|
||||||
return qthConverter.positionToQTH(lat, lon)
|
return QthConverter.positionToQTH(lat, lon)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadStationPosition(): StationPos {
|
override fun loadStationPosition(): StationPos {
|
||||||
@@ -123,7 +122,7 @@ class PreferencesProvider @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun updatePositionFromQTH(qthString: String): Boolean {
|
override fun updatePositionFromQTH(qthString: String): Boolean {
|
||||||
val position = qthConverter.qthToPosition(qthString) ?: return false
|
val position = QthConverter.qthToPosition(qthString) ?: return false
|
||||||
val stationPosition = StationPos(position.latitude, position.longitude, 0.0)
|
val stationPosition = StationPos(position.latitude, position.longitude, 0.0)
|
||||||
saveStationPosition(stationPosition)
|
saveStationPosition(stationPosition)
|
||||||
return true
|
return true
|
||||||
|
|||||||
+3
-3
@@ -17,12 +17,12 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.framework.api
|
package com.rtbishop.look4sat.framework.api
|
||||||
|
|
||||||
import com.rtbishop.look4sat.data.SatDataRemoteSource
|
import com.rtbishop.look4sat.data.RemoteDataSource
|
||||||
import com.rtbishop.look4sat.domain.model.SatTrans
|
import com.rtbishop.look4sat.domain.SatTrans
|
||||||
import com.rtbishop.look4sat.utility.DataMapper
|
import com.rtbishop.look4sat.utility.DataMapper
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
class SatDataRemote(private val api: SatDataService) : SatDataRemoteSource {
|
class RemoteSource(private val api: SatelliteService) : RemoteDataSource {
|
||||||
|
|
||||||
override suspend fun fetchDataStream(url: String): InputStream? {
|
override suspend fun fetchDataStream(url: String): InputStream? {
|
||||||
return api.fetchFileByUrl(url).body()?.byteStream()
|
return api.fetchFileByUrl(url).body()?.byteStream()
|
||||||
+1
-1
@@ -24,7 +24,7 @@ import retrofit2.http.GET
|
|||||||
import retrofit2.http.Streaming
|
import retrofit2.http.Streaming
|
||||||
import retrofit2.http.Url
|
import retrofit2.http.Url
|
||||||
|
|
||||||
interface SatDataService {
|
interface SatelliteService {
|
||||||
|
|
||||||
@Streaming
|
@Streaming
|
||||||
@GET
|
@GET
|
||||||
+8
-8
@@ -18,34 +18,34 @@
|
|||||||
package com.rtbishop.look4sat.framework.db
|
package com.rtbishop.look4sat.framework.db
|
||||||
|
|
||||||
import androidx.room.TypeConverter
|
import androidx.room.TypeConverter
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
|
import com.rtbishop.look4sat.domain.Satellite
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.TLE
|
import com.rtbishop.look4sat.domain.TLE
|
||||||
import com.squareup.moshi.JsonAdapter
|
import com.squareup.moshi.JsonAdapter
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
|
|
||||||
object RoomConverters {
|
object Converters {
|
||||||
|
|
||||||
private lateinit var tleAdapter: JsonAdapter<TLE>
|
private lateinit var paramsAdapter: JsonAdapter<TLE>
|
||||||
|
|
||||||
fun initialize(moshi: Moshi) {
|
fun initialize(moshi: Moshi) {
|
||||||
tleAdapter = moshi.adapter(TLE::class.java)
|
paramsAdapter = moshi.adapter(TLE::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun tleToString(tle: TLE): String {
|
fun tleToString(tle: TLE): String {
|
||||||
return tleAdapter.toJson(tle)
|
return paramsAdapter.toJson(tle)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun tleFromString(string: String): TLE? {
|
fun tleFromString(string: String): TLE? {
|
||||||
return tleAdapter.fromJson(string)
|
return paramsAdapter.fromJson(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun satFromString(string: String): Satellite? {
|
fun satFromString(string: String): Satellite? {
|
||||||
return tleAdapter.fromJson(string)?.createSat()
|
return paramsAdapter.fromJson(string)?.createSat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+12
-12
@@ -17,42 +17,42 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.framework.db
|
package com.rtbishop.look4sat.framework.db
|
||||||
|
|
||||||
import com.rtbishop.look4sat.data.SatDataLocalSource
|
import com.rtbishop.look4sat.data.LocalDataSource
|
||||||
import com.rtbishop.look4sat.domain.model.SatEntry
|
import com.rtbishop.look4sat.domain.SatEntry
|
||||||
import com.rtbishop.look4sat.domain.model.SatItem
|
import com.rtbishop.look4sat.domain.SatItem
|
||||||
import com.rtbishop.look4sat.domain.model.SatTrans
|
import com.rtbishop.look4sat.domain.SatTrans
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
|
import com.rtbishop.look4sat.domain.Satellite
|
||||||
import com.rtbishop.look4sat.utility.DataMapper
|
import com.rtbishop.look4sat.utility.DataMapper
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
class SatDataLocal(private val satDataDao: SatDataDao) : SatDataLocalSource {
|
class LocalSource(private val satelliteDao: SatelliteDao) : LocalDataSource {
|
||||||
|
|
||||||
override fun getSatItems(): Flow<List<SatItem>> {
|
override fun getSatItems(): Flow<List<SatItem>> {
|
||||||
return satDataDao.getSatItems()
|
return satelliteDao.getSatItems()
|
||||||
.map { satItems -> DataMapper.satItemsToDomainItems(satItems) }
|
.map { satItems -> DataMapper.satItemsToDomainItems(satItems) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getSelectedSatellites(): List<Satellite> {
|
override suspend fun getSelectedSatellites(): List<Satellite> {
|
||||||
return satDataDao.getSelectedSatellites()
|
return satelliteDao.getSelectedSatellites()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateEntries(entries: List<SatEntry>) {
|
override suspend fun updateEntries(entries: List<SatEntry>) {
|
||||||
val satEntries = entries.map { entry -> DataMapper.domainEntryToSatEntry(entry) }
|
val satEntries = entries.map { entry -> DataMapper.domainEntryToSatEntry(entry) }
|
||||||
satDataDao.updateEntries(satEntries)
|
satelliteDao.updateEntries(satEntries)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
|
override suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
|
||||||
satDataDao.updateEntriesSelection(catNums, isSelected)
|
satelliteDao.updateEntriesSelection(catNums, isSelected)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSatTransmitters(catNum: Int): Flow<List<SatTrans>> {
|
override fun getSatTransmitters(catNum: Int): Flow<List<SatTrans>> {
|
||||||
return satDataDao.getSatTransmitters(catNum)
|
return satelliteDao.getSatTransmitters(catNum)
|
||||||
.map { satTransList -> DataMapper.satTransListToDomainTransList(satTransList) }
|
.map { satTransList -> DataMapper.satTransListToDomainTransList(satTransList) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateTransmitters(satelliteTrans: List<SatTrans>) {
|
override suspend fun updateTransmitters(satelliteTrans: List<SatTrans>) {
|
||||||
val satTransList = DataMapper.domainTransListToSatTransList(satelliteTrans)
|
val satTransList = DataMapper.domainTransListToSatTransList(satelliteTrans)
|
||||||
satDataDao.updateTransmitters(satTransList)
|
satelliteDao.updateTransmitters(satTransList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -21,11 +21,11 @@ import androidx.room.*
|
|||||||
import com.rtbishop.look4sat.framework.model.SatEntry
|
import com.rtbishop.look4sat.framework.model.SatEntry
|
||||||
import com.rtbishop.look4sat.framework.model.SatItem
|
import com.rtbishop.look4sat.framework.model.SatItem
|
||||||
import com.rtbishop.look4sat.framework.model.SatTrans
|
import com.rtbishop.look4sat.framework.model.SatTrans
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
|
import com.rtbishop.look4sat.domain.Satellite
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface SatDataDao {
|
interface SatelliteDao {
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
@Query("SELECT catNum, name, isSelected FROM entries ORDER BY name ASC")
|
@Query("SELECT catNum, name, isSelected FROM entries ORDER BY name ASC")
|
||||||
+3
-3
@@ -24,8 +24,8 @@ import com.rtbishop.look4sat.framework.model.SatEntry
|
|||||||
import com.rtbishop.look4sat.framework.model.SatTrans
|
import com.rtbishop.look4sat.framework.model.SatTrans
|
||||||
|
|
||||||
@Database(entities = [SatEntry::class, SatTrans::class], version = 1, exportSchema = false)
|
@Database(entities = [SatEntry::class, SatTrans::class], version = 1, exportSchema = false)
|
||||||
@TypeConverters(RoomConverters::class)
|
@TypeConverters(Converters::class)
|
||||||
abstract class SatDataDb : RoomDatabase() {
|
abstract class SatelliteDb : RoomDatabase() {
|
||||||
|
|
||||||
abstract fun satelliteDao(): SatDataDao
|
abstract fun satelliteDao(): SatelliteDao
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ package com.rtbishop.look4sat.framework.model
|
|||||||
|
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.TLE
|
import com.rtbishop.look4sat.domain.TLE
|
||||||
|
|
||||||
@Entity(tableName = "entries")
|
@Entity(tableName = "entries")
|
||||||
data class SatEntry(
|
data class SatEntry(
|
||||||
|
|||||||
+25
-35
@@ -22,14 +22,13 @@ import android.content.SharedPreferences
|
|||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import com.rtbishop.look4sat.data.*
|
import com.rtbishop.look4sat.data.*
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.QthConverter
|
|
||||||
import com.rtbishop.look4sat.framework.PreferencesProvider
|
import com.rtbishop.look4sat.framework.PreferencesProvider
|
||||||
import com.rtbishop.look4sat.framework.api.SatDataRemote
|
import com.rtbishop.look4sat.framework.api.RemoteSource
|
||||||
import com.rtbishop.look4sat.framework.api.SatDataService
|
import com.rtbishop.look4sat.framework.api.SatelliteService
|
||||||
import com.rtbishop.look4sat.framework.db.RoomConverters
|
import com.rtbishop.look4sat.framework.db.Converters
|
||||||
import com.rtbishop.look4sat.framework.db.SatDataDao
|
import com.rtbishop.look4sat.framework.db.LocalSource
|
||||||
import com.rtbishop.look4sat.framework.db.SatDataDb
|
import com.rtbishop.look4sat.framework.db.SatelliteDao
|
||||||
import com.rtbishop.look4sat.framework.db.SatDataLocal
|
import com.rtbishop.look4sat.framework.db.SatelliteDb
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
@@ -45,12 +44,7 @@ import javax.net.ssl.HostnameVerifier
|
|||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
object SatDataModule {
|
object SatelliteDataModule {
|
||||||
|
|
||||||
@Provides
|
|
||||||
fun provideQthConverter(): QthConverter {
|
|
||||||
return QthConverter()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideMoshi(): Moshi {
|
fun provideMoshi(): Moshi {
|
||||||
@@ -61,56 +55,52 @@ object SatDataModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
fun providePreferenceSource(
|
fun providePreferenceSource(
|
||||||
moshi: Moshi,
|
moshi: Moshi,
|
||||||
qthConverter: QthConverter,
|
|
||||||
locationManager: LocationManager,
|
locationManager: LocationManager,
|
||||||
preferences: SharedPreferences
|
preferences: SharedPreferences
|
||||||
): PreferencesSource {
|
): PreferencesSource {
|
||||||
return PreferencesProvider(moshi, qthConverter, locationManager, preferences)
|
return PreferencesProvider(moshi, locationManager, preferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun providePassesRepo(
|
fun providePassPredictor(@DefaultDispatcher dispatcher: CoroutineDispatcher): PassPredictor {
|
||||||
preferencesSource: PreferencesSource,
|
return PassPredictor(dispatcher)
|
||||||
@DefaultDispatcher defaultDispatcher: CoroutineDispatcher
|
|
||||||
): SatPassRepository {
|
|
||||||
return SatPassRepository(preferencesSource, defaultDispatcher)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideSatelliteRepo(
|
fun provideSatelliteRepo(
|
||||||
preferencesSource: PreferencesSource,
|
preferencesSource: PreferencesSource,
|
||||||
satLocalSource: SatDataLocalSource,
|
localDataSource: LocalDataSource,
|
||||||
satRemoteSource: SatDataRemoteSource,
|
remoteDataSource: RemoteDataSource,
|
||||||
@IoDispatcher ioDispatcher: CoroutineDispatcher
|
@IoDispatcher dispatcher: CoroutineDispatcher
|
||||||
): SatDataRepository {
|
): SatelliteRepo {
|
||||||
return SatDataRepository(preferencesSource, satLocalSource, satRemoteSource, ioDispatcher)
|
return SatelliteRepo(preferencesSource, localDataSource, remoteDataSource, dispatcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideLocalDataSource(satDataDao: SatDataDao): SatDataLocalSource {
|
fun provideLocalDataSource(satelliteDao: SatelliteDao): LocalDataSource {
|
||||||
return SatDataLocal(satDataDao)
|
return LocalSource(satelliteDao)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideSatDataDao(db: SatDataDb): SatDataDao {
|
fun provideSatelliteDao(db: SatelliteDb): SatelliteDao {
|
||||||
return db.satelliteDao()
|
return db.satelliteDao()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideSatelliteDb(@ApplicationContext context: Context, moshi: Moshi): SatDataDb {
|
fun provideSatelliteDb(@ApplicationContext context: Context, moshi: Moshi): SatelliteDb {
|
||||||
RoomConverters.initialize(moshi)
|
Converters.initialize(moshi)
|
||||||
return Room.databaseBuilder(context, SatDataDb::class.java, "SatelliteDb").build()
|
return Room.databaseBuilder(context, SatelliteDb::class.java, "SatelliteDb").build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideRemoteDataSource(satDataService: SatDataService): SatDataRemoteSource {
|
fun provideRemoteDataSource(satelliteService: SatelliteService): RemoteDataSource {
|
||||||
return SatDataRemote(satDataService)
|
return RemoteSource(satelliteService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideSatDataService(): SatDataService {
|
fun provideSatelliteService(): SatelliteService {
|
||||||
val verifier = HostnameVerifier { _, _ -> true }
|
val verifier = HostnameVerifier { _, _ -> true }
|
||||||
val httpClient = OkHttpClient.Builder().hostnameVerifier(verifier).build()
|
val httpClient = OkHttpClient.Builder().hostnameVerifier(verifier).build()
|
||||||
return Retrofit.Builder()
|
return Retrofit.Builder()
|
||||||
@@ -118,6 +108,6 @@ object SatDataModule {
|
|||||||
.client(httpClient)
|
.client(httpClient)
|
||||||
.addConverterFactory(MoshiConverterFactory.create())
|
.addConverterFactory(MoshiConverterFactory.create())
|
||||||
.build()
|
.build()
|
||||||
.create(SatDataService::class.java)
|
.create(SatelliteService::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -23,7 +23,7 @@ import androidx.recyclerview.widget.AsyncListDiffer
|
|||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.rtbishop.look4sat.databinding.SatItemBinding
|
import com.rtbishop.look4sat.databinding.SatItemBinding
|
||||||
import com.rtbishop.look4sat.domain.model.SatItem
|
import com.rtbishop.look4sat.domain.SatItem
|
||||||
|
|
||||||
class SatItemAdapter : RecyclerView.Adapter<SatItemAdapter.SatItemHolder>() {
|
class SatItemAdapter : RecyclerView.Adapter<SatItemAdapter.SatItemHolder>() {
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ import androidx.recyclerview.widget.SimpleItemAnimator
|
|||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.rtbishop.look4sat.R
|
import com.rtbishop.look4sat.R
|
||||||
import com.rtbishop.look4sat.databinding.FragmentEntriesBinding
|
import com.rtbishop.look4sat.databinding.FragmentEntriesBinding
|
||||||
import com.rtbishop.look4sat.domain.model.SatItem
|
import com.rtbishop.look4sat.domain.SatItem
|
||||||
import com.rtbishop.look4sat.framework.model.Result
|
import com.rtbishop.look4sat.framework.model.Result
|
||||||
import com.rtbishop.look4sat.utility.RecyclerDivider
|
import com.rtbishop.look4sat.utility.RecyclerDivider
|
||||||
import com.rtbishop.look4sat.utility.getNavResult
|
import com.rtbishop.look4sat.utility.getNavResult
|
||||||
|
|||||||
+10
-10
@@ -22,8 +22,8 @@ import android.net.Uri
|
|||||||
import android.widget.SearchView
|
import android.widget.SearchView
|
||||||
import androidx.lifecycle.*
|
import androidx.lifecycle.*
|
||||||
import com.rtbishop.look4sat.data.PreferencesSource
|
import com.rtbishop.look4sat.data.PreferencesSource
|
||||||
import com.rtbishop.look4sat.data.SatDataRepository
|
import com.rtbishop.look4sat.data.SatelliteRepo
|
||||||
import com.rtbishop.look4sat.domain.model.SatItem
|
import com.rtbishop.look4sat.domain.SatItem
|
||||||
import com.rtbishop.look4sat.framework.model.Result
|
import com.rtbishop.look4sat.framework.model.Result
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
@@ -35,13 +35,13 @@ import javax.inject.Inject
|
|||||||
class SatItemViewModel @Inject constructor(
|
class SatItemViewModel @Inject constructor(
|
||||||
private val preferencesSource: PreferencesSource,
|
private val preferencesSource: PreferencesSource,
|
||||||
private val resolver: ContentResolver,
|
private val resolver: ContentResolver,
|
||||||
private val satDataRepository: SatDataRepository,
|
private val satelliteRepo: SatelliteRepo,
|
||||||
) : ViewModel(), SatItemAdapter.EntriesClickListener, SearchView.OnQueryTextListener {
|
) : ViewModel(), SatItemAdapter.EntriesClickListener, SearchView.OnQueryTextListener {
|
||||||
|
|
||||||
private val transModes = MutableLiveData(satDataRepository.loadSelectedModes())
|
private val transModes = MutableLiveData(satelliteRepo.loadSelectedModes())
|
||||||
private val currentQuery = MutableLiveData(String())
|
private val currentQuery = MutableLiveData(String())
|
||||||
private val itemsWithModes = transModes.switchMap { modes ->
|
private val itemsWithModes = transModes.switchMap { modes ->
|
||||||
liveData { satDataRepository.getSatItems().collect { emit(filterByModes(it, modes)) } }
|
liveData { satelliteRepo.getSatItems().collect { emit(filterByModes(it, modes)) } }
|
||||||
}
|
}
|
||||||
private val itemsWithQuery = currentQuery.switchMap { query ->
|
private val itemsWithQuery = currentQuery.switchMap { query ->
|
||||||
itemsWithModes.map { items -> Result.Success(filterByQuery(items, query)) }
|
itemsWithModes.map { items -> Result.Success(filterByQuery(items, query)) }
|
||||||
@@ -57,7 +57,7 @@ class SatItemViewModel @Inject constructor(
|
|||||||
_satData.value = Result.InProgress
|
_satData.value = Result.InProgress
|
||||||
runCatching {
|
runCatching {
|
||||||
resolver.openInputStream(uri)?.use { stream ->
|
resolver.openInputStream(uri)?.use { stream ->
|
||||||
satDataRepository.updateEntriesFromFile(stream)
|
satelliteRepo.updateEntriesFromFile(stream)
|
||||||
}
|
}
|
||||||
}.onFailure { _satData.value = Result.Error(it) }
|
}.onFailure { _satData.value = Result.Error(it) }
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ class SatItemViewModel @Inject constructor(
|
|||||||
_satData.value = Result.InProgress
|
_satData.value = Result.InProgress
|
||||||
try {
|
try {
|
||||||
preferencesSource.saveTleSources(sources)
|
preferencesSource.saveTleSources(sources)
|
||||||
satDataRepository.updateEntriesFromWeb(sources)
|
satelliteRepo.updateEntriesFromWeb(sources)
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
_satData.value = Result.Error(exception)
|
_satData.value = Result.Error(exception)
|
||||||
}
|
}
|
||||||
@@ -84,12 +84,12 @@ class SatItemViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun loadSelectedModes(): List<String> {
|
fun loadSelectedModes(): List<String> {
|
||||||
return satDataRepository.loadSelectedModes()
|
return satelliteRepo.loadSelectedModes()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveSelectedModes(modes: List<String>) {
|
fun saveSelectedModes(modes: List<String>) {
|
||||||
transModes.value = modes
|
transModes.value = modes
|
||||||
satDataRepository.saveSelectedModes(modes)
|
satelliteRepo.saveSelectedModes(modes)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQueryTextSubmit(query: String): Boolean {
|
override fun onQueryTextSubmit(query: String): Boolean {
|
||||||
@@ -102,7 +102,7 @@ class SatItemViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun updateSelection(catNums: List<Int>, isSelected: Boolean) {
|
override fun updateSelection(catNums: List<Int>, isSelected: Boolean) {
|
||||||
viewModelScope.launch { satDataRepository.updateEntriesSelection(catNums, isSelected) }
|
viewModelScope.launch { satelliteRepo.updateEntriesSelection(catNums, isSelected) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun filterByModes(items: List<SatItem>, modes: List<String>): List<SatItem> {
|
private fun filterByModes(items: List<SatItem>, modes: List<String>): List<SatItem> {
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ import androidx.fragment.app.Fragment
|
|||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import com.rtbishop.look4sat.R
|
import com.rtbishop.look4sat.R
|
||||||
import com.rtbishop.look4sat.databinding.FragmentMapBinding
|
import com.rtbishop.look4sat.databinding.FragmentMapBinding
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.GeoPos
|
import com.rtbishop.look4sat.domain.GeoPos
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
|
import com.rtbishop.look4sat.domain.Satellite
|
||||||
import com.rtbishop.look4sat.framework.model.SatData
|
import com.rtbishop.look4sat.domain.SatData
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import org.osmdroid.config.Configuration
|
import org.osmdroid.config.Configuration
|
||||||
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
|
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
|
||||||
@@ -89,7 +89,7 @@ class SatMapFragment : Fragment(R.layout.fragment_map) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setupObservers(binding: FragmentMapBinding) {
|
private fun setupObservers(binding: FragmentMapBinding) {
|
||||||
viewModel.stationPos.observe(viewLifecycleOwner, { renderStationPos(it, binding) })
|
viewModel.stationPosLiveData.observe(viewLifecycleOwner, { renderStationPos(it, binding) })
|
||||||
viewModel.satPositions.observe(viewLifecycleOwner, { renderSatPositions(it, binding) })
|
viewModel.satPositions.observe(viewLifecycleOwner, { renderSatPositions(it, binding) })
|
||||||
viewModel.satTrack.observe(viewLifecycleOwner, { renderSatTrack(it, binding) })
|
viewModel.satTrack.observe(viewLifecycleOwner, { renderSatTrack(it, binding) })
|
||||||
viewModel.satFootprint.observe(viewLifecycleOwner, { renderSatFootprint(it, binding) })
|
viewModel.satFootprint.observe(viewLifecycleOwner, { renderSatFootprint(it, binding) })
|
||||||
|
|||||||
+24
-23
@@ -19,11 +19,12 @@ package com.rtbishop.look4sat.presentation.satMapScreen
|
|||||||
|
|
||||||
import androidx.lifecycle.*
|
import androidx.lifecycle.*
|
||||||
import com.rtbishop.look4sat.data.PreferencesSource
|
import com.rtbishop.look4sat.data.PreferencesSource
|
||||||
import com.rtbishop.look4sat.data.SatDataRepository
|
import com.rtbishop.look4sat.data.SatelliteRepo
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.GeoPos
|
import com.rtbishop.look4sat.data.PassPredictor
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
|
import com.rtbishop.look4sat.domain.GeoPos
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.StationPos
|
import com.rtbishop.look4sat.domain.Satellite
|
||||||
import com.rtbishop.look4sat.framework.model.SatData
|
import com.rtbishop.look4sat.domain.StationPos
|
||||||
|
import com.rtbishop.look4sat.domain.SatData
|
||||||
import com.rtbishop.look4sat.injection.DefaultDispatcher
|
import com.rtbishop.look4sat.injection.DefaultDispatcher
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
@@ -35,19 +36,20 @@ import kotlin.math.sqrt
|
|||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class SatMapViewModel @Inject constructor(
|
class SatMapViewModel @Inject constructor(
|
||||||
private val satDataRepository: SatDataRepository,
|
private val satelliteRepo: SatelliteRepo,
|
||||||
private val preferencesSource: PreferencesSource,
|
private val passPredictor: PassPredictor,
|
||||||
|
private val preferences: PreferencesSource,
|
||||||
@DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher
|
@DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val gsp = preferencesSource.loadStationPosition()
|
private val stationPos = preferences.loadStationPosition()
|
||||||
private var dataUpdateJob: Job? = null
|
private var dataUpdateJob: Job? = null
|
||||||
private var allSatList = listOf<Satellite>()
|
private var allSatList = listOf<Satellite>()
|
||||||
private lateinit var selectedSat: Satellite
|
private lateinit var selectedSat: Satellite
|
||||||
|
|
||||||
val stationPos = liveData {
|
val stationPosLiveData = liveData {
|
||||||
val osmLat = clipLat(gsp.latitude)
|
val osmLat = clipLat(stationPos.latitude)
|
||||||
val osmLon = clipLon(gsp.longitude)
|
val osmLon = clipLon(stationPos.longitude)
|
||||||
emit(GeoPos(osmLat, osmLon))
|
emit(GeoPos(osmLat, osmLon))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +67,7 @@ class SatMapViewModel @Inject constructor(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
satDataRepository.getSelectedSatellites().also { selectedSatellites ->
|
satelliteRepo.getSelectedSatellites().also { selectedSatellites ->
|
||||||
if (selectedSatellites.isNotEmpty()) {
|
if (selectedSatellites.isNotEmpty()) {
|
||||||
allSatList = selectedSatellites
|
allSatList = selectedSatellites
|
||||||
selectSatellite(selectedSatellites.first())
|
selectSatellite(selectedSatellites.first())
|
||||||
@@ -75,7 +77,7 @@ class SatMapViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun shouldUseTextLabels(): Boolean {
|
fun shouldUseTextLabels(): Boolean {
|
||||||
return preferencesSource.shouldUseTextLabels()
|
return preferences.shouldUseTextLabels()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scrollSelection(decrement: Boolean) {
|
fun scrollSelection(decrement: Boolean) {
|
||||||
@@ -97,12 +99,12 @@ class SatMapViewModel @Inject constructor(
|
|||||||
dataUpdateJob?.cancelAndJoin()
|
dataUpdateJob?.cancelAndJoin()
|
||||||
dataUpdateJob = launch {
|
dataUpdateJob = launch {
|
||||||
val dateNow = Date()
|
val dateNow = Date()
|
||||||
setSelectedSatTrack(selectedSat, gsp, dateNow)
|
setSelectedSatTrack(selectedSat, stationPos, dateNow)
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
dateNow.time = System.currentTimeMillis()
|
dateNow.time = System.currentTimeMillis()
|
||||||
setSatPositions(allSatList, gsp, dateNow)
|
setSatPositions(allSatList, stationPos, dateNow)
|
||||||
setSelectedSatFootprint(selectedSat, gsp, dateNow)
|
setSelectedSatFootprint(selectedSat, stationPos, dateNow)
|
||||||
setSelectedSatData(selectedSat, gsp, dateNow)
|
setSelectedSatData(selectedSat, stationPos, dateNow)
|
||||||
delay(updateFreq)
|
delay(updateFreq)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,7 +115,7 @@ class SatMapViewModel @Inject constructor(
|
|||||||
withContext(defaultDispatcher) {
|
withContext(defaultDispatcher) {
|
||||||
val satPositions = mutableMapOf<Satellite, GeoPos>()
|
val satPositions = mutableMapOf<Satellite, GeoPos>()
|
||||||
list.forEach { satellite ->
|
list.forEach { satellite ->
|
||||||
val satPos = satellite.getPredictor(gsp).getSatPos(date)
|
val satPos = satellite.getPosition(gsp, date.time)
|
||||||
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
|
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
|
||||||
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
|
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
|
||||||
satPositions[satellite] = GeoPos(osmLat, osmLon)
|
satPositions[satellite] = GeoPos(osmLat, osmLon)
|
||||||
@@ -127,7 +129,7 @@ class SatMapViewModel @Inject constructor(
|
|||||||
val satTracks = mutableListOf<List<GeoPos>>()
|
val satTracks = mutableListOf<List<GeoPos>>()
|
||||||
val currentTrack = mutableListOf<GeoPos>()
|
val currentTrack = mutableListOf<GeoPos>()
|
||||||
var oldLongitude = 0.0
|
var oldLongitude = 0.0
|
||||||
sat.getPredictor(gsp).getPositions(date, 15, 0, 2.4).forEach { satPos ->
|
passPredictor.getPositions(sat, gsp, date, 15, 0, 2.4).forEach { satPos ->
|
||||||
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
|
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
|
||||||
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
|
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
|
||||||
val currentPosition = GeoPos(osmLat, osmLon)
|
val currentPosition = GeoPos(osmLat, osmLon)
|
||||||
@@ -154,7 +156,7 @@ class SatMapViewModel @Inject constructor(
|
|||||||
|
|
||||||
private suspend fun setSelectedSatFootprint(sat: Satellite, gsp: StationPos, date: Date) {
|
private suspend fun setSelectedSatFootprint(sat: Satellite, gsp: StationPos, date: Date) {
|
||||||
withContext(defaultDispatcher) {
|
withContext(defaultDispatcher) {
|
||||||
val satFootprint = sat.getPosition(gsp, date).getRangeCircle().map { rangePos ->
|
val satFootprint = sat.getPosition(gsp, date.time).getRangeCircle().map { rangePos ->
|
||||||
val osmLat = clipLat(rangePos.latitude)
|
val osmLat = clipLat(rangePos.latitude)
|
||||||
val osmLon = clipLon(rangePos.longitude)
|
val osmLon = clipLon(rangePos.longitude)
|
||||||
GeoPos(osmLat, osmLon)
|
GeoPos(osmLat, osmLon)
|
||||||
@@ -165,12 +167,11 @@ class SatMapViewModel @Inject constructor(
|
|||||||
|
|
||||||
private suspend fun setSelectedSatData(sat: Satellite, gsp: StationPos, date: Date) {
|
private suspend fun setSelectedSatData(sat: Satellite, gsp: StationPos, date: Date) {
|
||||||
withContext(defaultDispatcher) {
|
withContext(defaultDispatcher) {
|
||||||
val satPos = sat.getPredictor(gsp).getSatPos(date)
|
val satPos = sat.getPosition(gsp, date.time)
|
||||||
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
|
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
|
||||||
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
|
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
|
||||||
val osmPos = GeoPos(osmLat, osmLon)
|
val osmPos = GeoPos(osmLat, osmLon)
|
||||||
val qthLoc =
|
val qthLoc = preferences.positionToQTH(osmPos.latitude, osmPos.longitude) ?: "-- --"
|
||||||
preferencesSource.positionToQTH(osmPos.latitude, osmPos.longitude) ?: "-- --"
|
|
||||||
val velocity = getOrbitalVelocity(satPos.altitude)
|
val velocity = getOrbitalVelocity(satPos.altitude)
|
||||||
val satData = SatData(
|
val satData = SatData(
|
||||||
sat, sat.params.catnum, sat.params.name, satPos.range,
|
sat, sat.params.catnum, sat.params.name, satPos.range,
|
||||||
|
|||||||
+7
-4
@@ -27,7 +27,7 @@ import androidx.recyclerview.widget.SimpleItemAnimator
|
|||||||
import com.rtbishop.look4sat.R
|
import com.rtbishop.look4sat.R
|
||||||
import com.rtbishop.look4sat.data.PreferencesSource
|
import com.rtbishop.look4sat.data.PreferencesSource
|
||||||
import com.rtbishop.look4sat.databinding.FragmentPolarBinding
|
import com.rtbishop.look4sat.databinding.FragmentPolarBinding
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.SatPass
|
import com.rtbishop.look4sat.domain.SatPass
|
||||||
import com.rtbishop.look4sat.utility.RecyclerDivider
|
import com.rtbishop.look4sat.utility.RecyclerDivider
|
||||||
import com.rtbishop.look4sat.utility.navigateSafe
|
import com.rtbishop.look4sat.utility.navigateSafe
|
||||||
import com.rtbishop.look4sat.utility.toTimerString
|
import com.rtbishop.look4sat.utility.toTimerString
|
||||||
@@ -39,7 +39,7 @@ import javax.inject.Inject
|
|||||||
class PassInfoFragment : Fragment(R.layout.fragment_polar) {
|
class PassInfoFragment : Fragment(R.layout.fragment_polar) {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var preferencesSource: PreferencesSource
|
lateinit var preferences: PreferencesSource
|
||||||
private val viewModel: PassInfoViewModel by viewModels()
|
private val viewModel: PassInfoViewModel by viewModels()
|
||||||
private var passInfoView: PassInfoView? = null
|
private var passInfoView: PassInfoView? = null
|
||||||
|
|
||||||
@@ -72,10 +72,12 @@ class PassInfoFragment : Fragment(R.layout.fragment_polar) {
|
|||||||
private fun setupObservers(satTransAdapter: SatTransAdapter, binding: FragmentPolarBinding) {
|
private fun setupObservers(satTransAdapter: SatTransAdapter, binding: FragmentPolarBinding) {
|
||||||
val catNum = requireArguments().getInt("catNum")
|
val catNum = requireArguments().getInt("catNum")
|
||||||
val aosTime = requireArguments().getLong("aosTime")
|
val aosTime = requireArguments().getLong("aosTime")
|
||||||
|
val stationPos = preferences.loadStationPosition()
|
||||||
viewModel.getPass(catNum, aosTime).observe(viewLifecycleOwner) { pass ->
|
viewModel.getPass(catNum, aosTime).observe(viewLifecycleOwner) { pass ->
|
||||||
passInfoView = PassInfoView(requireContext()).apply {
|
passInfoView = PassInfoView(requireContext()).apply {
|
||||||
setShowAim(preferencesSource.shouldUseCompass())
|
setShowAim(preferences.shouldUseCompass())
|
||||||
setPass(pass)
|
setPass(pass)
|
||||||
|
setStationPos(stationPos)
|
||||||
}
|
}
|
||||||
binding.frame.addView(passInfoView)
|
binding.frame.addView(passInfoView)
|
||||||
observeTransmitters(pass, satTransAdapter, binding)
|
observeTransmitters(pass, satTransAdapter, binding)
|
||||||
@@ -107,7 +109,8 @@ class PassInfoFragment : Fragment(R.layout.fragment_polar) {
|
|||||||
private fun setPassText(satPass: SatPass, binding: FragmentPolarBinding) {
|
private fun setPassText(satPass: SatPass, binding: FragmentPolarBinding) {
|
||||||
val dateNow = Date()
|
val dateNow = Date()
|
||||||
val timeNow = System.currentTimeMillis()
|
val timeNow = System.currentTimeMillis()
|
||||||
val satPos = satPass.predictor.getSatPos(dateNow)
|
val stationPos = preferences.loadStationPosition()
|
||||||
|
val satPos = satPass.satellite.getPosition(stationPos, dateNow.time)
|
||||||
val polarAz = getString(R.string.pat_azimuth)
|
val polarAz = getString(R.string.pat_azimuth)
|
||||||
val polarEl = getString(R.string.pat_elevation)
|
val polarEl = getString(R.string.pat_elevation)
|
||||||
val polarRng = getString(R.string.pat_distance)
|
val polarRng = getString(R.string.pat_distance)
|
||||||
|
|||||||
+9
-3
@@ -22,7 +22,8 @@ import android.graphics.*
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.rtbishop.look4sat.R
|
import com.rtbishop.look4sat.R
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.SatPass
|
import com.rtbishop.look4sat.domain.SatPass
|
||||||
|
import com.rtbishop.look4sat.domain.StationPos
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
@@ -37,6 +38,7 @@ class PassInfoView(context: Context) : View(context) {
|
|||||||
private val radarRadius = radarWidth * 0.48f
|
private val radarRadius = radarWidth * 0.48f
|
||||||
private val piDiv2 = Math.PI / 2.0
|
private val piDiv2 = Math.PI / 2.0
|
||||||
private val strokeSize = scale * 2f
|
private val strokeSize = scale * 2f
|
||||||
|
private var stationPos: StationPos = StationPos(0.0, 0.0, 0.0)
|
||||||
|
|
||||||
private var radarColor = ContextCompat.getColor(context, R.color.greyLight)
|
private var radarColor = ContextCompat.getColor(context, R.color.greyLight)
|
||||||
private var radarCircleNum = 3
|
private var radarCircleNum = 3
|
||||||
@@ -101,6 +103,10 @@ class PassInfoView(context: Context) : View(context) {
|
|||||||
this.satPass = satPass
|
this.satPass = satPass
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setStationPos(stationPos: StationPos) {
|
||||||
|
this.stationPos = stationPos
|
||||||
|
}
|
||||||
|
|
||||||
fun setShowAim(showAim: Boolean) {
|
fun setShowAim(showAim: Boolean) {
|
||||||
shouldShowAim = showAim
|
shouldShowAim = showAim
|
||||||
}
|
}
|
||||||
@@ -170,7 +176,7 @@ class PassInfoView(context: Context) : View(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun drawSatellite(canvas: Canvas, satPass: SatPass) {
|
private fun drawSatellite(canvas: Canvas, satPass: SatPass) {
|
||||||
val satPos = satPass.predictor.getSatPos(Date())
|
val satPos = satPass.satellite.getPosition(stationPos, Date().time)
|
||||||
if (satPos.elevation > 0) {
|
if (satPos.elevation > 0) {
|
||||||
val satX = sph2CartX(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
val satX = sph2CartX(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
||||||
val satY = sph2CartY(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
val satY = sph2CartY(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
||||||
@@ -202,7 +208,7 @@ class PassInfoView(context: Context) : View(context) {
|
|||||||
private fun createPassTrajectory(satPass: SatPass) {
|
private fun createPassTrajectory(satPass: SatPass) {
|
||||||
var currentTime = satPass.aosTime
|
var currentTime = satPass.aosTime
|
||||||
while (currentTime < satPass.losTime) {
|
while (currentTime < satPass.losTime) {
|
||||||
val satPos = satPass.predictor.getSatPos(Date(currentTime))
|
val satPos = satPass.satellite.getPosition(stationPos, currentTime)
|
||||||
val passX = sph2CartX(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
val passX = sph2CartX(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
||||||
val passY = sph2CartY(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
val passY = sph2CartY(satPos.azimuth, satPos.elevation, radarRadius.toDouble())
|
||||||
if (currentTime == satPass.aosTime) {
|
if (currentTime == satPass.aosTime) {
|
||||||
|
|||||||
+19
-18
@@ -18,12 +18,12 @@
|
|||||||
package com.rtbishop.look4sat.presentation.satPassInfoScreen
|
package com.rtbishop.look4sat.presentation.satPassInfoScreen
|
||||||
|
|
||||||
import androidx.lifecycle.*
|
import androidx.lifecycle.*
|
||||||
import com.rtbishop.look4sat.data.SatPassRepository
|
import com.rtbishop.look4sat.data.PassPredictor
|
||||||
import com.rtbishop.look4sat.data.PreferencesSource
|
import com.rtbishop.look4sat.data.PreferencesSource
|
||||||
import com.rtbishop.look4sat.data.SatDataRepository
|
import com.rtbishop.look4sat.data.SatelliteRepo
|
||||||
import com.rtbishop.look4sat.injection.IoDispatcher
|
import com.rtbishop.look4sat.injection.IoDispatcher
|
||||||
import com.rtbishop.look4sat.domain.model.SatTrans
|
import com.rtbishop.look4sat.domain.SatTrans
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.SatPass
|
import com.rtbishop.look4sat.domain.SatPass
|
||||||
import com.rtbishop.look4sat.framework.OrientationProvider
|
import com.rtbishop.look4sat.framework.OrientationProvider
|
||||||
import com.rtbishop.look4sat.utility.round
|
import com.rtbishop.look4sat.utility.round
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
@@ -37,19 +37,20 @@ import javax.inject.Inject
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class PassInfoViewModel @Inject constructor(
|
class PassInfoViewModel @Inject constructor(
|
||||||
private val orientationProvider: OrientationProvider,
|
private val orientationProvider: OrientationProvider,
|
||||||
private val preferencesSource: PreferencesSource,
|
private val preferences: PreferencesSource,
|
||||||
private val satPassRepository: SatPassRepository,
|
private val passPredictor: PassPredictor,
|
||||||
private val satDataRepository: SatDataRepository,
|
private val satelliteRepo: SatelliteRepo,
|
||||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher
|
||||||
) : ViewModel(), OrientationProvider.OrientationListener {
|
) : ViewModel(), OrientationProvider.OrientationListener {
|
||||||
|
|
||||||
|
private val stationPos = preferences.loadStationPosition()
|
||||||
private val _transmitters = MutableLiveData<List<SatTrans>>()
|
private val _transmitters = MutableLiveData<List<SatTrans>>()
|
||||||
private val _orientation = MutableLiveData<Triple<Float, Float, Float>>()
|
private val _orientation = MutableLiveData<Triple<Float, Float, Float>>()
|
||||||
val transmitters: LiveData<List<SatTrans>> = _transmitters
|
val transmitters: LiveData<List<SatTrans>> = _transmitters
|
||||||
val orientation: LiveData<Triple<Float, Float, Float>> = _orientation
|
val orientation: LiveData<Triple<Float, Float, Float>> = _orientation
|
||||||
|
|
||||||
fun getPass(catNum: Int, aosTime: Long) = liveData {
|
fun getPass(catNum: Int, aosTime: Long) = liveData {
|
||||||
satPassRepository.passes.collect { passes ->
|
passPredictor.passes.collect { passes ->
|
||||||
val pass = passes.find { it.catNum == catNum && it.aosTime == aosTime }
|
val pass = passes.find { it.catNum == catNum && it.aosTime == aosTime }
|
||||||
pass?.let { satPass ->
|
pass?.let { satPass ->
|
||||||
processTransmitters(satPass)
|
processTransmitters(satPass)
|
||||||
@@ -60,27 +61,27 @@ class PassInfoViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun enableSensor() {
|
fun enableSensor() {
|
||||||
if (preferencesSource.shouldUseCompass()) orientationProvider.startListening(this)
|
if (preferences.shouldUseCompass()) orientationProvider.startListening(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun disableSensor() {
|
fun disableSensor() {
|
||||||
if (preferencesSource.shouldUseCompass()) orientationProvider.stopListening()
|
if (preferences.shouldUseCompass()) orientationProvider.stopListening()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOrientationChanged(azimuth: Float, pitch: Float, roll: Float) {
|
override fun onOrientationChanged(azimuth: Float, pitch: Float, roll: Float) {
|
||||||
_orientation.value = Triple(azimuth + preferencesSource.getMagDeclination(), pitch, roll)
|
_orientation.value = Triple(azimuth + preferences.getMagDeclination(), pitch, roll)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initRotatorControl(satPass: SatPass) {
|
private fun initRotatorControl(satPass: SatPass) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val rotatorPrefs = preferencesSource.getRotatorServer()
|
val rotatorPrefs = preferences.getRotatorServer()
|
||||||
if (rotatorPrefs != null) {
|
if (rotatorPrefs != null) {
|
||||||
runCatching {
|
runCatching {
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
val socket = Socket(rotatorPrefs.first, rotatorPrefs.second)
|
val socket = Socket(rotatorPrefs.first, rotatorPrefs.second)
|
||||||
val writer = socket.getOutputStream().bufferedWriter()
|
val writer = socket.getOutputStream().bufferedWriter()
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
val satPos = satPass.predictor.getSatPos(Date())
|
val satPos = satPass.satellite.getPosition(stationPos, Date().time)
|
||||||
val azimuth = Math.toDegrees(satPos.azimuth).round(1)
|
val azimuth = Math.toDegrees(satPos.azimuth).round(1)
|
||||||
val elevation = Math.toDegrees(satPos.elevation).round(1)
|
val elevation = Math.toDegrees(satPos.elevation).round(1)
|
||||||
writer.write("\\set_pos $azimuth $elevation")
|
writer.write("\\set_pos $azimuth $elevation")
|
||||||
@@ -97,16 +98,16 @@ class PassInfoViewModel @Inject constructor(
|
|||||||
|
|
||||||
private fun processTransmitters(satPass: SatPass) {
|
private fun processTransmitters(satPass: SatPass) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
satDataRepository.getSatTransmitters(satPass.catNum).collect { transList ->
|
satelliteRepo.getSatTransmitters(satPass.catNum).collect { transList ->
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
val timeNow = Date()
|
val satPos = satPass.satellite.getPosition(stationPos, Date().time)
|
||||||
val copiedList = transList.map { it.copy() }
|
val copiedList = transList.map { it.copy() }
|
||||||
copiedList.forEach { transmitter ->
|
copiedList.forEach { transmitter ->
|
||||||
transmitter.downlink?.let {
|
transmitter.downlink?.let {
|
||||||
transmitter.downlink = satPass.predictor.getDownlinkFreq(it, timeNow)
|
transmitter.downlink = satPos.getDownlinkFreq(it)
|
||||||
}
|
}
|
||||||
transmitter.uplink?.let {
|
transmitter.uplink?.let {
|
||||||
transmitter.uplink = satPass.predictor.getUplinkFreq(it, timeNow)
|
transmitter.uplink = satPos.getUplinkFreq(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_transmitters.postValue(copiedList.map { it.copy() })
|
_transmitters.postValue(copiedList.map { it.copy() })
|
||||||
@@ -115,4 +116,4 @@ class PassInfoViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -24,7 +24,7 @@ import androidx.recyclerview.widget.DiffUtil
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.rtbishop.look4sat.R
|
import com.rtbishop.look4sat.R
|
||||||
import com.rtbishop.look4sat.databinding.ItemTransBinding
|
import com.rtbishop.look4sat.databinding.ItemTransBinding
|
||||||
import com.rtbishop.look4sat.domain.model.SatTrans
|
import com.rtbishop.look4sat.domain.SatTrans
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class SatTransAdapter : RecyclerView.Adapter<SatTransAdapter.TransHolder>() {
|
class SatTransAdapter : RecyclerView.Adapter<SatTransAdapter.TransHolder>() {
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import com.rtbishop.look4sat.R
|
import com.rtbishop.look4sat.R
|
||||||
import com.rtbishop.look4sat.databinding.ItemPassGeoBinding
|
import com.rtbishop.look4sat.databinding.ItemPassGeoBinding
|
||||||
import com.rtbishop.look4sat.databinding.ItemPassLeoBinding
|
import com.rtbishop.look4sat.databinding.ItemPassLeoBinding
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.SatPass
|
import com.rtbishop.look4sat.domain.SatPass
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||||
import com.rtbishop.look4sat.R
|
import com.rtbishop.look4sat.R
|
||||||
import com.rtbishop.look4sat.databinding.FragmentPassesBinding
|
import com.rtbishop.look4sat.databinding.FragmentPassesBinding
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.SatPass
|
import com.rtbishop.look4sat.domain.SatPass
|
||||||
import com.rtbishop.look4sat.framework.model.Result
|
import com.rtbishop.look4sat.framework.model.Result
|
||||||
import com.rtbishop.look4sat.utility.RecyclerDivider
|
import com.rtbishop.look4sat.utility.RecyclerDivider
|
||||||
import com.rtbishop.look4sat.utility.navigateSafe
|
import com.rtbishop.look4sat.utility.navigateSafe
|
||||||
|
|||||||
+14
-11
@@ -22,9 +22,9 @@ import androidx.lifecycle.MutableLiveData
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.rtbishop.look4sat.data.PreferencesSource
|
import com.rtbishop.look4sat.data.PreferencesSource
|
||||||
import com.rtbishop.look4sat.data.SatDataRepository
|
import com.rtbishop.look4sat.data.SatelliteRepo
|
||||||
import com.rtbishop.look4sat.data.SatPassRepository
|
import com.rtbishop.look4sat.data.PassPredictor
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.SatPass
|
import com.rtbishop.look4sat.domain.SatPass
|
||||||
import com.rtbishop.look4sat.framework.model.Result
|
import com.rtbishop.look4sat.framework.model.Result
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
@@ -33,8 +33,8 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class SatPassViewModel @Inject constructor(
|
class SatPassViewModel @Inject constructor(
|
||||||
private val satDataRepository: SatDataRepository,
|
private val satelliteRepo: SatelliteRepo,
|
||||||
private val satPassRepository: SatPassRepository,
|
private val passPredictor: PassPredictor,
|
||||||
private val preferencesSource: PreferencesSource
|
private val preferencesSource: PreferencesSource
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
@@ -48,13 +48,14 @@ class SatPassViewModel @Inject constructor(
|
|||||||
if (preferencesSource.isSetupDone()) {
|
if (preferencesSource.isSetupDone()) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_passes.postValue(Result.InProgress)
|
_passes.postValue(Result.InProgress)
|
||||||
satPassRepository.triggerCalculation(satDataRepository.getSelectedSatellites())
|
val stationPos = preferencesSource.loadStationPosition()
|
||||||
|
passPredictor.triggerCalculation(satelliteRepo.getSelectedSatellites(), stationPos)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_isFirstLaunchDone.value = false
|
_isFirstLaunchDone.value = false
|
||||||
}
|
}
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
satPassRepository.passes.collect { passes ->
|
passPredictor.passes.collect { passes ->
|
||||||
passesProcessing?.cancelAndJoin()
|
passesProcessing?.cancelAndJoin()
|
||||||
passesProcessing = viewModelScope.launch { tickPasses(passes) }
|
passesProcessing = viewModelScope.launch { tickPasses(passes) }
|
||||||
}
|
}
|
||||||
@@ -66,9 +67,10 @@ class SatPassViewModel @Inject constructor(
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_passes.postValue(Result.InProgress)
|
_passes.postValue(Result.InProgress)
|
||||||
val defaultCatNums = listOf(43700, 25544, 25338, 28654, 33591, 40069, 27607, 24278)
|
val defaultCatNums = listOf(43700, 25544, 25338, 28654, 33591, 40069, 27607, 24278)
|
||||||
satDataRepository.updateEntriesFromWeb(preferencesSource.loadDefaultSources())
|
val stationPos = preferencesSource.loadStationPosition()
|
||||||
satDataRepository.updateEntriesSelection(defaultCatNums, true)
|
satelliteRepo.updateEntriesFromWeb(preferencesSource.loadDefaultSources())
|
||||||
satPassRepository.forceCalculation(satDataRepository.getSelectedSatellites())
|
satelliteRepo.updateEntriesSelection(defaultCatNums, true)
|
||||||
|
passPredictor.forceCalculation(satelliteRepo.getSelectedSatellites(), stationPos)
|
||||||
preferencesSource.setSetupDone()
|
preferencesSource.setSetupDone()
|
||||||
_isFirstLaunchDone.value = true
|
_isFirstLaunchDone.value = true
|
||||||
}
|
}
|
||||||
@@ -78,7 +80,8 @@ class SatPassViewModel @Inject constructor(
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_passes.postValue(Result.InProgress)
|
_passes.postValue(Result.InProgress)
|
||||||
passesProcessing?.cancelAndJoin()
|
passesProcessing?.cancelAndJoin()
|
||||||
satPassRepository.forceCalculation(satDataRepository.getSelectedSatellites())
|
val stationPos = preferencesSource.loadStationPosition()
|
||||||
|
passPredictor.forceCalculation(satelliteRepo.getSelectedSatellites(), stationPos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ package com.rtbishop.look4sat.utility
|
|||||||
import com.rtbishop.look4sat.framework.model.SatEntry
|
import com.rtbishop.look4sat.framework.model.SatEntry
|
||||||
import com.rtbishop.look4sat.framework.model.SatItem
|
import com.rtbishop.look4sat.framework.model.SatItem
|
||||||
import com.rtbishop.look4sat.framework.model.SatTrans
|
import com.rtbishop.look4sat.framework.model.SatTrans
|
||||||
import com.rtbishop.look4sat.domain.model.SatEntry as DomainEntry
|
import com.rtbishop.look4sat.domain.SatEntry as DomainEntry
|
||||||
import com.rtbishop.look4sat.domain.model.SatItem as DomainItem
|
import com.rtbishop.look4sat.domain.SatItem as DomainItem
|
||||||
import com.rtbishop.look4sat.domain.model.SatTrans as DomainTrans
|
import com.rtbishop.look4sat.domain.SatTrans as DomainTrans
|
||||||
|
|
||||||
object DataMapper {
|
object DataMapper {
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -17,13 +17,13 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.data
|
package com.rtbishop.look4sat.data
|
||||||
|
|
||||||
import com.rtbishop.look4sat.domain.model.SatEntry
|
import com.rtbishop.look4sat.domain.SatEntry
|
||||||
import com.rtbishop.look4sat.domain.model.SatItem
|
import com.rtbishop.look4sat.domain.SatItem
|
||||||
import com.rtbishop.look4sat.domain.model.SatTrans
|
import com.rtbishop.look4sat.domain.SatTrans
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
|
import com.rtbishop.look4sat.domain.Satellite
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface SatDataLocalSource {
|
interface LocalDataSource {
|
||||||
|
|
||||||
fun getSatItems(): Flow<List<SatItem>>
|
fun getSatItems(): Flow<List<SatItem>>
|
||||||
|
|
||||||
@@ -0,0 +1,388 @@
|
|||||||
|
/*
|
||||||
|
* 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.data
|
||||||
|
|
||||||
|
import com.rtbishop.look4sat.domain.*
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
import kotlin.math.pow
|
||||||
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
|
class PassPredictor(private val defaultDispatcher: CoroutineDispatcher) {
|
||||||
|
|
||||||
|
// Radar view - beacon
|
||||||
|
private val _skyPosition = MutableSharedFlow<SatPos>(replay = 1)
|
||||||
|
val skyPosition: SharedFlow<SatPos> = _skyPosition
|
||||||
|
|
||||||
|
suspend fun getSkyPosition(pass: SatPass, stationPos: StationPos, date: Date) {
|
||||||
|
withContext(defaultDispatcher) {
|
||||||
|
_skyPosition.emit(pass.satellite.getPosition(stationPos, date.time))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Radar view - track
|
||||||
|
private val _skyTrack = MutableSharedFlow<List<SatPos>>(replay = 1)
|
||||||
|
val skyTrack: SharedFlow<List<SatPos>> = _skyTrack
|
||||||
|
|
||||||
|
suspend fun getSkyTrack(pass: SatPass, stationPos: StationPos) {
|
||||||
|
withContext(defaultDispatcher) {
|
||||||
|
val positions = mutableListOf<SatPos>()
|
||||||
|
var currentTime = pass.aosTime
|
||||||
|
while (currentTime < pass.losTime) {
|
||||||
|
positions.add(pass.satellite.getPosition(stationPos, currentTime))
|
||||||
|
currentTime += 15000
|
||||||
|
}
|
||||||
|
_skyTrack.emit(positions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map view - beacons
|
||||||
|
private val _mapPositions = MutableSharedFlow<Map<Satellite, GeoPos>>(replay = 1)
|
||||||
|
val mapPositions: SharedFlow<Map<Satellite, GeoPos>> = _mapPositions
|
||||||
|
|
||||||
|
suspend fun getMapPositions(satellites: List<Satellite>, stationPos: StationPos, date: Date) {
|
||||||
|
withContext(defaultDispatcher) {
|
||||||
|
val positions = mutableMapOf<Satellite, GeoPos>()
|
||||||
|
satellites.forEach { satellite ->
|
||||||
|
val satPos = satellite.getPosition(stationPos, date.time)
|
||||||
|
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
|
||||||
|
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
|
||||||
|
positions[satellite] = GeoPos(osmLat, osmLon)
|
||||||
|
}
|
||||||
|
_mapPositions.emit(positions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map view - track
|
||||||
|
private val _mapTrack = MutableSharedFlow<List<List<GeoPos>>>(replay = 1)
|
||||||
|
val mapTrack: SharedFlow<List<List<GeoPos>>> = _mapTrack
|
||||||
|
|
||||||
|
suspend fun getMapTrack(satellite: Satellite, stationPos: StationPos, date: Date) {
|
||||||
|
withContext(defaultDispatcher) {
|
||||||
|
val positions = mutableListOf<SatPos>()
|
||||||
|
val orbitalPeriod = satellite.getQuarterOrbitMin() * 4
|
||||||
|
val endTime = date.time + (orbitalPeriod * 2.4 * 60L * 1000L).toLong()
|
||||||
|
var currentTime = date.time
|
||||||
|
while (currentTime < endTime) {
|
||||||
|
positions.add(satellite.getPosition(stationPos, currentTime))
|
||||||
|
currentTime += 15000
|
||||||
|
}
|
||||||
|
|
||||||
|
val satTracks = mutableListOf<List<GeoPos>>()
|
||||||
|
val currentTrack = mutableListOf<GeoPos>()
|
||||||
|
var oldLongitude = 0.0
|
||||||
|
positions.forEach { satPos ->
|
||||||
|
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
|
||||||
|
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
|
||||||
|
val currentPos = GeoPos(osmLat, osmLon)
|
||||||
|
if (oldLongitude < -170.0 && currentPos.longitude > 170.0) {
|
||||||
|
// adding left terminal position
|
||||||
|
currentTrack.add(GeoPos(osmLat, -180.0))
|
||||||
|
satTracks.add(mutableListOf<GeoPos>().apply { addAll(currentTrack) })
|
||||||
|
currentTrack.clear()
|
||||||
|
} else if (oldLongitude > 170.0 && currentPos.longitude < -170.0) {
|
||||||
|
// adding right terminal position
|
||||||
|
currentTrack.add(GeoPos(osmLat, 180.0))
|
||||||
|
satTracks.add(mutableListOf<GeoPos>().apply { addAll(currentTrack) })
|
||||||
|
currentTrack.clear()
|
||||||
|
}
|
||||||
|
oldLongitude = currentPos.longitude
|
||||||
|
currentTrack.add(currentPos)
|
||||||
|
}
|
||||||
|
satTracks.add(currentTrack)
|
||||||
|
_mapTrack.emit(satTracks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _mapFootprint = MutableSharedFlow<List<GeoPos>>(replay = 1)
|
||||||
|
val mapFootprint: SharedFlow<List<GeoPos>> = _mapFootprint
|
||||||
|
|
||||||
|
suspend fun setSelectedSatFootprint(satellite: Satellite, stationPos: StationPos, date: Date) {
|
||||||
|
withContext(defaultDispatcher) {
|
||||||
|
val satPos = satellite.getPosition(stationPos, date.time)
|
||||||
|
val satFootprint = satPos.getRangeCircle().map { groundPos ->
|
||||||
|
val osmLat = clipLat(groundPos.latitude)
|
||||||
|
val osmLon = clipLon(groundPos.longitude)
|
||||||
|
GeoPos(osmLat, osmLon)
|
||||||
|
}
|
||||||
|
_mapFootprint.emit(satFootprint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _satData = MutableSharedFlow<SatData>(replay = 1)
|
||||||
|
val satData: SharedFlow<SatData> = _satData
|
||||||
|
|
||||||
|
suspend fun setSelectedSatData(satellite: Satellite, stationPos: StationPos, date: Date) {
|
||||||
|
withContext(defaultDispatcher) {
|
||||||
|
val satPos = satellite.getPosition(stationPos, date.time)
|
||||||
|
val osmLat = clipLat(Math.toDegrees(satPos.latitude))
|
||||||
|
val osmLon = clipLon(Math.toDegrees(satPos.longitude))
|
||||||
|
val osmPos = GeoPos(osmLat, osmLon)
|
||||||
|
val qthLoc = QthConverter.positionToQTH(osmPos.latitude, osmPos.longitude) ?: "-- --"
|
||||||
|
val velocity = getOrbitalVelocity(satPos.altitude)
|
||||||
|
val satData = SatData(
|
||||||
|
satellite, satellite.params.catnum, satellite.params.name, satPos.range,
|
||||||
|
satPos.altitude, velocity, qthLoc, osmPos
|
||||||
|
)
|
||||||
|
_satData.emit(satData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getOrbitalVelocity(altitude: Double): Double {
|
||||||
|
val earthG = 6.674 * 10.0.pow(-11)
|
||||||
|
val earthM = 5.98 * 10.0.pow(24)
|
||||||
|
val radius = 6.37 * 10.0.pow(6) + altitude * 10.0.pow(3)
|
||||||
|
return sqrt(earthG * earthM / radius) / 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clipLat(pLatitude: Double): Double {
|
||||||
|
return clip(pLatitude, -85.05, 85.05)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clipLon(pLongitude: Double): Double {
|
||||||
|
var result = pLongitude
|
||||||
|
while (result < -180.0) result += 360.0
|
||||||
|
while (result > 180.0) result -= 360.0
|
||||||
|
return clip(result, -180.0, 180.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clip(currentValue: Double, minValue: Double, maxValue: Double): Double {
|
||||||
|
return min(max(currentValue, minValue), maxValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPositions(
|
||||||
|
satellite: Satellite,
|
||||||
|
stationPos: StationPos,
|
||||||
|
refDate: Date,
|
||||||
|
stepSec: Int,
|
||||||
|
minBefore: Int,
|
||||||
|
orbits: Double
|
||||||
|
): List<SatPos> {
|
||||||
|
val positions = mutableListOf<SatPos>()
|
||||||
|
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
|
||||||
|
while (currentDate.before(endDate)) {
|
||||||
|
val satPos = satellite.getPosition(stationPos, currentDate.time)
|
||||||
|
positions.add(satPos)
|
||||||
|
currentDate = Date(currentDate.time + stepSec * 1000)
|
||||||
|
}
|
||||||
|
return positions
|
||||||
|
}
|
||||||
|
|
||||||
|
// List view - 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>,
|
||||||
|
stationPos: StationPos,
|
||||||
|
refDate: Date = Date(),
|
||||||
|
hoursAhead: Int = 8
|
||||||
|
) {
|
||||||
|
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,
|
||||||
|
stationPos,
|
||||||
|
refDate,
|
||||||
|
hoursAhead
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun forceCalculation(
|
||||||
|
satellites: List<Satellite>,
|
||||||
|
stationPos: StationPos,
|
||||||
|
refDate: Date = Date(),
|
||||||
|
hoursAhead: Int = 8
|
||||||
|
) {
|
||||||
|
if (satellites.isEmpty()) {
|
||||||
|
_passes.emit(emptyList())
|
||||||
|
} else {
|
||||||
|
withContext(defaultDispatcher) {
|
||||||
|
val allPasses = mutableListOf<SatPass>()
|
||||||
|
selectedSatellites = satellites
|
||||||
|
satellites.forEach { satellite ->
|
||||||
|
val passes = getPasses(satellite, stationPos, refDate, hoursAhead)
|
||||||
|
allPasses.addAll(passes)
|
||||||
|
}
|
||||||
|
_passes.emit(filterPasses(allPasses, refDate))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPasses(
|
||||||
|
satellite: Satellite,
|
||||||
|
stationPos: StationPos,
|
||||||
|
date: Date,
|
||||||
|
hoursAhead: Int
|
||||||
|
): List<SatPass> {
|
||||||
|
val passes = mutableListOf<SatPass>()
|
||||||
|
val endDate = Date(date.time + hoursAhead * 60L * 60L * 1000L)
|
||||||
|
val quarterOrbitMin = satellite.getQuarterOrbitMin()
|
||||||
|
var startDate = date
|
||||||
|
var shouldWindBack = true
|
||||||
|
var lastAosDate: Date
|
||||||
|
var count = 0
|
||||||
|
if (satellite.willBeSeen(stationPos)) {
|
||||||
|
if (satellite.params.isDeepspace) {
|
||||||
|
passes.add(nextDeepSpacePass(satellite, stationPos, date))
|
||||||
|
} else {
|
||||||
|
do {
|
||||||
|
if (count > 0) shouldWindBack = false
|
||||||
|
val pass = nextNearEarthPass(satellite, stationPos, startDate, shouldWindBack)
|
||||||
|
lastAosDate = Date(pass.aosTime)
|
||||||
|
passes.add(pass)
|
||||||
|
startDate = Date(pass.losTime + (quarterOrbitMin * 3) * 60L * 1000L)
|
||||||
|
count++
|
||||||
|
} while (lastAosDate < endDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return passes
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun filterPasses(
|
||||||
|
passes: List<SatPass>,
|
||||||
|
refDate: Date,
|
||||||
|
hoursAhead: Int = 8,
|
||||||
|
minElevation: Double = 16.0
|
||||||
|
): List<SatPass> {
|
||||||
|
val timeFuture = refDate.time + (hoursAhead * 3600 * 1000)
|
||||||
|
return passes.filter { it.losTime > refDate.time }
|
||||||
|
.filter { it.aosTime < timeFuture }
|
||||||
|
.filter { it.maxElevation > minElevation }
|
||||||
|
.sortedBy { it.aosTime }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun nextDeepSpacePass(
|
||||||
|
satellite: Satellite,
|
||||||
|
stationPos: StationPos,
|
||||||
|
refDate: Date
|
||||||
|
): SatPass {
|
||||||
|
val satPos = satellite.getPosition(stationPos, refDate.time)
|
||||||
|
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(aos, az, los, az, tca, az, alt, elev, satellite)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun nextNearEarthPass(
|
||||||
|
satellite: Satellite,
|
||||||
|
stationPos: StationPos,
|
||||||
|
refDate: Date,
|
||||||
|
windBack: Boolean = false
|
||||||
|
): SatPass {
|
||||||
|
val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply {
|
||||||
|
clear()
|
||||||
|
timeInMillis = refDate.time
|
||||||
|
}
|
||||||
|
val quarterOrbitMin = satellite.getQuarterOrbitMin()
|
||||||
|
|
||||||
|
var elevation: Double
|
||||||
|
var maxElevation = 0.0
|
||||||
|
var alt = 0.0
|
||||||
|
var tcaAz = 0.0
|
||||||
|
|
||||||
|
// wind back time 1/4 of an orbit
|
||||||
|
if (windBack) calendar.add(Calendar.MINUTE, -quarterOrbitMin)
|
||||||
|
var satPos = satellite.getPosition(stationPos, calendar.time.time)
|
||||||
|
|
||||||
|
if (satPos.elevation > 0.0) {
|
||||||
|
// move forward in 30 second intervals until the sat goes below the horizon
|
||||||
|
do {
|
||||||
|
calendar.add(Calendar.SECOND, 30)
|
||||||
|
satPos = satellite.getPosition(stationPos, calendar.time.time)
|
||||||
|
} while (satPos.elevation > 0.0)
|
||||||
|
// move forward 3/4 of an orbit
|
||||||
|
calendar.add(Calendar.MINUTE, quarterOrbitMin * 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the next time sat comes above the horizon
|
||||||
|
do {
|
||||||
|
calendar.add(Calendar.SECOND, 60)
|
||||||
|
satPos = satellite.getPosition(stationPos, calendar.time.time)
|
||||||
|
elevation = satPos.elevation
|
||||||
|
if (elevation > maxElevation) {
|
||||||
|
maxElevation = elevation
|
||||||
|
alt = satPos.altitude
|
||||||
|
tcaAz = Math.toDegrees(satPos.azimuth)
|
||||||
|
}
|
||||||
|
} while (satPos.elevation < 0.0)
|
||||||
|
|
||||||
|
// refine to 3 seconds
|
||||||
|
calendar.add(Calendar.SECOND, -60)
|
||||||
|
do {
|
||||||
|
calendar.add(Calendar.SECOND, 3)
|
||||||
|
satPos = satellite.getPosition(stationPos, calendar.time.time)
|
||||||
|
elevation = satPos.elevation
|
||||||
|
if (elevation > maxElevation) {
|
||||||
|
maxElevation = elevation
|
||||||
|
alt = satPos.altitude
|
||||||
|
tcaAz = Math.toDegrees(satPos.azimuth)
|
||||||
|
}
|
||||||
|
} while (satPos.elevation < 0.0)
|
||||||
|
|
||||||
|
val aos = satPos.time
|
||||||
|
val aosAz = Math.toDegrees(satPos.azimuth)
|
||||||
|
|
||||||
|
// find when sat goes below
|
||||||
|
do {
|
||||||
|
calendar.add(Calendar.SECOND, 30)
|
||||||
|
satPos = satellite.getPosition(stationPos, calendar.time.time)
|
||||||
|
elevation = satPos.elevation
|
||||||
|
if (elevation > maxElevation) {
|
||||||
|
maxElevation = elevation
|
||||||
|
alt = satPos.altitude
|
||||||
|
tcaAz = Math.toDegrees(satPos.azimuth)
|
||||||
|
}
|
||||||
|
} while (satPos.elevation > 0.0)
|
||||||
|
|
||||||
|
// refine to 3 seconds
|
||||||
|
calendar.add(Calendar.SECOND, -30)
|
||||||
|
do {
|
||||||
|
calendar.add(Calendar.SECOND, 3)
|
||||||
|
satPos = satellite.getPosition(stationPos, calendar.time.time)
|
||||||
|
elevation = satPos.elevation
|
||||||
|
if (elevation > maxElevation) {
|
||||||
|
maxElevation = elevation
|
||||||
|
alt = satPos.altitude
|
||||||
|
tcaAz = Math.toDegrees(satPos.azimuth)
|
||||||
|
}
|
||||||
|
} while (satPos.elevation > 0.0)
|
||||||
|
|
||||||
|
val los = satPos.time
|
||||||
|
val losAz = Math.toDegrees(satPos.azimuth)
|
||||||
|
val tca = Date((aos + los) / 2).time
|
||||||
|
val elev = Math.toDegrees(maxElevation)
|
||||||
|
return SatPass(aos, aosAz, los, losAz, tca, tcaAz, alt, elev, satellite)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.data
|
package com.rtbishop.look4sat.data
|
||||||
|
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.StationPos
|
import com.rtbishop.look4sat.domain.StationPos
|
||||||
|
|
||||||
interface PreferencesSource {
|
interface PreferencesSource {
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -17,10 +17,10 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.data
|
package com.rtbishop.look4sat.data
|
||||||
|
|
||||||
import com.rtbishop.look4sat.domain.model.SatTrans
|
import com.rtbishop.look4sat.domain.SatTrans
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
interface SatDataRemoteSource {
|
interface RemoteDataSource {
|
||||||
|
|
||||||
suspend fun fetchDataStream(url: String): InputStream?
|
suspend fun fetchDataStream(url: String): InputStream?
|
||||||
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.data
|
|
||||||
|
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.SatPass
|
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
|
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class SatPassRepository(
|
|
||||||
private val preferencesSource: PreferencesSource,
|
|
||||||
private val defaultDispatcher: CoroutineDispatcher
|
|
||||||
) {
|
|
||||||
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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
+17
-17
@@ -17,11 +17,11 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.data
|
package com.rtbishop.look4sat.data
|
||||||
|
|
||||||
import com.rtbishop.look4sat.domain.model.SatEntry
|
import com.rtbishop.look4sat.domain.SatEntry
|
||||||
import com.rtbishop.look4sat.domain.model.SatItem
|
import com.rtbishop.look4sat.domain.SatItem
|
||||||
import com.rtbishop.look4sat.domain.model.SatTrans
|
import com.rtbishop.look4sat.domain.SatTrans
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
|
import com.rtbishop.look4sat.domain.Satellite
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.TLE
|
import com.rtbishop.look4sat.domain.TLE
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -30,10 +30,10 @@ import kotlinx.coroutines.withContext
|
|||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
|
|
||||||
class SatDataRepository(
|
class SatelliteRepo(
|
||||||
private val preferencesSource: PreferencesSource,
|
private val preferencesSource: PreferencesSource,
|
||||||
private val satLocalSource: SatDataLocalSource,
|
private val localDataSource: LocalDataSource,
|
||||||
private val satRemoteSource: SatDataRemoteSource,
|
private val remoteDataSource: RemoteDataSource,
|
||||||
private val ioDispatcher: CoroutineDispatcher
|
private val ioDispatcher: CoroutineDispatcher
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -46,19 +46,19 @@ class SatDataRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getSatItems(): Flow<List<SatItem>> {
|
fun getSatItems(): Flow<List<SatItem>> {
|
||||||
return satLocalSource.getSatItems()
|
return localDataSource.getSatItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSatTransmitters(catNum: Int): Flow<List<SatTrans>> {
|
fun getSatTransmitters(catNum: Int): Flow<List<SatTrans>> {
|
||||||
return satLocalSource.getSatTransmitters(catNum)
|
return localDataSource.getSatTransmitters(catNum)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getSelectedSatellites(): List<Satellite> {
|
suspend fun getSelectedSatellites(): List<Satellite> {
|
||||||
return satLocalSource.getSelectedSatellites()
|
return localDataSource.getSelectedSatellites()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateEntriesFromFile(stream: InputStream) = withContext(ioDispatcher) {
|
suspend fun updateEntriesFromFile(stream: InputStream) = withContext(ioDispatcher) {
|
||||||
satLocalSource.updateEntries(importSatEntries(stream))
|
localDataSource.updateEntries(importSatEntries(stream))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateEntriesFromWeb(sources: List<String>) {
|
suspend fun updateEntriesFromWeb(sources: List<String>) {
|
||||||
@@ -67,7 +67,7 @@ class SatDataRepository(
|
|||||||
val streams = mutableListOf<InputStream>()
|
val streams = mutableListOf<InputStream>()
|
||||||
val entries = mutableListOf<SatEntry>()
|
val entries = mutableListOf<SatEntry>()
|
||||||
sources.forEach { source ->
|
sources.forEach { source ->
|
||||||
satRemoteSource.fetchDataStream(source)?.let { stream ->
|
remoteDataSource.fetchDataStream(source)?.let { stream ->
|
||||||
if (source.contains(".zip", true)) {
|
if (source.contains(".zip", true)) {
|
||||||
val zipStream = ZipInputStream(stream).apply { nextEntry }
|
val zipStream = ZipInputStream(stream).apply { nextEntry }
|
||||||
streams.add(zipStream)
|
streams.add(zipStream)
|
||||||
@@ -77,17 +77,17 @@ class SatDataRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
streams.forEach { stream -> entries.addAll(importSatEntries(stream)) }
|
streams.forEach { stream -> entries.addAll(importSatEntries(stream)) }
|
||||||
satLocalSource.updateEntries(entries)
|
localDataSource.updateEntries(entries)
|
||||||
}
|
}
|
||||||
launch(ioDispatcher) {
|
launch(ioDispatcher) {
|
||||||
val transmitters = satRemoteSource.fetchTransmitters().filter { it.isAlive }
|
val transmitters = remoteDataSource.fetchTransmitters().filter { it.isAlive }
|
||||||
satLocalSource.updateTransmitters(transmitters)
|
localDataSource.updateTransmitters(transmitters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
|
suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
|
||||||
satLocalSource.updateEntriesSelection(catNums, isSelected)
|
localDataSource.updateEntriesSelection(catNums, isSelected)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun importSatEntries(stream: InputStream): List<SatEntry> {
|
private fun importSatEntries(stream: InputStream): List<SatEntry> {
|
||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.domain.predict4kotlin
|
package com.rtbishop.look4sat.domain
|
||||||
|
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
+1
-1
@@ -15,6 +15,6 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.domain.predict4kotlin
|
package com.rtbishop.look4sat.domain
|
||||||
|
|
||||||
data class GeoPos(val latitude: Double, val longitude: Double)
|
data class GeoPos(val latitude: Double, val longitude: Double)
|
||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.domain.predict4kotlin
|
package com.rtbishop.look4sat.domain
|
||||||
|
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
+2
-2
@@ -15,11 +15,11 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.domain.predict4kotlin
|
package com.rtbishop.look4sat.domain
|
||||||
|
|
||||||
import kotlin.math.round
|
import kotlin.math.round
|
||||||
|
|
||||||
class QthConverter {
|
object QthConverter {
|
||||||
|
|
||||||
fun qthToPosition(qthString: String): GeoPos? {
|
fun qthToPosition(qthString: String): GeoPos? {
|
||||||
val trimmedQth = qthString.take(6)
|
val trimmedQth = qthString.take(6)
|
||||||
+1
-4
@@ -15,10 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.framework.model
|
package com.rtbishop.look4sat.domain
|
||||||
|
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.GeoPos
|
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
|
|
||||||
|
|
||||||
data class SatData(
|
data class SatData(
|
||||||
val pass: Satellite,
|
val pass: Satellite,
|
||||||
+1
-3
@@ -15,9 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.domain.model
|
package com.rtbishop.look4sat.domain
|
||||||
|
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.TLE
|
|
||||||
|
|
||||||
data class SatEntry(
|
data class SatEntry(
|
||||||
val tle: TLE,
|
val tle: TLE,
|
||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.domain.model
|
package com.rtbishop.look4sat.domain
|
||||||
|
|
||||||
data class SatItem(
|
data class SatItem(
|
||||||
val catNum: Int,
|
val catNum: Int,
|
||||||
+2
-3
@@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.domain.predict4kotlin
|
package com.rtbishop.look4sat.domain
|
||||||
|
|
||||||
data class SatPass(
|
data class SatPass(
|
||||||
val aosTime: Long,
|
val aosTime: Long,
|
||||||
@@ -26,8 +26,7 @@ data class SatPass(
|
|||||||
val tcaAzimuth: Double,
|
val tcaAzimuth: Double,
|
||||||
val altitude: Double,
|
val altitude: Double,
|
||||||
val maxElevation: Double,
|
val maxElevation: Double,
|
||||||
val satellite: Satellite,
|
val satellite: Satellite
|
||||||
val predictor: PassPredictor,
|
|
||||||
) {
|
) {
|
||||||
val catNum: Int = satellite.params.catnum
|
val catNum: Int = satellite.params.catnum
|
||||||
val name: String = satellite.params.name
|
val name: String = satellite.params.name
|
||||||
+2
-3
@@ -15,9 +15,8 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.domain.predict4kotlin
|
package com.rtbishop.look4sat.domain
|
||||||
|
|
||||||
import java.util.*
|
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
data class SatPos(
|
data class SatPos(
|
||||||
@@ -29,7 +28,7 @@ data class SatPos(
|
|||||||
var range: Double = 0.0,
|
var range: Double = 0.0,
|
||||||
var rangeRate: Double = 0.0,
|
var rangeRate: Double = 0.0,
|
||||||
var theta: Double = 0.0,
|
var theta: Double = 0.0,
|
||||||
var time: Date = Date()
|
var time: Long = 0L
|
||||||
) {
|
) {
|
||||||
private val earthRadiusKm = 6378.16
|
private val earthRadiusKm = 6378.16
|
||||||
private val speedOfLight = 2.99792458E8
|
private val speedOfLight = 2.99792458E8
|
||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.domain.model
|
package com.rtbishop.look4sat.domain
|
||||||
|
|
||||||
data class SatTrans(
|
data class SatTrans(
|
||||||
val uuid: String,
|
val uuid: String,
|
||||||
+7
-8
@@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.domain.predict4kotlin
|
package com.rtbishop.look4sat.domain
|
||||||
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
@@ -41,6 +41,10 @@ abstract class Satellite(val params: TLE) {
|
|||||||
var s4 = 0.0
|
var s4 = 0.0
|
||||||
val xke = 7.43669161E-2
|
val xke = 7.43669161E-2
|
||||||
|
|
||||||
|
fun getQuarterOrbitMin(): Int {
|
||||||
|
return (24.0 * 60.0 / params.meanmo / 4.0).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
fun willBeSeen(pos: StationPos): Boolean {
|
fun willBeSeen(pos: StationPos): Boolean {
|
||||||
return if (params.meanmo < 1e-8) false else {
|
return if (params.meanmo < 1e-8) false else {
|
||||||
var lin = params.incl
|
var lin = params.incl
|
||||||
@@ -51,11 +55,7 @@ abstract class Satellite(val params: TLE) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPredictor(pos: StationPos): PassPredictor {
|
fun getPosition(pos: StationPos, time: Long): SatPos {
|
||||||
return PassPredictor(this, pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getPosition(pos: StationPos, time: Date): SatPos {
|
|
||||||
val satPos = SatPos()
|
val satPos = SatPos()
|
||||||
// Date/time at which the position and velocity were calculated
|
// Date/time at which the position and velocity were calculated
|
||||||
val julUTC = calcCurrentDaynum(time) + 2444238.5
|
val julUTC = calcCurrentDaynum(time) + 2444238.5
|
||||||
@@ -76,8 +76,7 @@ abstract class Satellite(val params: TLE) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read the system clock and return the number of days since 31Dec79 00:00:00 UTC (daynum 0)
|
// Read the system clock and return the number of days since 31Dec79 00:00:00 UTC (daynum 0)
|
||||||
private fun calcCurrentDaynum(date: Date): Double {
|
private fun calcCurrentDaynum(now: Long): Double {
|
||||||
val now = date.time
|
|
||||||
val sgp4Epoch = Calendar.getInstance(TimeZone.getTimeZone("UTC:UTC"))
|
val sgp4Epoch = Calendar.getInstance(TimeZone.getTimeZone("UTC:UTC"))
|
||||||
sgp4Epoch.clear()
|
sgp4Epoch.clear()
|
||||||
sgp4Epoch[1979, 11, 31, 0, 0] = 0
|
sgp4Epoch[1979, 11, 31, 0, 0] = 0
|
||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.domain.predict4kotlin
|
package com.rtbishop.look4sat.domain
|
||||||
|
|
||||||
data class StationPos(
|
data class StationPos(
|
||||||
val latitude: Double,
|
val latitude: Double,
|
||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.domain.predict4kotlin
|
package com.rtbishop.look4sat.domain
|
||||||
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class PassPredictor(private val satellite: Satellite, private val stationPos: StationPos) {
|
|
||||||
|
|
||||||
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 {
|
|
||||||
val rangeRate = getSatPos(date).rangeRate
|
|
||||||
return (freq.toDouble() * (speedOfLight - rangeRate * 1000.0) / speedOfLight).toLong()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getUplinkFreq(freq: Long, date: Date): Long {
|
|
||||||
val rangeRate = getSatPos(date).rangeRate
|
|
||||||
return (freq.toDouble() * (speedOfLight + rangeRate * 1000.0) / speedOfLight).toLong()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSatPos(date: Date): SatPos {
|
|
||||||
return satellite.getPosition(stationPos, date)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getPositions(refDate: Date, stepSec: Int, minBefore: Int, orbits: Double): List<SatPos> {
|
|
||||||
val positions = mutableListOf<SatPos>()
|
|
||||||
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
|
|
||||||
while (currentDate.before(endDate)) {
|
|
||||||
positions.add(getSatPos(currentDate))
|
|
||||||
currentDate = Date(currentDate.time + stepSec * 1000)
|
|
||||||
}
|
|
||||||
return positions
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getPasses(refDate: Date, hoursAhead: Int, windBack: Boolean): List<SatPass> {
|
|
||||||
val passes = mutableListOf<SatPass>()
|
|
||||||
val endDate = Date(refDate.time + hoursAhead * 60L * 60L * 1000L)
|
|
||||||
var startDate = refDate
|
|
||||||
var shouldWindBack = windBack
|
|
||||||
var lastAosDate: Date
|
|
||||||
var count = 0
|
|
||||||
if (satellite.willBeSeen(stationPos)) {
|
|
||||||
if (satellite.params.isDeepspace) {
|
|
||||||
passes.add(nextDeepSpacePass(refDate))
|
|
||||||
} else {
|
|
||||||
do {
|
|
||||||
if (count > 0) shouldWindBack = false
|
|
||||||
val pass = nextNearEarthPass(startDate, shouldWindBack)
|
|
||||||
lastAosDate = Date(pass.aosTime)
|
|
||||||
passes.add(pass)
|
|
||||||
startDate = Date(pass.losTime + (oneQuarterOrbitMin * 3) * 60L * 1000L)
|
|
||||||
count++
|
|
||||||
} while (lastAosDate < endDate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return passes
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun nextDeepSpacePass(refDate: Date): SatPass {
|
|
||||||
val satPos = getSatPos(refDate)
|
|
||||||
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(aos, az, los, az, tca, az, alt, elev, satellite, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun nextNearEarthPass(refDate: Date, windBack: Boolean = false): SatPass {
|
|
||||||
val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply {
|
|
||||||
clear()
|
|
||||||
timeInMillis = refDate.time
|
|
||||||
}
|
|
||||||
|
|
||||||
var elevation: Double
|
|
||||||
var maxElevation = 0.0
|
|
||||||
var alt = 0.0
|
|
||||||
var tcaAz = 0.0
|
|
||||||
|
|
||||||
// wind back time 1/4 of an orbit
|
|
||||||
if (windBack) calendar.add(Calendar.MINUTE, -oneQuarterOrbitMin)
|
|
||||||
var satPos = getSatPos(calendar.time)
|
|
||||||
|
|
||||||
if (satPos.elevation > 0.0) {
|
|
||||||
// move forward in 30 second intervals until the sat goes below the horizon
|
|
||||||
do {
|
|
||||||
calendar.add(Calendar.SECOND, 30)
|
|
||||||
satPos = getSatPos(calendar.time)
|
|
||||||
} while (satPos.elevation > 0.0)
|
|
||||||
// move forward 3/4 of an orbit
|
|
||||||
calendar.add(Calendar.MINUTE, oneQuarterOrbitMin * 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
// find the next time sat comes above the horizon
|
|
||||||
do {
|
|
||||||
calendar.add(Calendar.SECOND, 60)
|
|
||||||
satPos = getSatPos(calendar.time)
|
|
||||||
elevation = satPos.elevation
|
|
||||||
if (elevation > maxElevation) {
|
|
||||||
maxElevation = elevation
|
|
||||||
alt = satPos.altitude
|
|
||||||
tcaAz = Math.toDegrees(satPos.azimuth)
|
|
||||||
}
|
|
||||||
} while (satPos.elevation < 0.0)
|
|
||||||
|
|
||||||
// refine to 3 seconds
|
|
||||||
calendar.add(Calendar.SECOND, -60)
|
|
||||||
do {
|
|
||||||
calendar.add(Calendar.SECOND, 3)
|
|
||||||
satPos = getSatPos(calendar.time)
|
|
||||||
elevation = satPos.elevation
|
|
||||||
if (elevation > maxElevation) {
|
|
||||||
maxElevation = elevation
|
|
||||||
alt = satPos.altitude
|
|
||||||
tcaAz = Math.toDegrees(satPos.azimuth)
|
|
||||||
}
|
|
||||||
} while (satPos.elevation < 0.0)
|
|
||||||
|
|
||||||
val aos = satPos.time.time
|
|
||||||
val aosAz = Math.toDegrees(satPos.azimuth)
|
|
||||||
|
|
||||||
// find when sat goes below
|
|
||||||
do {
|
|
||||||
calendar.add(Calendar.SECOND, 30)
|
|
||||||
satPos = getSatPos(calendar.time)
|
|
||||||
elevation = satPos.elevation
|
|
||||||
if (elevation > maxElevation) {
|
|
||||||
maxElevation = elevation
|
|
||||||
alt = satPos.altitude
|
|
||||||
tcaAz = Math.toDegrees(satPos.azimuth)
|
|
||||||
}
|
|
||||||
} while (satPos.elevation > 0.0)
|
|
||||||
|
|
||||||
// refine to 3 seconds
|
|
||||||
calendar.add(Calendar.SECOND, -30)
|
|
||||||
do {
|
|
||||||
calendar.add(Calendar.SECOND, 3)
|
|
||||||
satPos = getSatPos(calendar.time)
|
|
||||||
elevation = satPos.elevation
|
|
||||||
if (elevation > maxElevation) {
|
|
||||||
maxElevation = elevation
|
|
||||||
alt = satPos.altitude
|
|
||||||
tcaAz = Math.toDegrees(satPos.azimuth)
|
|
||||||
}
|
|
||||||
} while (satPos.elevation > 0.0)
|
|
||||||
|
|
||||||
val los = satPos.time.time
|
|
||||||
val losAz = Math.toDegrees(satPos.azimuth)
|
|
||||||
val tca = Date((aos + los) / 2).time
|
|
||||||
val elev = Math.toDegrees(maxElevation)
|
|
||||||
return SatPass(aos, aosAz, los, losAz, tca, tcaAz, alt, elev, satellite, this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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,6 +1,6 @@
|
|||||||
package com.rtbishop.look4sat
|
package com.rtbishop.look4sat
|
||||||
|
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.TLE
|
import com.rtbishop.look4sat.domain.TLE
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class Look4SatTest {
|
class Look4SatTest {
|
||||||
|
|||||||
@@ -17,44 +17,42 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat
|
package com.rtbishop.look4sat
|
||||||
|
|
||||||
import com.rtbishop.look4sat.domain.predict4kotlin.QthConverter
|
import com.rtbishop.look4sat.domain.QthConverter
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class QthConverterTest {
|
class QthConverterTest {
|
||||||
|
|
||||||
private val qthConverter = QthConverter()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Given valid QTH returns correct POS`() {
|
fun `Given valid QTH returns correct POS`() {
|
||||||
var result = qthConverter.qthToPosition("io91VL39FX")
|
var result = QthConverter.qthToPosition("io91VL39FX")
|
||||||
assert(result?.latitude == 51.4792 && result.longitude == -0.2083)
|
assert(result?.latitude == 51.4792 && result.longitude == -0.2083)
|
||||||
result = qthConverter.qthToPosition("JN58TD")
|
result = QthConverter.qthToPosition("JN58TD")
|
||||||
assert(result?.latitude == 48.1458 && result.longitude == 11.6250)
|
assert(result?.latitude == 48.1458 && result.longitude == 11.6250)
|
||||||
result = qthConverter.qthToPosition("gf15vc")
|
result = QthConverter.qthToPosition("gf15vc")
|
||||||
assert(result?.latitude == -34.8958 && result.longitude == -56.2083)
|
assert(result?.latitude == -34.8958 && result.longitude == -56.2083)
|
||||||
result = qthConverter.qthToPosition("fm18LW")
|
result = QthConverter.qthToPosition("fm18LW")
|
||||||
assert(result?.latitude == 38.9375 && result.longitude == -77.0417)
|
assert(result?.latitude == 38.9375 && result.longitude == -77.0417)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Given invalid QTH returns null`() {
|
fun `Given invalid QTH returns null`() {
|
||||||
var result = qthConverter.qthToPosition("ZZ00zz")
|
var result = QthConverter.qthToPosition("ZZ00zz")
|
||||||
assert(result == null)
|
assert(result == null)
|
||||||
result = qthConverter.qthToPosition("JN58tz")
|
result = QthConverter.qthToPosition("JN58tz")
|
||||||
assert(result == null)
|
assert(result == null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Given valid POS returns correct QTH`() {
|
fun `Given valid POS returns correct QTH`() {
|
||||||
assert(qthConverter.positionToQTH(51.4878, -0.2146) == "IO91vl")
|
assert(QthConverter.positionToQTH(51.4878, -0.2146) == "IO91vl")
|
||||||
assert(qthConverter.positionToQTH(48.1466, 11.6083) == "JN58td")
|
assert(QthConverter.positionToQTH(48.1466, 11.6083) == "JN58td")
|
||||||
assert(qthConverter.positionToQTH(-34.91, -56.2116) == "GF15vc")
|
assert(QthConverter.positionToQTH(-34.91, -56.2116) == "GF15vc")
|
||||||
assert(qthConverter.positionToQTH(38.92, -77.065) == "FM18lw")
|
assert(QthConverter.positionToQTH(38.92, -77.065) == "FM18lw")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Given invalid POS returns null`() {
|
fun `Given invalid POS returns null`() {
|
||||||
assert(qthConverter.positionToQTH(91.0542, -170.1142) == null)
|
assert(QthConverter.positionToQTH(91.0542, -170.1142) == null)
|
||||||
assert(qthConverter.positionToQTH(89.0542, -240.1142) == null)
|
assert(QthConverter.positionToQTH(89.0542, -240.1142) == null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user