Renamed and moved several classes

This commit is contained in:
Arty Bishop committed 2022-03-06 18:12:11 +00:00
1 parent 5804f42211
commit 4ee74a6789
59 files changed
+563 -449

No files matched your search

+1 -1
View File
@@ -55,7 +55,7 @@ android {
} }
dependencies { dependencies {
implementation project(":data") implementation project(":base")
implementation "androidx.core:core-splashscreen:1.0.0-beta01" implementation "androidx.core:core-splashscreen:1.0.0-beta01"
implementation "androidx.constraintlayout:constraintlayout:2.1.3" implementation "androidx.constraintlayout:constraintlayout:2.1.3"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
@@ -26,12 +26,12 @@ import android.location.LocationManager
import android.os.Bundle import android.os.Bundle
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.ILocationHandler import com.rtbishop.look4sat.domain.ILocationManager
import com.rtbishop.look4sat.domain.ISettings import com.rtbishop.look4sat.domain.ISettingsManager
import com.rtbishop.look4sat.domain.QthConverter
import com.rtbishop.look4sat.domain.model.DataState import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.domain.round import com.rtbishop.look4sat.utility.QthConverter
import com.rtbishop.look4sat.utility.round
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@@ -39,12 +39,12 @@ import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class LocationHandler @Inject constructor( class LocationManager @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
private val settings: ISettings, private val manager: LocationManager,
) : LocationListener, ILocationHandler { private val settings: ISettingsManager,
) : LocationListener, ILocationManager {
private val manager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private val providerDef = LocationManager.PASSIVE_PROVIDER private val providerDef = LocationManager.PASSIVE_PROVIDER
private val providerNet = LocationManager.NETWORK_PROVIDER private val providerNet = LocationManager.NETWORK_PROVIDER
private val providerGps = LocationManager.GPS_PROVIDER private val providerGps = LocationManager.GPS_PROVIDER
@@ -21,12 +21,13 @@ import android.hardware.Sensor
import android.hardware.SensorEvent import android.hardware.SensorEvent
import android.hardware.SensorEventListener import android.hardware.SensorEventListener
import android.hardware.SensorManager import android.hardware.SensorManager
import com.rtbishop.look4sat.domain.predict.RAD2DEG
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.math.round import kotlin.math.round
@Singleton @Singleton
class OrientationHandler @Inject constructor(private val sensorManager: SensorManager) : class OrientationManager @Inject constructor(private val sensorManager: SensorManager) :
SensorEventListener { SensorEventListener {
interface OrientationListener { interface OrientationListener {
@@ -36,7 +37,6 @@ class OrientationHandler @Inject constructor(private val sensorManager: SensorMa
private val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR) private val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
private val rotationMatrix = FloatArray(9) private val rotationMatrix = FloatArray(9)
private val orientationValues = FloatArray(3) private val orientationValues = FloatArray(3)
private val oneRadDegrees = 57.295779513f
private var sensorAccuracy: Int = SensorManager.SENSOR_STATUS_UNRELIABLE private var sensorAccuracy: Int = SensorManager.SENSOR_STATUS_UNRELIABLE
private var orientationListener: OrientationListener? = null private var orientationListener: OrientationListener? = null
@@ -67,9 +67,9 @@ class OrientationHandler @Inject constructor(private val sensorManager: SensorMa
private fun updateOrientation(rotationVector: FloatArray) { private fun updateOrientation(rotationVector: FloatArray) {
SensorManager.getRotationMatrixFromVector(rotationMatrix, rotationVector) SensorManager.getRotationMatrixFromVector(rotationMatrix, rotationVector)
SensorManager.getOrientation(rotationMatrix, orientationValues) SensorManager.getOrientation(rotationMatrix, orientationValues)
val azimuth = orientationValues[0] * oneRadDegrees val azimuth = (orientationValues[0] * RAD2DEG).toFloat()
val pitch = orientationValues[1] * oneRadDegrees val pitch = (orientationValues[1] * RAD2DEG).toFloat()
val roll = orientationValues[2] * oneRadDegrees val roll = (orientationValues[2] * RAD2DEG).toFloat()
val magneticAzimuth = (azimuth + 360f) % 360f val magneticAzimuth = (azimuth + 360f) % 360f
val roundedAzimuth = round(magneticAzimuth * 10) / 10 val roundedAzimuth = round(magneticAzimuth * 10) / 10
val roundedPitch = round(pitch * 10) / 10 val roundedPitch = round(pitch * 10) / 10
@@ -19,13 +19,13 @@ package com.rtbishop.look4sat.framework
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.core.content.edit import androidx.core.content.edit
import com.rtbishop.look4sat.domain.ISettings import com.rtbishop.look4sat.domain.ISettingsManager
import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.domain.predict.GeoPos
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class SettingsHandler @Inject constructor(private val prefs: SharedPreferences) : ISettings { class SettingsManager @Inject constructor(private val prefs: SharedPreferences) : ISettingsManager {
companion object { companion object {
const val keyDataSources = "dataSources" const val keyDataSources = "dataSources"
@@ -19,24 +19,18 @@ package com.rtbishop.look4sat.framework.data
import android.content.ContentResolver import android.content.ContentResolver
import android.net.Uri import android.net.Uri
import com.rtbishop.look4sat.data.IProvider import com.rtbishop.look4sat.domain.data.IFileDataSource
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.InputStream import java.io.InputStream
import java.net.URL
class Provider( class FileDataSource(
private val contentResolver: ContentResolver, private val contentResolver: ContentResolver,
private val ioDispatcher: CoroutineDispatcher private val ioDispatcher: CoroutineDispatcher
) : IProvider { ) : IFileDataSource {
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
override suspend fun getLocalFileStream(uri: String): InputStream? { override suspend fun getDataStream(uri: String): InputStream? {
return withContext(ioDispatcher) { contentResolver.openInputStream(Uri.parse(uri)) } return withContext(ioDispatcher) { contentResolver.openInputStream(Uri.parse(uri)) }
} }
@Suppress("BlockingMethodInNonBlockingContext")
override suspend fun getRemoteFileStream(url: String): InputStream? {
return withContext(ioDispatcher) { URL(url).openStream() }
}
} }
@@ -19,11 +19,13 @@ package com.rtbishop.look4sat.framework.data
import androidx.room.Database import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import com.rtbishop.look4sat.framework.data.dao.EntriesDao
import com.rtbishop.look4sat.framework.data.dao.RadiosDao
import com.rtbishop.look4sat.framework.model.SatEntry import com.rtbishop.look4sat.framework.model.SatEntry
import com.rtbishop.look4sat.framework.model.SatRadio import com.rtbishop.look4sat.framework.model.SatRadio
@Database(entities = [SatEntry::class, SatRadio::class], version = 1, exportSchema = true) @Database(entities = [SatEntry::class, SatRadio::class], version = 1, exportSchema = true)
abstract class StorageDb : RoomDatabase() { abstract class LocalDatabase : RoomDatabase() {
abstract fun entriesDao(): EntriesDao abstract fun entriesDao(): EntriesDao
@@ -17,21 +17,18 @@
*/ */
package com.rtbishop.look4sat.framework.data package com.rtbishop.look4sat.framework.data
import com.rtbishop.look4sat.data.IStorage import com.rtbishop.look4sat.domain.data.ILocalEntrySource
import com.rtbishop.look4sat.domain.predict.Satellite import com.rtbishop.look4sat.domain.predict.Satellite
import com.rtbishop.look4sat.framework.data.dao.EntriesDao
import com.rtbishop.look4sat.domain.model.SatEntry as DomainEntry import com.rtbishop.look4sat.domain.model.SatEntry as DomainEntry
import com.rtbishop.look4sat.domain.model.SatItem as DomainItem import com.rtbishop.look4sat.domain.model.SatItem as DomainItem
import com.rtbishop.look4sat.domain.model.SatRadio as DomainRadio
import com.rtbishop.look4sat.framework.model.SatEntry as FrameworkEntry import com.rtbishop.look4sat.framework.model.SatEntry as FrameworkEntry
import com.rtbishop.look4sat.framework.model.SatItem as FrameworkItem import com.rtbishop.look4sat.framework.model.SatItem as FrameworkItem
import com.rtbishop.look4sat.framework.model.SatRadio as FrameworkRadio
class Storage(private val entriesDao: EntriesDao, private val radiosDao: RadiosDao) : IStorage { class LocalEntrySource(private val entriesDao: EntriesDao) : ILocalEntrySource {
override fun getEntriesTotal() = entriesDao.getEntriesTotal() override fun getEntriesTotal() = entriesDao.getEntriesTotal()
override fun getRadiosTotal() = radiosDao.getRadiosTotal()
override suspend fun getEntriesWithModes(): List<DomainItem> { override suspend fun getEntriesWithModes(): List<DomainItem> {
return entriesDao.getEntriesWithModes().toDomainItems() return entriesDao.getEntriesWithModes().toDomainItems()
} }
@@ -45,42 +42,17 @@ class Storage(private val entriesDao: EntriesDao, private val radiosDao: RadiosD
return selectedSatellites return selectedSatellites
} }
override suspend fun getRadiosWithId(id: Int): List<DomainRadio> {
return radiosDao.getRadiosWithId(id).toDomainRadios()
}
override suspend fun insertEntries(entries: List<DomainEntry>) { override suspend fun insertEntries(entries: List<DomainEntry>) {
entriesDao.insertEntries(entries.toFrameworkEntries()) entriesDao.insertEntries(entries.toFrameworkEntries())
} }
override suspend fun insertRadios(radios: List<DomainRadio>) { override suspend fun deleteEntries() = entriesDao.deleteEntries()
radiosDao.insertRadios(radios.toFrameworkRadios())
}
override suspend fun clearAllData() {
entriesDao.deleteEntries()
radiosDao.deleteRadios()
}
private fun DomainEntry.toFramework() = FrameworkEntry(this.data, this.comment) private fun DomainEntry.toFramework() = FrameworkEntry(this.data, this.comment)
private fun DomainRadio.toFramework() = FrameworkRadio(
this.uuid, this.info, this.isAlive, this.downlink, this.uplink,
this.mode, this.isInverted, this.catnum, this.comment
)
private fun FrameworkItem.toDomain() = DomainItem(this.catnum, this.name, this.modes, false) private fun FrameworkItem.toDomain() = DomainItem(this.catnum, this.name, this.modes, false)
private fun FrameworkRadio.toDomain() = DomainRadio(
this.uuid, this.info, this.isAlive, this.downlink, this.uplink,
this.mode, this.isInverted, this.catnum, this.comment
)
private fun List<DomainEntry>.toFrameworkEntries() = this.map { entry -> entry.toFramework() } private fun List<DomainEntry>.toFrameworkEntries() = this.map { entry -> entry.toFramework() }
private fun List<DomainRadio>.toFrameworkRadios() = this.map { radio -> radio.toFramework() }
private fun List<FrameworkItem>.toDomainItems() = this.map { item -> item.toDomain() } private fun List<FrameworkItem>.toDomainItems() = this.map { item -> item.toDomain() }
private fun List<FrameworkRadio>.toDomainRadios() = this.map { radio -> radio.toDomain() }
} }
@@ -0,0 +1,52 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2022 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.framework.data
import com.rtbishop.look4sat.domain.data.ILocalRadioSource
import com.rtbishop.look4sat.framework.data.dao.RadiosDao
import com.rtbishop.look4sat.domain.model.SatRadio as DomainRadio
import com.rtbishop.look4sat.framework.model.SatRadio as FrameworkRadio
class LocalRadioSource(private val radiosDao: RadiosDao) : ILocalRadioSource {
override fun getRadiosTotal() = radiosDao.getRadiosTotal()
override suspend fun getRadiosWithId(id: Int): List<DomainRadio> {
return radiosDao.getRadiosWithId(id).toDomainRadios()
}
override suspend fun insertRadios(radios: List<DomainRadio>) {
radiosDao.insertRadios(radios.toFrameworkRadios())
}
override suspend fun deleteRadios() = radiosDao.deleteRadios()
private fun DomainRadio.toFramework() = FrameworkRadio(
this.uuid, this.info, this.isAlive, this.downlink, this.uplink,
this.mode, this.isInverted, this.catnum, this.comment
)
private fun FrameworkRadio.toDomain() = DomainRadio(
this.uuid, this.info, this.isAlive, this.downlink, this.uplink,
this.mode, this.isInverted, this.catnum, this.comment
)
private fun List<DomainRadio>.toFrameworkRadios() = this.map { radio -> radio.toFramework() }
private fun List<FrameworkRadio>.toDomainRadios() = this.map { radio -> radio.toDomain() }
}
@@ -0,0 +1,32 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2022 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.framework.data
import com.rtbishop.look4sat.domain.data.IRemoteDataSource
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import java.io.InputStream
import java.net.URL
class RemoteDataSource(private val ioDispatcher: CoroutineDispatcher) : IRemoteDataSource {
@Suppress("BlockingMethodInNonBlockingContext")
override suspend fun getDataStream(url: String): InputStream? = withContext(ioDispatcher) {
URL(url).openStream()
}
}
@@ -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.framework.data package com.rtbishop.look4sat.framework.data.dao
import androidx.room.* import androidx.room.*
import com.rtbishop.look4sat.framework.model.SatEntry import com.rtbishop.look4sat.framework.model.SatEntry
@@ -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.framework.data package com.rtbishop.look4sat.framework.data.dao
import androidx.room.* import androidx.room.*
import com.rtbishop.look4sat.framework.model.SatRadio import com.rtbishop.look4sat.framework.model.SatRadio
@@ -17,7 +17,6 @@
*/ */
package com.rtbishop.look4sat.injection package com.rtbishop.look4sat.injection
import android.content.ContentResolver
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.hardware.SensorManager import android.hardware.SensorManager
@@ -27,21 +26,12 @@ import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import javax.inject.Qualifier
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
object AppModule { object AppModule {
@Provides
@Singleton
fun provideContentResolver(@ApplicationContext context: Context): ContentResolver {
return context.contentResolver
}
@Provides @Provides
@Singleton @Singleton
fun provideLocationManager(@ApplicationContext context: Context): LocationManager { fun provideLocationManager(@ApplicationContext context: Context): LocationManager {
@@ -59,20 +49,4 @@ object AppModule {
fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences { fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences {
return context.getSharedPreferences("default", Context.MODE_PRIVATE) return context.getSharedPreferences("default", Context.MODE_PRIVATE)
} }
@Provides
@DefaultDispatcher
fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
@Provides
@IoDispatcher
fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
} }
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class DefaultDispatcher
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class IoDispatcher
@@ -0,0 +1,76 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2022 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.injection
import android.content.Context
import androidx.room.Room
import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.domain.ILocationManager
import com.rtbishop.look4sat.domain.ISatelliteManager
import com.rtbishop.look4sat.domain.ISettingsManager
import com.rtbishop.look4sat.domain.data.DataRepository
import com.rtbishop.look4sat.domain.predict.SatelliteManager
import com.rtbishop.look4sat.framework.LocationManager
import com.rtbishop.look4sat.framework.SettingsManager
import com.rtbishop.look4sat.framework.data.*
import com.rtbishop.look4sat.utility.DataParser
import com.rtbishop.look4sat.utility.DataReporter
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object BaseModule {
@Provides
@Singleton
fun provideDataRepository(@ApplicationContext context: Context): IDataRepository {
val db = Room.databaseBuilder(context, LocalDatabase::class.java, "Look4SatDb")
.fallbackToDestructiveMigration().build()
val parser = DataParser(Dispatchers.Default)
val fileSource = FileDataSource(context.contentResolver, Dispatchers.IO)
val entries = LocalEntrySource(db.entriesDao())
val radios = LocalRadioSource(db.radiosDao())
val remoteSource = RemoteDataSource(Dispatchers.IO)
val repositoryScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
return DataRepository(parser, fileSource, entries, radios, remoteSource, repositoryScope)
}
@Provides
@Singleton
fun provideDataReporter(): DataReporter = DataReporter(CoroutineScope(Dispatchers.IO))
@Provides
@Singleton
fun provideLocationManager(manager: LocationManager): ILocationManager = manager
@Provides
@Singleton
fun provideSatelliteManager(): ISatelliteManager = SatelliteManager(Dispatchers.Default)
@Provides
@Singleton
fun provideSettingsManager(manager: SettingsManager): ISettingsManager = manager
}
@@ -1,91 +0,0 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2022 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.injection
import android.content.Context
import android.content.SharedPreferences
import androidx.room.Room
import com.rtbishop.look4sat.data.DataParser
import com.rtbishop.look4sat.data.Repository
import com.rtbishop.look4sat.domain.DataReporter
import com.rtbishop.look4sat.domain.ILocationHandler
import com.rtbishop.look4sat.domain.IRepository
import com.rtbishop.look4sat.domain.ISettings
import com.rtbishop.look4sat.domain.predict.Predictor
import com.rtbishop.look4sat.framework.LocationHandler
import com.rtbishop.look4sat.framework.SettingsHandler
import com.rtbishop.look4sat.framework.data.Provider
import com.rtbishop.look4sat.framework.data.Storage
import com.rtbishop.look4sat.framework.data.StorageDb
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object DataModule {
@Provides
@Singleton
fun provideSatelliteRepo(
@ApplicationContext context: Context,
@IoDispatcher ioDispatcher: CoroutineDispatcher,
@DefaultDispatcher defaultDispatcher: CoroutineDispatcher
): IRepository {
val db = Room.databaseBuilder(context, StorageDb::class.java, "Look4SatDb")
.fallbackToDestructiveMigration().build()
val dataParser = DataParser(defaultDispatcher)
val localSource = Storage(db.entriesDao(), db.radiosDao())
val remoteSource = Provider(context.contentResolver, ioDispatcher)
val repositoryScope = CoroutineScope(SupervisorJob())
return Repository(dataParser, localSource, remoteSource, repositoryScope)
}
@Provides
@Singleton
fun provideSettingsHandler(sharedPreferences: SharedPreferences): ISettings {
return SettingsHandler(sharedPreferences)
}
@Provides
@Singleton
fun provideLocationHandler(
@ApplicationContext context: Context,
settings: ISettings
): ILocationHandler {
return LocationHandler(context, settings)
}
@Provides
@Singleton
fun providePredictor(@DefaultDispatcher dispatcher: CoroutineDispatcher): Predictor {
return Predictor(dispatcher)
}
@Provides
@Singleton
fun provideDataReporter(@IoDispatcher dispatcher: CoroutineDispatcher): DataReporter {
return DataReporter(dispatcher)
}
}
@@ -18,8 +18,8 @@
package com.rtbishop.look4sat.presentation.entriesScreen package com.rtbishop.look4sat.presentation.entriesScreen
import androidx.lifecycle.* import androidx.lifecycle.*
import com.rtbishop.look4sat.domain.IRepository import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.domain.ISettings import com.rtbishop.look4sat.domain.ISettingsManager
import com.rtbishop.look4sat.domain.model.DataState import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.domain.model.SatItem import com.rtbishop.look4sat.domain.model.SatItem
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@@ -29,8 +29,8 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class EntriesViewModel @Inject constructor( class EntriesViewModel @Inject constructor(
private val repository: IRepository, private val repository: IDataRepository,
private val settings: ISettings private val settings: ISettingsManager
) : ViewModel(), EntriesAdapter.EntriesClickListener { ) : ViewModel(), EntriesAdapter.EntriesClickListener {
private val transModes = MutableLiveData(settings.loadModesSelection()) private val transModes = MutableLiveData(settings.loadModesSelection())
@@ -27,7 +27,7 @@ import androidx.recyclerview.widget.GridLayoutManager
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.DialogModesBinding import com.rtbishop.look4sat.databinding.DialogModesBinding
import com.rtbishop.look4sat.domain.ISettings import com.rtbishop.look4sat.domain.ISettingsManager
import com.rtbishop.look4sat.presentation.setNavResult import com.rtbishop.look4sat.presentation.setNavResult
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
@@ -36,7 +36,7 @@ import javax.inject.Inject
class ModesDialog : AppCompatDialogFragment(), ModesAdapter.ModesClickListener { class ModesDialog : AppCompatDialogFragment(), ModesAdapter.ModesClickListener {
@Inject @Inject
lateinit var preferences: ISettings lateinit var preferences: ISettingsManager
private lateinit var binding: DialogModesBinding private lateinit var binding: DialogModesBinding
private val allModes = listOf( private val allModes = listOf(
"AFSK", "AFSK S-Net", "AFSK SALSAT", "AHRPT", "AM", "APT", "BPSK", "BPSK PMT-A3", "AFSK", "AFSK S-Net", "AFSK SALSAT", "AHRPT", "AM", "APT", "BPSK", "BPSK PMT-A3",
@@ -18,13 +18,13 @@
package com.rtbishop.look4sat.presentation.mapScreen package com.rtbishop.look4sat.presentation.mapScreen
import androidx.lifecycle.* import androidx.lifecycle.*
import com.rtbishop.look4sat.domain.IRepository import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.domain.ISettings import com.rtbishop.look4sat.domain.ISatelliteManager
import com.rtbishop.look4sat.domain.QthConverter import com.rtbishop.look4sat.domain.ISettingsManager
import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.domain.predict.Predictor
import com.rtbishop.look4sat.domain.predict.SatPos import com.rtbishop.look4sat.domain.predict.SatPos
import com.rtbishop.look4sat.domain.predict.Satellite import com.rtbishop.look4sat.domain.predict.Satellite
import com.rtbishop.look4sat.utility.QthConverter
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.util.* import java.util.*
@@ -34,9 +34,9 @@ import kotlin.math.min
@HiltViewModel @HiltViewModel
class MapViewModel @Inject constructor( class MapViewModel @Inject constructor(
private val predictor: Predictor, private val satelliteManager: ISatelliteManager,
private val repository: IRepository, private val repository: IDataRepository,
private val settings: ISettings, private val settings: ISettingsManager,
) : ViewModel() { ) : ViewModel() {
private val stationPosition = settings.loadStationPosition() private val stationPosition = settings.loadStationPosition()
@@ -115,7 +115,7 @@ class MapViewModel @Inject constructor(
val currentTrack = mutableListOf<GeoPos>() val currentTrack = mutableListOf<GeoPos>()
val endDate = Date(date.time + (satellite.orbitalPeriod * 2.4 * 60000L).toLong()) val endDate = Date(date.time + (satellite.orbitalPeriod * 2.4 * 60000L).toLong())
var oldLongitude = 0.0 var oldLongitude = 0.0
predictor.getSatTrack(satellite, pos, date.time, endDate.time).forEach { satPos -> satelliteManager.getTrack(satellite, pos, date.time, endDate.time).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)
@@ -142,7 +142,7 @@ class MapViewModel @Inject constructor(
private suspend fun getPositions(satellites: List<Satellite>, pos: GeoPos, date: Date) { private suspend fun getPositions(satellites: List<Satellite>, pos: GeoPos, date: Date) {
val positions = mutableMapOf<Satellite, GeoPos>() val positions = mutableMapOf<Satellite, GeoPos>()
satellites.forEach { satellite -> satellites.forEach { satellite ->
val satPos = predictor.getSatPos(satellite, pos, date.time) val satPos = satelliteManager.getPosition(satellite, pos, 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))
positions[satellite] = GeoPos(osmLat, osmLon) positions[satellite] = GeoPos(osmLat, osmLon)
@@ -151,12 +151,12 @@ class MapViewModel @Inject constructor(
} }
private suspend fun getSatFootprint(satellite: Satellite, pos: GeoPos, date: Date) { private suspend fun getSatFootprint(satellite: Satellite, pos: GeoPos, date: Date) {
val satPos = predictor.getSatPos(satellite, pos, date.time) val satPos = satelliteManager.getPosition(satellite, pos, date.time)
_footprint.postValue(satPos) _footprint.postValue(satPos)
} }
private suspend fun getSatData(satellite: Satellite, pos: GeoPos, date: Date) { private suspend fun getSatData(satellite: Satellite, pos: GeoPos, date: Date) {
val satPos = predictor.getSatPos(satellite, pos, date.time) val satPos = satelliteManager.getPosition(satellite, pos, 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)
@@ -25,7 +25,7 @@ import android.view.WindowManager
import androidx.appcompat.app.AppCompatDialogFragment import androidx.appcompat.app.AppCompatDialogFragment
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.DialogFilterBinding import com.rtbishop.look4sat.databinding.DialogFilterBinding
import com.rtbishop.look4sat.domain.ISettings import com.rtbishop.look4sat.domain.ISettingsManager
import com.rtbishop.look4sat.presentation.setNavResult import com.rtbishop.look4sat.presentation.setNavResult
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
@@ -34,7 +34,7 @@ import javax.inject.Inject
class FilterDialog : AppCompatDialogFragment() { class FilterDialog : AppCompatDialogFragment() {
@Inject @Inject
lateinit var preferences: ISettings lateinit var preferences: ISettingsManager
override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? {
return inflater.inflate(R.layout.dialog_filter, group, false) return inflater.inflate(R.layout.dialog_filter, group, false)
@@ -31,8 +31,8 @@ import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.FragmentPassesBinding import com.rtbishop.look4sat.databinding.FragmentPassesBinding
import com.rtbishop.look4sat.domain.model.DataState import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.domain.predict.SatPass import com.rtbishop.look4sat.domain.predict.SatPass
import com.rtbishop.look4sat.domain.toTimerString
import com.rtbishop.look4sat.presentation.getNavResult import com.rtbishop.look4sat.presentation.getNavResult
import com.rtbishop.look4sat.utility.toTimerString
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint @AndroidEntryPoint
@@ -18,10 +18,10 @@
package com.rtbishop.look4sat.presentation.passesScreen package com.rtbishop.look4sat.presentation.passesScreen
import androidx.lifecycle.* import androidx.lifecycle.*
import com.rtbishop.look4sat.domain.IRepository import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.domain.ISettings import com.rtbishop.look4sat.domain.ISatelliteManager
import com.rtbishop.look4sat.domain.ISettingsManager
import com.rtbishop.look4sat.domain.model.DataState import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.domain.predict.Predictor
import com.rtbishop.look4sat.domain.predict.SatPass import com.rtbishop.look4sat.domain.predict.SatPass
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.* import kotlinx.coroutines.*
@@ -29,9 +29,9 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class PassesViewModel @Inject constructor( class PassesViewModel @Inject constructor(
private val predictor: Predictor, private val satelliteManager: ISatelliteManager,
private val repository: IRepository, private val repository: IDataRepository,
private val settings: ISettings private val settings: ISettingsManager
) : ViewModel() { ) : ViewModel() {
private var passesProcessing: Job? = null private var passesProcessing: Job? = null
@@ -41,12 +41,12 @@ class PassesViewModel @Inject constructor(
init { init {
viewModelScope.launch { viewModelScope.launch {
predictor.calculatedPasses.collect { passes -> satelliteManager.calculatedPasses.collect { passes ->
passesProcessing?.cancelAndJoin() passesProcessing?.cancelAndJoin()
passesProcessing = viewModelScope.launch { passesProcessing = viewModelScope.launch {
while (isActive) { while (isActive) {
val time = System.currentTimeMillis() val time = System.currentTimeMillis()
val newPasses = predictor.processPasses(passes, time) val newPasses = satelliteManager.processPasses(passes, time)
_passes.postValue(DataState.Success(newPasses)) _passes.postValue(DataState.Success(newPasses))
delay(1000) delay(1000)
} }
@@ -73,7 +73,7 @@ class PassesViewModel @Inject constructor(
val stationPos = settings.loadStationPosition() val stationPos = settings.loadStationPosition()
val selectedIds = settings.loadEntriesSelection() val selectedIds = settings.loadEntriesSelection()
val satellites = repository.getEntriesWithIds(selectedIds) val satellites = repository.getEntriesWithIds(selectedIds)
predictor.forceCalculation(satellites, stationPos, timeRef, hoursAhead, minElevation) satelliteManager.calculatePasses(satellites, stationPos, timeRef, hoursAhead, minElevation)
} }
} }
} }
@@ -30,7 +30,7 @@ import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.FragmentRadarBinding import com.rtbishop.look4sat.databinding.FragmentRadarBinding
import com.rtbishop.look4sat.domain.predict.SatPass import com.rtbishop.look4sat.domain.predict.SatPass
import com.rtbishop.look4sat.domain.predict.SatPos import com.rtbishop.look4sat.domain.predict.SatPos
import com.rtbishop.look4sat.domain.toTimerString import com.rtbishop.look4sat.utility.toTimerString
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint @AndroidEntryPoint
@@ -19,16 +19,16 @@ package com.rtbishop.look4sat.presentation.radarScreen
import android.hardware.GeomagneticField import android.hardware.GeomagneticField
import androidx.lifecycle.* import androidx.lifecycle.*
import com.rtbishop.look4sat.domain.DataReporter import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.domain.IRepository import com.rtbishop.look4sat.domain.ISatelliteManager
import com.rtbishop.look4sat.domain.ISettings import com.rtbishop.look4sat.domain.ISettingsManager
import com.rtbishop.look4sat.domain.model.SatRadio import com.rtbishop.look4sat.domain.model.SatRadio
import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.domain.predict.Predictor
import com.rtbishop.look4sat.domain.predict.SatPass import com.rtbishop.look4sat.domain.predict.SatPass
import com.rtbishop.look4sat.domain.predict.SatPos import com.rtbishop.look4sat.domain.predict.SatPos
import com.rtbishop.look4sat.domain.round import com.rtbishop.look4sat.framework.OrientationManager
import com.rtbishop.look4sat.framework.OrientationHandler import com.rtbishop.look4sat.utility.DataReporter
import com.rtbishop.look4sat.utility.round
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
@@ -38,12 +38,12 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class RadarViewModel @Inject constructor( class RadarViewModel @Inject constructor(
private val orientationHandler: OrientationHandler, private val orientationManager: OrientationManager,
private val reporter: DataReporter, private val reporter: DataReporter,
private val predictor: Predictor, private val satelliteManager: ISatelliteManager,
private val repository: IRepository, private val repository: IDataRepository,
private val settings: ISettings private val settings: ISettingsManager
) : ViewModel(), OrientationHandler.OrientationListener { ) : ViewModel(), OrientationManager.OrientationListener {
private val stationPos = settings.loadStationPosition() private val stationPos = settings.loadStationPosition()
private val _passData = MutableLiveData<RadarData>() private val _passData = MutableLiveData<RadarData>()
@@ -54,7 +54,7 @@ class RadarViewModel @Inject constructor(
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 {
predictor.calculatedPasses.collect { passes -> satelliteManager.calculatedPasses.collect { passes ->
val pass = passes.find { pass -> pass.catNum == catNum && pass.aosTime == aosTime } val pass = passes.find { pass -> pass.catNum == catNum && pass.aosTime == aosTime }
pass?.let { satPass -> pass?.let { satPass ->
emit(satPass) emit(satPass)
@@ -65,11 +65,11 @@ class RadarViewModel @Inject constructor(
} }
fun enableSensor() { fun enableSensor() {
if (settings.getUseCompass()) orientationHandler.startListening(this) if (settings.getUseCompass()) orientationManager.startListening(this)
} }
fun disableSensor() { fun disableSensor() {
if (settings.getUseCompass()) orientationHandler.stopListening() if (settings.getUseCompass()) orientationManager.stopListening()
} }
fun getUseCompass(): Boolean = settings.getUseCompass() fun getUseCompass(): Boolean = settings.getUseCompass()
@@ -92,10 +92,10 @@ class RadarViewModel @Inject constructor(
if (!satPass.isDeepSpace) { if (!satPass.isDeepSpace) {
val startDate = satPass.aosTime val startDate = satPass.aosTime
val endDate = satPass.losTime val endDate = satPass.losTime
satTrack = predictor.getSatTrack(satPass.satellite, stationPos, startDate, endDate) satTrack = satelliteManager.getTrack(satPass.satellite, stationPos, startDate, endDate)
} }
while (isActive) { while (isActive) {
val satPos = predictor.getSatPos(satPass.satellite, stationPos, Date().time) val satPos = satelliteManager.getPosition(satPass.satellite, stationPos, Date().time)
if (settings.getRotatorEnabled()) { if (settings.getRotatorEnabled()) {
val server = settings.getRotatorServer() val server = settings.getRotatorServer()
val port = settings.getRotatorPort().toInt() val port = settings.getRotatorPort().toInt()
@@ -115,7 +115,7 @@ class RadarViewModel @Inject constructor(
val transmitters = repository.getRadiosWithId(pass.catNum) val transmitters = repository.getRadiosWithId(pass.catNum)
while (isActive) { while (isActive) {
val time = System.currentTimeMillis() val time = System.currentTimeMillis()
val list = predictor.processRadios(pass.satellite, stationPos, transmitters, time) val list = satelliteManager.processRadios(pass.satellite, stationPos, transmitters, time)
_transmitters.postValue(list) _transmitters.postValue(list)
delay(1000) delay(1000)
} }
@@ -25,7 +25,7 @@ import android.view.WindowManager
import androidx.appcompat.app.AppCompatDialogFragment import androidx.appcompat.app.AppCompatDialogFragment
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.DialogLocatorBinding import com.rtbishop.look4sat.databinding.DialogLocatorBinding
import com.rtbishop.look4sat.domain.ISettings import com.rtbishop.look4sat.domain.ISettingsManager
import com.rtbishop.look4sat.presentation.setNavResult import com.rtbishop.look4sat.presentation.setNavResult
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
@@ -34,7 +34,7 @@ import javax.inject.Inject
class LocatorDialog : AppCompatDialogFragment() { class LocatorDialog : AppCompatDialogFragment() {
@Inject @Inject
lateinit var preferences: ISettings lateinit var preferences: ISettingsManager
override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? {
return inflater.inflate(R.layout.dialog_locator, group, false) return inflater.inflate(R.layout.dialog_locator, group, false)
@@ -25,7 +25,7 @@ import android.view.WindowManager
import androidx.appcompat.app.AppCompatDialogFragment import androidx.appcompat.app.AppCompatDialogFragment
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.DialogPositionBinding import com.rtbishop.look4sat.databinding.DialogPositionBinding
import com.rtbishop.look4sat.domain.ISettings import com.rtbishop.look4sat.domain.ISettingsManager
import com.rtbishop.look4sat.presentation.setNavResult import com.rtbishop.look4sat.presentation.setNavResult
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
@@ -34,7 +34,7 @@ import javax.inject.Inject
class PositionDialog : AppCompatDialogFragment() { class PositionDialog : AppCompatDialogFragment() {
@Inject @Inject
lateinit var preferences: ISettings lateinit var preferences: ISettingsManager
override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? {
return inflater.inflate(R.layout.dialog_position, group, false) return inflater.inflate(R.layout.dialog_position, group, false)
@@ -34,11 +34,11 @@ import androidx.navigation.fragment.findNavController
import com.rtbishop.look4sat.BuildConfig import com.rtbishop.look4sat.BuildConfig
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.FragmentSettingsBinding import com.rtbishop.look4sat.databinding.FragmentSettingsBinding
import com.rtbishop.look4sat.domain.isValidIPv4
import com.rtbishop.look4sat.domain.isValidPort
import com.rtbishop.look4sat.domain.model.DataState import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.presentation.getNavResult import com.rtbishop.look4sat.presentation.getNavResult
import com.rtbishop.look4sat.utility.isValidIPv4
import com.rtbishop.look4sat.utility.isValidPort
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint @AndroidEntryPoint
@@ -19,9 +19,9 @@ package com.rtbishop.look4sat.presentation.settingsScreen
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import com.rtbishop.look4sat.domain.ILocationHandler import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.domain.IRepository import com.rtbishop.look4sat.domain.ILocationManager
import com.rtbishop.look4sat.domain.ISettings import com.rtbishop.look4sat.domain.ISettingsManager
import com.rtbishop.look4sat.domain.model.DataState import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.domain.predict.GeoPos
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@@ -30,9 +30,9 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class SettingsViewModel @Inject constructor( class SettingsViewModel @Inject constructor(
private val locationHandler: ILocationHandler, private val locationManager: ILocationManager,
private val repository: IRepository, private val repository: IDataRepository,
private val settings: ISettings private val settings: ISettingsManager
) : ViewModel() { ) : ViewModel() {
val entriesTotal = repository.getEntriesTotal().asLiveData() val entriesTotal = repository.getEntriesTotal().asLiveData()
@@ -79,17 +79,17 @@ class SettingsViewModel @Inject constructor(
fun setUpdateHandled() = repository.setUpdateStateHandled() fun setUpdateHandled() = repository.setUpdateStateHandled()
val stationPosition: SharedFlow<DataState<GeoPos>> = locationHandler.stationPosition val stationPosition: SharedFlow<DataState<GeoPos>> = locationManager.stationPosition
fun getStationPosition(): GeoPos = locationHandler.getStationPosition() fun getStationPosition(): GeoPos = locationManager.getStationPosition()
fun setStationPosition(lat: Double, lon: Double) = locationHandler.setStationPosition(lat, lon) fun setStationPosition(lat: Double, lon: Double) = locationManager.setStationPosition(lat, lon)
fun setPositionFromGps() = locationHandler.setPositionFromGps() fun setPositionFromGps() = locationManager.setPositionFromGps()
fun setPositionFromNet() = locationHandler.setPositionFromNet() fun setPositionFromNet() = locationManager.setPositionFromNet()
fun setPositionFromQth(qthString: String) = locationHandler.setPositionFromQth(qthString) fun setPositionFromQth(qthString: String) = locationManager.setPositionFromQth(qthString)
fun setPositionHandled() = locationHandler.setPositionHandled() fun setPositionHandled() = locationManager.setPositionHandled()
} }
@@ -27,7 +27,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.DialogSourcesBinding import com.rtbishop.look4sat.databinding.DialogSourcesBinding
import com.rtbishop.look4sat.domain.ISettings import com.rtbishop.look4sat.domain.ISettingsManager
import com.rtbishop.look4sat.framework.model.DataSource import com.rtbishop.look4sat.framework.model.DataSource
import com.rtbishop.look4sat.presentation.setNavResult import com.rtbishop.look4sat.presentation.setNavResult
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@@ -37,7 +37,7 @@ import javax.inject.Inject
class SourcesDialog : AppCompatDialogFragment(), SourcesAdapter.SourcesClickListener { class SourcesDialog : AppCompatDialogFragment(), SourcesAdapter.SourcesClickListener {
@Inject @Inject
lateinit var settings: ISettings lateinit var settings: ISettingsManager
private lateinit var binding: DialogSourcesBinding private lateinit var binding: DialogSourcesBinding
private lateinit var sourcesAdapter: SourcesAdapter private lateinit var sourcesAdapter: SourcesAdapter
View File
File renamed without changes.
File renamed without changes.
@@ -24,7 +24,7 @@ import com.rtbishop.look4sat.domain.predict.Satellite
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
interface IRepository { interface IDataRepository {
val updateState: StateFlow<DataState<Long>> val updateState: StateFlow<DataState<Long>>
@@ -21,7 +21,7 @@ import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.domain.predict.GeoPos
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
interface ILocationHandler { interface ILocationManager {
val stationPosition: SharedFlow<DataState<GeoPos>> val stationPosition: SharedFlow<DataState<GeoPos>>
@@ -0,0 +1,34 @@
package com.rtbishop.look4sat.domain
import com.rtbishop.look4sat.domain.model.SatRadio
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.domain.predict.SatPass
import com.rtbishop.look4sat.domain.predict.SatPos
import com.rtbishop.look4sat.domain.predict.Satellite
import kotlinx.coroutines.flow.SharedFlow
interface ISatelliteManager {
val calculatedPasses: SharedFlow<List<SatPass>>
suspend fun getPosition(sat: Satellite, pos: GeoPos, time: Long): SatPos
suspend fun getTrack(sat: Satellite, pos: GeoPos, start: Long, end: Long): List<SatPos>
suspend fun processRadios(
sat: Satellite,
pos: GeoPos,
radios: List<SatRadio>,
time: Long
): List<SatRadio>
suspend fun processPasses(passList: List<SatPass>, time: Long): List<SatPass>
suspend fun calculatePasses(
satList: List<Satellite>,
pos: GeoPos,
time: Long,
hoursAhead: Int = 8,
minElevation: Double = 16.0
)
}
@@ -19,7 +19,7 @@ package com.rtbishop.look4sat.domain
import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.domain.predict.GeoPos
interface ISettings { interface ISettingsManager {
val defaultSources: List<String> val defaultSources: List<String>
get() = listOf( get() = listOf(
@@ -15,23 +15,26 @@
* 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.data package com.rtbishop.look4sat.domain.data
import com.rtbishop.look4sat.domain.IRepository import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.domain.model.DataState import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.domain.model.SatEntry import com.rtbishop.look4sat.domain.model.SatEntry
import com.rtbishop.look4sat.utility.DataParser
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import java.io.InputStream import java.io.InputStream
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
class Repository( class DataRepository(
private val dataParser: DataParser, private val dataParser: DataParser,
private val storage: IStorage, private val fileSource: IFileDataSource,
private val provider: IProvider, private val entrySource: ILocalEntrySource,
private val repoScope: CoroutineScope private val radioSource: ILocalRadioSource,
) : IRepository { private val remoteSource: IRemoteDataSource,
private val repositoryScope: CoroutineScope
) : IDataRepository {
private val exceptionHandler = CoroutineExceptionHandler { _, exception -> private val exceptionHandler = CoroutineExceptionHandler { _, exception ->
_updateState.value = DataState.Error(exception.message) _updateState.value = DataState.Error(exception.message)
@@ -40,22 +43,22 @@ class Repository(
private val _updateState = MutableStateFlow<DataState<Long>>(DataState.Handled) private val _updateState = MutableStateFlow<DataState<Long>>(DataState.Handled)
override val updateState: StateFlow<DataState<Long>> = _updateState override val updateState: StateFlow<DataState<Long>> = _updateState
override fun getEntriesTotal() = storage.getEntriesTotal() override fun getEntriesTotal() = entrySource.getEntriesTotal()
override fun getRadiosTotal() = storage.getRadiosTotal() override fun getRadiosTotal() = radioSource.getRadiosTotal()
override suspend fun getEntriesWithModes() = storage.getEntriesWithModes() override suspend fun getEntriesWithModes() = entrySource.getEntriesWithModes()
override suspend fun getEntriesWithIds(ids: List<Int>) = storage.getEntriesWithIds(ids) override suspend fun getEntriesWithIds(ids: List<Int>) = entrySource.getEntriesWithIds(ids)
override suspend fun getRadiosWithId(id: Int) = storage.getRadiosWithId(id) override suspend fun getRadiosWithId(id: Int) = radioSource.getRadiosWithId(id)
override fun updateFromFile(uri: String) { override fun updateFromFile(uri: String) {
repoScope.launch(exceptionHandler) { repositoryScope.launch(exceptionHandler) {
_updateState.value = DataState.Loading _updateState.value = DataState.Loading
provider.getLocalFileStream(uri)?.let { fileStream -> fileSource.getDataStream(uri)?.let { fileStream ->
delay(updateStateDelay) delay(updateStateDelay)
storage.insertEntries(importSatellites(fileStream)) entrySource.insertEntries(importSatellites(fileStream))
} }
_updateState.value = DataState.Success(0L) _updateState.value = DataState.Success(0L)
} }
@@ -63,12 +66,12 @@ class Repository(
override fun updateFromWeb(urls: List<String>) { override fun updateFromWeb(urls: List<String>) {
_updateState.value = DataState.Loading _updateState.value = DataState.Loading
repoScope.launch(exceptionHandler) { repositoryScope.launch(exceptionHandler) {
val jobsMap = mutableMapOf<String, Deferred<InputStream?>>() val jobsMap = mutableMapOf<String, Deferred<InputStream?>>()
val streamsMap = mutableMapOf<String, InputStream?>() val streamsMap = mutableMapOf<String, InputStream?>()
val streams = mutableListOf<InputStream>() val streams = mutableListOf<InputStream>()
val entries = mutableListOf<SatEntry>() val entries = mutableListOf<SatEntry>()
urls.forEach { jobsMap[it] = async { provider.getRemoteFileStream(it) } } urls.forEach { jobsMap[it] = async { remoteSource.getDataStream(it) } }
jobsMap.forEach { job -> streamsMap[job.key] = job.value.await() } jobsMap.forEach { job -> streamsMap[job.key] = job.value.await() }
streamsMap.forEach { stream -> streamsMap.forEach { stream ->
stream.value?.let { inputStream -> stream.value?.let { inputStream ->
@@ -85,11 +88,11 @@ class Repository(
} }
} }
streams.forEach { stream -> entries.addAll(importSatellites(stream)) } streams.forEach { stream -> entries.addAll(importSatellites(stream)) }
storage.insertEntries(entries) entrySource.insertEntries(entries)
} }
repoScope.launch(exceptionHandler) { repositoryScope.launch(exceptionHandler) {
provider.getRemoteFileStream(provider.radioApi)?.let { stream -> remoteSource.getDataStream(remoteSource.radioApi)?.let { stream ->
storage.insertRadios(dataParser.parseJSONStream(stream)) radioSource.insertRadios(dataParser.parseJSONStream(stream))
_updateState.value = DataState.Success(0L) _updateState.value = DataState.Success(0L)
} }
} }
@@ -100,10 +103,11 @@ class Repository(
} }
override fun clearAllData() { override fun clearAllData() {
repoScope.launch { repositoryScope.launch {
_updateState.value = DataState.Loading _updateState.value = DataState.Loading
delay(updateStateDelay) delay(updateStateDelay)
storage.clearAllData() entrySource.deleteEntries()
radioSource.deleteRadios()
_updateState.value = DataState.Success(0L) _updateState.value = DataState.Success(0L)
} }
} }
@@ -0,0 +1,25 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2022 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.data
import java.io.InputStream
interface IFileDataSource {
suspend fun getDataStream(uri: String): InputStream?
}
@@ -15,29 +15,22 @@
* 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.data package com.rtbishop.look4sat.domain.data
import com.rtbishop.look4sat.domain.model.SatEntry import com.rtbishop.look4sat.domain.model.SatEntry
import com.rtbishop.look4sat.domain.model.SatItem import com.rtbishop.look4sat.domain.model.SatItem
import com.rtbishop.look4sat.domain.model.SatRadio
import com.rtbishop.look4sat.domain.predict.Satellite import com.rtbishop.look4sat.domain.predict.Satellite
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface IStorage { interface ILocalEntrySource {
fun getEntriesTotal(): Flow<Int> fun getEntriesTotal(): Flow<Int>
fun getRadiosTotal(): Flow<Int>
suspend fun getEntriesWithModes(): List<SatItem> suspend fun getEntriesWithModes(): List<SatItem>
suspend fun getEntriesWithIds(ids: List<Int>): List<Satellite> suspend fun getEntriesWithIds(ids: List<Int>): List<Satellite>
suspend fun getRadiosWithId(id: Int): List<SatRadio>
suspend fun insertEntries(entries: List<SatEntry>) suspend fun insertEntries(entries: List<SatEntry>)
suspend fun insertRadios(radios: List<SatRadio>) suspend fun deleteEntries()
suspend fun clearAllData()
} }
@@ -0,0 +1,32 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2022 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.data
import com.rtbishop.look4sat.domain.model.SatRadio
import kotlinx.coroutines.flow.Flow
interface ILocalRadioSource {
fun getRadiosTotal(): Flow<Int>
suspend fun getRadiosWithId(id: Int): List<SatRadio>
suspend fun insertRadios(radios: List<SatRadio>)
suspend fun deleteRadios()
}
@@ -15,15 +15,13 @@
* 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.data package com.rtbishop.look4sat.domain.data
import java.io.InputStream import java.io.InputStream
interface IProvider { interface IRemoteDataSource {
val radioApi: String get() = "https://db.satnogs.org/api/transmitters/?format=json" val radioApi: String get() = "https://db.satnogs.org/api/transmitters/?format=json"
suspend fun getLocalFileStream(uri: String): InputStream? suspend fun getDataStream(url: String): InputStream?
suspend fun getRemoteFileStream(url: String): InputStream?
} }
@@ -35,45 +35,45 @@ class DeepSpaceSat(data: OrbitalData) : Satellite(data) {
init { init {
// Recover original mean motion (xnodp) and semimajor axis (aodp) from input elements // Recover original mean motion (xnodp) and semimajor axis (aodp) from input elements
val a1 = (xke / super.data.xno).pow(twoThirds) val a1 = (XKE / data.xno).pow(TWO_THIRDS)
dsv.cosio = cos(super.data.xincl) dsv.cosio = cos(data.xincl)
dsv.theta2 = dsv.cosio * dsv.cosio dsv.theta2 = dsv.cosio * dsv.cosio
x3thm1 = 3.0 * dsv.theta2 - 1 x3thm1 = 3.0 * dsv.theta2 - 1
dsv.eosq = super.data.eccn * super.data.eccn dsv.eosq = data.eccn * data.eccn
dsv.betao2 = 1.0 - dsv.eosq dsv.betao2 = 1.0 - dsv.eosq
dsv.betao = sqrt(dsv.betao2) dsv.betao = sqrt(dsv.betao2)
val del1 = 1.5 * ck2 * x3thm1 / (a1 * a1 * dsv.betao * dsv.betao2) val del1 = 1.5 * CK2 * x3thm1 / (a1 * a1 * dsv.betao * dsv.betao2)
val ao = a1 * (1.0 - del1 * (0.5 * twoThirds + del1 * (1.0 + 134.0 / 81.0 * del1))) val ao = a1 * (1.0 - del1 * (0.5 * TWO_THIRDS + del1 * (1.0 + 134.0 / 81.0 * del1)))
val delo = 1.5 * ck2 * x3thm1 / (ao * ao * dsv.betao * dsv.betao2) val delo = 1.5 * CK2 * x3thm1 / (ao * ao * dsv.betao * dsv.betao2)
dsv.xnodp = super.data.xno / (1.0 + delo) dsv.xnodp = data.xno / (1.0 + delo)
dsv.aodp = ao / (1.0 - delo) dsv.aodp = ao / (1.0 - delo)
// For perigee below 156 km, the values of S and QOMS2T are altered // For perigee below 156 km, the values of S and QOMS2T are altered
setPerigee((dsv.aodp * (1.0 - super.data.eccn) - 1.0) * earthRadius) setPerigee((dsv.aodp * (1.0 - data.eccn) - 1.0) * EARTH_RADIUS)
val pinvsq = invert(dsv.aodp * dsv.aodp * dsv.betao2 * dsv.betao2) val pinvsq = invert(dsv.aodp * dsv.aodp * dsv.betao2 * dsv.betao2)
dsv.sing = sin(super.data.omegao) dsv.sing = sin(data.omegao)
dsv.cosg = cos(super.data.omegao) dsv.cosg = cos(data.omegao)
val tsi = invert(dsv.aodp - s4) val tsi = invert(dsv.aodp - s4)
val eta = dsv.aodp * super.data.eccn * tsi val eta = dsv.aodp * data.eccn * tsi
val etasq = eta * eta val etasq = eta * eta
val eeta = super.data.eccn * eta val eeta = data.eccn * eta
val psisq = abs(1.0 - etasq) val psisq = abs(1.0 - etasq)
val coef = qoms24 * tsi.pow(4.0) val coef = qoms24 * tsi.pow(4.0)
val coef1 = coef / psisq.pow(3.5) val coef1 = coef / psisq.pow(3.5)
val c2 = coef1 * dsv.xnodp * (dsv.aodp * (1.0 + 1.5 * etasq + eeta * (4.0 + etasq)) val c2 = coef1 * dsv.xnodp * (dsv.aodp * (1.0 + 1.5 * etasq + eeta * (4.0 + etasq))
+ 0.75 * ck2 * tsi / psisq * x3thm1 * (8.0 + 3.0 * etasq * (8.0 + etasq))) + 0.75 * CK2 * tsi / psisq * x3thm1 * (8.0 + 3.0 * etasq * (8.0 + etasq)))
c1 = super.data.bstar * c2 c1 = data.bstar * c2
dsv.sinio = sin(super.data.xincl) dsv.sinio = sin(data.xincl)
val a3ovk2 = -j3Harmonic / ck2 val a3ovk2 = -J3_HARMONIC / CK2
x1mth2 = 1.0 - dsv.theta2 x1mth2 = 1.0 - dsv.theta2
c4 = c4 =
2 * dsv.xnodp * coef1 * dsv.aodp * dsv.betao2 * (eta * (2.0 + 0.5 * etasq) + super.data.eccn 2 * dsv.xnodp * coef1 * dsv.aodp * dsv.betao2 * (eta * (2.0 + 0.5 * etasq) + data.eccn
* (0.5 + 2 * etasq) - 2 * ck2 * tsi / (dsv.aodp * psisq) * (0.5 + 2 * etasq) - 2 * CK2 * tsi / (dsv.aodp * psisq)
* (-3 * x3thm1 * (1.0 - 2 * eeta + etasq * (1.5 - 0.5 * eeta)) + (0.75 * x1mth2 * (-3 * x3thm1 * (1.0 - 2 * eeta + etasq * (1.5 - 0.5 * eeta)) + (0.75 * x1mth2
* (2.0 * etasq - eeta * (1.0 + etasq)) * cos(2.0 * super.data.omegao)))) * (2.0 * etasq - eeta * (1.0 + etasq)) * cos(2.0 * data.omegao))))
val theta4 = dsv.theta2 * dsv.theta2 val theta4 = dsv.theta2 * dsv.theta2
val temp1 = 3.0 * ck2 * pinvsq * dsv.xnodp val temp1 = 3.0 * CK2 * pinvsq * dsv.xnodp
val temp2 = temp1 * ck2 * pinvsq val temp2 = temp1 * CK2 * pinvsq
val temp3 = 1.25 * ck4 * pinvsq * pinvsq * dsv.xnodp val temp3 = 1.25 * CK4 * pinvsq * pinvsq * dsv.xnodp
dsv.xmdot = dsv.xmdot =
dsv.xnodp + 0.5 * temp1 * dsv.betao * x3thm1 + 0.0625 * temp2 * dsv.betao * (13 - 78 * dsv.theta2 + 137 * theta4) dsv.xnodp + 0.5 * temp1 * dsv.betao * x3thm1 + 0.0625 * temp2 * dsv.betao * (13 - 78 * dsv.theta2 + 137 * theta4)
val x1m5th = 1.0 - 5 * dsv.theta2 val x1m5th = 1.0 - 5 * dsv.theta2
@@ -105,12 +105,12 @@ class DeepSpaceSat(data: OrbitalData) : Satellite(data) {
dsv.xn = dsv.xnodp dsv.xn = dsv.xnodp
dsv.t = tSince dsv.t = tSince
deep.dpsec(data) deep.dpsec(data)
val a = (xke / dsv.xn).pow(twoThirds) * tempa * tempa val a = (XKE / dsv.xn).pow(TWO_THIRDS) * tempa * tempa
dsv.em = dsv.em - tempe dsv.em = dsv.em - tempe
deep.dpper() deep.dpper()
val xl = dsv.xll + dsv.omgadf + dsv.xnode val xl = dsv.xll + dsv.omgadf + dsv.xnode
val beta = sqrt(1.0 - dsv.em * dsv.em) val beta = sqrt(1.0 - dsv.em * dsv.em)
dsv.xn = xke / a.pow(1.5) dsv.xn = XKE / a.pow(1.5)
// Long period periodics // Long period periodics
val axn = dsv.em * cos(dsv.omgadf) val axn = dsv.em * cos(dsv.omgadf)
@@ -136,8 +136,8 @@ class DeepSpaceSat(data: OrbitalData) : Satellite(data) {
val pl = a * temp[0] val pl = a * temp[0]
temp[9] = a * (1.0 - ecose) temp[9] = a * (1.0 - ecose)
temp[1] = invert(temp[9]) temp[1] = invert(temp[9])
temp[10] = xke * sqrt(a) * esine * temp[1] temp[10] = XKE * sqrt(a) * esine * temp[1]
temp[11] = xke * sqrt(pl) * temp[1] temp[11] = XKE * sqrt(pl) * temp[1]
temp[2] = a * temp[1] temp[2] = a * temp[1]
val betal = sqrt(temp[0]) val betal = sqrt(temp[0])
temp[3] = invert(1.0 + betal) temp[3] = invert(1.0 + betal)
@@ -147,7 +147,7 @@ class DeepSpaceSat(data: OrbitalData) : Satellite(data) {
val sin2u = 2.0 * sinu * cosu val sin2u = 2.0 * sinu * cosu
val cos2u = 2.0 * cosu * cosu - 1 val cos2u = 2.0 * cosu * cosu - 1
temp[0] = invert(pl) temp[0] = invert(pl)
temp[1] = ck2 * temp[0] temp[1] = CK2 * temp[0]
temp[2] = temp[1] * temp[0] temp[2] = temp[1] * temp[0]
// Update for short periodics // Update for short periodics
@@ -614,8 +614,8 @@ class DeepSpaceSat(data: OrbitalData) : Satellite(data) {
} else { } else {
applyPeriodics() applyPeriodics()
// This is a patch to Lyddane modification suggested by Rob Matson // This is a patch to Lyddane modification suggested by Rob Matson
if (abs(xnoh - dsv.xnode) > Math.PI) { if (abs(xnoh - dsv.xnode) > PI) {
if (dsv.xnode < xnoh) dsv.xnode += twoPi else dsv.xnode -= twoPi if (dsv.xnode < xnoh) dsv.xnode += TWO_PI else dsv.xnode -= TWO_PI
} }
dsv.xll = dsv.xll + pl dsv.xll = dsv.xll + pl
dsv.omgadf = xls - dsv.xll - cos(dsv.xinc) * dsv.xnode dsv.omgadf = xls - dsv.xll - cos(dsv.xinc) * dsv.xnode
@@ -631,8 +631,8 @@ class DeepSpaceSat(data: OrbitalData) : Satellite(data) {
dsv.xinc = params.xincl + ssi * dsv.t dsv.xinc = params.xincl + ssi * dsv.t
if (dsv.xinc < 0) { if (dsv.xinc < 0) {
dsv.xinc = -dsv.xinc dsv.xinc = -dsv.xinc
dsv.xnode = dsv.xnode + Math.PI dsv.xnode = dsv.xnode + PI
dsv.omgadf = dsv.omgadf - Math.PI dsv.omgadf = dsv.omgadf - PI
} }
if (!resonance) return if (!resonance) return
do processEpochRestartLoop() while (doLoop && epochRestart) do processEpochRestartLoop() while (doLoop && epochRestart)
@@ -53,25 +53,25 @@ class NearEarthSat(data: OrbitalData) : Satellite(data) {
init { init {
// Recover original mean motion (xnodp) and semimajor axis (aodp) from input elements // Recover original mean motion (xnodp) and semimajor axis (aodp) from input elements
val a1 = (xke / super.data.xno).pow(twoThirds) val a1 = (XKE / data.xno).pow(TWO_THIRDS)
cosio = cos(super.data.xincl) cosio = cos(data.xincl)
val theta2 = sqr(cosio) val theta2 = sqr(cosio)
x3thm1 = 3.0 * theta2 - 1.0 x3thm1 = 3.0 * theta2 - 1.0
val eo = super.data.eccn val eo = data.eccn
val eosq = sqr(eo) val eosq = sqr(eo)
val betao2 = 1.0 - eosq val betao2 = 1.0 - eosq
val betao = sqrt(betao2) val betao = sqrt(betao2)
val del1 = 1.5 * ck2 * x3thm1 / (sqr(a1) * betao * betao2) val del1 = 1.5 * CK2 * x3thm1 / (sqr(a1) * betao * betao2)
val ao = a1 * (1.0 - del1 * (0.5 * twoThirds + del1 * (1.0 + 134.0 / 81.0 * del1))) val ao = a1 * (1.0 - del1 * (0.5 * TWO_THIRDS + del1 * (1.0 + 134.0 / 81.0 * del1)))
val delo = 1.5 * ck2 * x3thm1 / (sqr(ao) * betao * betao2) val delo = 1.5 * CK2 * x3thm1 / (sqr(ao) * betao * betao2)
xnodp = super.data.xno / (1.0 + delo) xnodp = data.xno / (1.0 + delo)
aodp = ao / (1.0 - delo) aodp = ao / (1.0 - delo)
// For perigee less than 220 kilometers, the "simple" flag is set // For perigee less than 220 kilometers, the "simple" flag is set
sgp4Simple = aodp * (1.0 - eo) < 220 / earthRadius + 1.0 sgp4Simple = aodp * (1.0 - eo) < 220 / EARTH_RADIUS + 1.0
// For perigees below 156 km, the values of S and QOMS2T are altered // For perigees below 156 km, the values of S and QOMS2T are altered
setPerigee((aodp * (1.0 - eo) - 1.0) * earthRadius) setPerigee((aodp * (1.0 - eo) - 1.0) * EARTH_RADIUS)
val pinvsq = invert(sqr(aodp) * sqr(betao2)) val pinvsq = invert(sqr(aodp) * sqr(betao2))
val tsi = invert(aodp - s4) val tsi = invert(aodp - s4)
eta = aodp * eo * tsi eta = aodp * eo * tsi
@@ -80,24 +80,24 @@ class NearEarthSat(data: OrbitalData) : Satellite(data) {
val psisq = abs(1.0 - etasq) val psisq = abs(1.0 - etasq)
val coef = qoms24 * tsi.pow(4.0) val coef = qoms24 * tsi.pow(4.0)
val coef1 = coef / psisq.pow(3.5) val coef1 = coef / psisq.pow(3.5)
val bstar = super.data.bstar val bstar = data.bstar
val c2 = coef1 * xnodp * (aodp * (1.0 + 1.5 * etasq + eeta * (4.0 + etasq)) + 0.75 val c2 = coef1 * xnodp * (aodp * (1.0 + 1.5 * etasq + eeta * (4.0 + etasq)) + 0.75
* ck2 * tsi / psisq * x3thm1 * (8.0 + 3.0 * etasq * (8.0 + etasq))) * CK2 * tsi / psisq * x3thm1 * (8.0 + 3.0 * etasq * (8.0 + etasq)))
c1 = bstar * c2 c1 = bstar * c2
sinio = sin(super.data.xincl) sinio = sin(data.xincl)
val a3ovk2 = -j3Harmonic / ck2 val a3ovk2 = -J3_HARMONIC / CK2
val c3 = coef * tsi * a3ovk2 * xnodp * sinio / eo val c3 = coef * tsi * a3ovk2 * xnodp * sinio / eo
x1mth2 = 1.0 - theta2 x1mth2 = 1.0 - theta2
val omegao = super.data.omegao val omegao = data.omegao
c4 = 2 * xnodp * coef1 * aodp * betao2 * (eta * (2.0 + 0.5 * etasq) + eo * (0.5 + 2 * etasq) c4 = 2 * xnodp * coef1 * aodp * betao2 * (eta * (2.0 + 0.5 * etasq) + eo * (0.5 + 2 * etasq)
- 2 * ck2 * tsi / (aodp * psisq) * (-3 * x3thm1 * (1.0 - 2 * eeta + etasq - 2 * CK2 * tsi / (aodp * psisq) * (-3 * x3thm1 * (1.0 - 2 * eeta + etasq
* (1.5 - 0.5 * eeta)) + 0.75 * x1mth2 * (2.0 * etasq - eeta * (1.0 + etasq)) * (1.5 - 0.5 * eeta)) + 0.75 * x1mth2 * (2.0 * etasq - eeta * (1.0 + etasq))
* cos(2.0 * omegao))) * cos(2.0 * omegao)))
c5 = 2.0 * coef1 * aodp * betao2 * (1.0 + 2.75 * (etasq + eeta) + eeta * etasq) c5 = 2.0 * coef1 * aodp * betao2 * (1.0 + 2.75 * (etasq + eeta) + eeta * etasq)
val theta4 = sqr(theta2) val theta4 = sqr(theta2)
val temp1 = 3.0 * ck2 * pinvsq * xnodp val temp1 = 3.0 * CK2 * pinvsq * xnodp
val temp2 = temp1 * ck2 * pinvsq val temp2 = temp1 * CK2 * pinvsq
val temp3 = 1.25 * ck4 * pinvsq * pinvsq * xnodp val temp3 = 1.25 * CK4 * pinvsq * pinvsq * xnodp
xmdot = xmdot =
xnodp + 0.5 * temp1 * betao * x3thm1 + (0.0625 * temp2 * betao * (13.0 - 78.0 * theta2 + 137.0 * theta4)) xnodp + 0.5 * temp1 * betao * x3thm1 + (0.0625 * temp2 * betao * (13.0 - 78.0 * theta2 + 137.0 * theta4))
val x1m5th = 1.0 - 5.0 * theta2 val x1m5th = 1.0 - 5.0 * theta2
@@ -107,12 +107,12 @@ class NearEarthSat(data: OrbitalData) : Satellite(data) {
xnodot = xnodot =
xhdot1 + (0.5 * temp2 * (4.0 - 19.0 * theta2) + 2.0 * temp3 * (3.0 - 7.0 * theta2)) * cosio xhdot1 + (0.5 * temp2 * (4.0 - 19.0 * theta2) + 2.0 * temp3 * (3.0 - 7.0 * theta2)) * cosio
omgcof = bstar * c3 * cos(omegao) omgcof = bstar * c3 * cos(omegao)
xmcof = -twoThirds * coef * bstar / eeta xmcof = -TWO_THIRDS * coef * bstar / eeta
xnodcf = 3.5 * betao2 * xhdot1 * c1 xnodcf = 3.5 * betao2 * xhdot1 * c1
t2cof = 1.5 * c1 t2cof = 1.5 * c1
xlcof = 0.125 * a3ovk2 * sinio * (3.0 + 5 * cosio) / (1.0 + cosio) xlcof = 0.125 * a3ovk2 * sinio * (3.0 + 5 * cosio) / (1.0 + cosio)
aycof = 0.25 * a3ovk2 * sinio aycof = 0.25 * a3ovk2 * sinio
val xmo = super.data.xmo val xmo = data.xmo
delmo = (1.0 + eta * cos(xmo)).pow(3.0) delmo = (1.0 + eta * cos(xmo)).pow(3.0)
sinmo = sin(xmo) sinmo = sin(xmo)
x7thm1 = 7.0 * theta2 - 1 x7thm1 = 7.0 * theta2 - 1
@@ -166,7 +166,7 @@ class NearEarthSat(data: OrbitalData) : Satellite(data) {
val e = eo - tempe val e = eo - tempe
val xl = xmp + omega + xnode + xnodp * templ val xl = xmp + omega + xnode + xnodp * templ
val beta = sqrt(1.0 - e * e) val beta = sqrt(1.0 - e * e)
val xn = xke / a.pow(1.5) val xn = XKE / a.pow(1.5)
// Long period periodics // Long period periodics
val axn = e * cos(omega) val axn = e * cos(omega)
@@ -195,8 +195,8 @@ class NearEarthSat(data: OrbitalData) : Satellite(data) {
val pl = a * temp[0] val pl = a * temp[0]
val r = a * (1.0 - ecose) val r = a * (1.0 - ecose)
temp[1] = invert(r) temp[1] = invert(r)
val rdot = xke * sqrt(a) * esine * temp[1] val rdot = XKE * sqrt(a) * esine * temp[1]
val rfdot = xke * sqrt(pl) * temp[1] val rfdot = XKE * sqrt(pl) * temp[1]
temp[2] = a * temp[1] temp[2] = a * temp[1]
val betal = sqrt(temp[0]) val betal = sqrt(temp[0])
temp[3] = invert(1.0 + betal) temp[3] = invert(1.0 + betal)
@@ -206,7 +206,7 @@ class NearEarthSat(data: OrbitalData) : Satellite(data) {
val sin2u = 2.0 * sinu * cosu val sin2u = 2.0 * sinu * cosu
val cos2u = 2.0 * cosu * cosu - 1 val cos2u = 2.0 * cosu * cosu - 1
temp[0] = invert(pl) temp[0] = invert(pl)
temp[1] = ck2 * temp[0] temp[1] = CK2 * temp[0]
temp[2] = temp[1] * temp[0] temp[2] = temp[1] * temp[0]
// Update for short periodics // Update for short periodics
@@ -28,11 +28,11 @@ data class OrbitalData(
val meanan: Double, val meanan: Double,
val catnum: Int, val catnum: Int,
val bstar: Double, val bstar: Double,
val xincl: Double = Math.toRadians(incl), val xincl: Double = incl * DEG2RAD,
val xnodeo: Double = Math.toRadians(raan), val xnodeo: Double = raan * DEG2RAD,
val omegao: Double = Math.toRadians(argper), val omegao: Double = argper * DEG2RAD,
val xmo: Double = Math.toRadians(meanan), val xmo: Double = meanan * DEG2RAD,
val xno: Double = meanmo * Math.PI * 2.0 / 1440, val xno: Double = meanmo * TWO_PI / MIN_PER_DAY,
val isDeepspace: Boolean = meanmo < 6.4 val isDeepspace: Boolean = meanmo < 6.4
) { ) {
@@ -30,15 +30,13 @@ data class SatPos(
var theta: Double = 0.0, var theta: Double = 0.0,
var time: Long = 0L var time: Long = 0L
) { ) {
private val earthRadiusKm = 6378.16
private val speedOfLight = 2.99792458E8
fun getDownlinkFreq(freq: Long): Long { fun getDownlinkFreq(freq: Long): Long {
return (freq.toDouble() * (speedOfLight - distanceRate * 1000.0) / speedOfLight).toLong() return (freq.toDouble() * (SPEED_OF_LIGHT - distanceRate * 1000.0) / SPEED_OF_LIGHT).toLong()
} }
fun getUplinkFreq(freq: Long): Long { fun getUplinkFreq(freq: Long): Long {
return (freq.toDouble() * (speedOfLight + distanceRate * 1000.0) / speedOfLight).toLong() return (freq.toDouble() * (SPEED_OF_LIGHT + distanceRate * 1000.0) / SPEED_OF_LIGHT).toLong()
} }
fun getOrbitalVelocity(): Double { fun getOrbitalVelocity(): Double {
@@ -50,7 +48,7 @@ data class SatPos(
fun getRangeCircle(): List<GeoPos> { fun getRangeCircle(): List<GeoPos> {
val positions = mutableListOf<GeoPos>() val positions = mutableListOf<GeoPos>()
val beta = acos(earthRadiusKm / (earthRadiusKm + altitude)) val beta = acos(EARTH_RADIUS / (EARTH_RADIUS + altitude))
var tempAzimuth = 0 var tempAzimuth = 0
while (tempAzimuth < 360) { while (tempAzimuth < 360) {
val azimuth = tempAzimuth / 360.0 * 2.0 * Math.PI val azimuth = tempAzimuth / 360.0 * 2.0 * Math.PI
@@ -81,6 +79,6 @@ data class SatPos(
} }
fun getRangeCircleRadiusKm(): Double { fun getRangeCircleRadiusKm(): Double {
return earthRadiusKm * acos(earthRadiusKm / (earthRadiusKm + altitude)) return EARTH_RADIUS * acos(EARTH_RADIUS / (EARTH_RADIUS + altitude))
} }
} }
@@ -19,36 +19,40 @@ package com.rtbishop.look4sat.domain.predict
import kotlin.math.* import kotlin.math.*
const val PI = 3.141592653589793
const val DEG2RAD = 0.017453292519943295
const val RAD2DEG = 57.29577951308232
const val EARTH_RADIUS = 6378.137
const val EPSILON = 1.0E-12
const val FLAT_FACTOR = 3.35281066474748E-3
const val J3_HARMONIC = -2.53881E-6
const val MIN_PER_DAY = 1.44E3
const val SEC_PER_DAY = 8.6400E4
const val SPEED_OF_LIGHT = 2.99792458E8
const val TWO_PI = PI * 2.0
const val TWO_THIRDS = 2.0 / 3.0
const val CK2 = 5.413079E-4
const val CK4 = 6.209887E-7
const val XKE = 7.43669161E-2
abstract class Satellite(val data: OrbitalData) { abstract class Satellite(val data: OrbitalData) {
private val flatFactor = 3.35281066474748E-3
private val deg2Rad = 1.745329251994330E-2
private val secPerDay = 8.6400E4
private val minPerDay = 1.44E3
private val epsilon = 1.0E-12
private val position = Vector4() private val position = Vector4()
private val velocity = Vector4() private val velocity = Vector4()
private var gsPosTheta = 0.0 private var gsPosTheta = 0.0
private var perigee = 0.0 private var perigee = 0.0
val orbitalPeriod = 24 * 60 / data.meanmo val orbitalPeriod = 24 * 60 / data.meanmo
val earthRadius = 6378.137
val j3Harmonic = -2.53881E-6
val twoPi = Math.PI * 2.0
val twoThirds = 2.0 / 3.0
val xke = 7.43669161E-2
val ck2 = 5.413079E-4
val ck4 = 6.209887E-7
var qoms24 = 0.0 var qoms24 = 0.0
var s4 = 0.0 var s4 = 0.0
internal fun willBeSeen(pos: GeoPos): Boolean { internal fun willBeSeen(pos: GeoPos): Boolean {
return if (data.meanmo < 1e-8) false return if (data.meanmo < 1e-8) false
else { else {
val sma = 331.25 * exp(ln(1440.0 / data.meanmo) * (2.0 / 3.0)) val sma = 331.25 * exp(ln(MIN_PER_DAY / data.meanmo) * (2.0 / 3.0))
val apogee = sma * (1.0 + data.eccn) - earthRadius val apogee = sma * (1.0 + data.eccn) - EARTH_RADIUS
var lin = data.incl var lin = data.incl
if (lin >= 90.0) lin = 180.0 - lin if (lin >= 90.0) lin = 180.0 - lin
acos(earthRadius / (apogee + earthRadius)) + lin * deg2Rad > abs(pos.latitude * deg2Rad) acos(EARTH_RADIUS / (apogee + EARTH_RADIUS)) + lin * DEG2RAD > abs(pos.latitude * DEG2RAD)
} }
} }
@@ -58,7 +62,7 @@ abstract class Satellite(val data: OrbitalData) {
val julUTC = calcCurrentDaynum(time) + 2444238.5 val julUTC = calcCurrentDaynum(time) + 2444238.5
// Convert satellite's epoch time to Julian and calculate time since epoch in minutes // Convert satellite's epoch time to Julian and calculate time since epoch in minutes
val julEpoch = juliandDateOfEpoch(data.epoch) val julEpoch = juliandDateOfEpoch(data.epoch)
val tsince = (julUTC - julEpoch) * minPerDay val tsince = (julUTC - julEpoch) * MIN_PER_DAY
calculateSDP4orSGP4(tsince) calculateSDP4orSGP4(tsince)
// Scale position and velocity vectors to km and km/sec // Scale position and velocity vectors to km and km/sec
convertSatState(position, velocity) convertSatState(position, velocity)
@@ -103,8 +107,8 @@ abstract class Satellite(val data: OrbitalData) {
// Converts the sat position and velocity vectors to km and km/sec // Converts the sat position and velocity vectors to km and km/sec
private fun convertSatState(pos: Vector4, vel: Vector4) { private fun convertSatState(pos: Vector4, vel: Vector4) {
scaleVector(earthRadius, pos) scaleVector(EARTH_RADIUS, pos)
scaleVector(earthRadius * minPerDay / secPerDay, vel) scaleVector(EARTH_RADIUS * MIN_PER_DAY / SEC_PER_DAY, vel)
} }
// Calculates the topocentric coordinates of the object with ECI pos and vel at time // Calculates the topocentric coordinates of the object with ECI pos and vel at time
@@ -134,16 +138,16 @@ abstract class Satellite(val data: OrbitalData) {
velocityVector.z - obsVel.z velocityVector.z - obsVel.z
) )
magnitude(range) magnitude(range)
val sinLat = sin(deg2Rad * gsPos.latitude) val sinLat = sin(DEG2RAD * gsPos.latitude)
val cosLat = cos(deg2Rad * gsPos.latitude) val cosLat = cos(DEG2RAD * gsPos.latitude)
val sinTheta = sin(gsPosTheta) val sinTheta = sin(gsPosTheta)
val cosTheta = cos(gsPosTheta) val cosTheta = cos(gsPosTheta)
val topS = sinLat * cosTheta * range.x + sinLat * sinTheta * range.y - cosLat * range.z val topS = sinLat * cosTheta * range.x + sinLat * sinTheta * range.y - cosLat * range.z
val topE = -sinTheta * range.x + cosTheta * range.y val topE = -sinTheta * range.x + cosTheta * range.y
val topZ = cosLat * cosTheta * range.x + cosLat * sinTheta * range.y + sinLat * range.z val topZ = cosLat * cosTheta * range.x + cosLat * sinTheta * range.y + sinLat * range.z
var azim = atan(-topE / topS) var azim = atan(-topE / topS)
if (topS > 0.0) azim += Math.PI if (topS > 0.0) azim += PI
if (azim < 0.0) azim += twoPi if (azim < 0.0) azim += TWO_PI
satPos.azimuth = azim satPos.azimuth = azim
satPos.elevation = asin(topZ / range.w) satPos.elevation = asin(topZ / range.w)
satPos.distance = range.w satPos.distance = range.w
@@ -158,14 +162,14 @@ abstract class Satellite(val data: OrbitalData) {
obsVel: Vector4 obsVel: Vector4
) { ) {
val mFactor = 7.292115E-5 val mFactor = 7.292115E-5
gsPosTheta = mod2PI(thetaGJD(time) + deg2Rad * gsPos.longitude) gsPosTheta = mod2PI(thetaGJD(time) + DEG2RAD * gsPos.longitude)
val c = val c =
invert(sqrt(1.0 + flatFactor * (flatFactor - 2) * sqr(sin(deg2Rad * gsPos.latitude)))) invert(sqrt(1.0 + FLAT_FACTOR * (FLAT_FACTOR - 2) * sqr(sin(DEG2RAD * gsPos.latitude))))
val sq = sqr(1.0 - flatFactor) * c val sq = sqr(1.0 - FLAT_FACTOR) * c
val achcp = (earthRadius * c) * cos(deg2Rad * gsPos.latitude) val achcp = (EARTH_RADIUS * c) * cos(DEG2RAD * gsPos.latitude)
obsPos.setXYZ( obsPos.setXYZ(
achcp * cos(gsPosTheta), achcp * sin(gsPosTheta), achcp * cos(gsPosTheta), achcp * sin(gsPosTheta),
(earthRadius * sq) * sin(deg2Rad * gsPos.latitude) (EARTH_RADIUS * sq) * sin(DEG2RAD * gsPos.latitude)
) )
obsVel.setXYZ(-mFactor * obsPos.y, mFactor * obsPos.x, 0.0) obsVel.setXYZ(-mFactor * obsPos.y, mFactor * obsPos.x, 0.0)
magnitude(obsPos) magnitude(obsPos)
@@ -181,7 +185,7 @@ abstract class Satellite(val data: OrbitalData) {
satPos.theta = atan2(position.y, position.x) satPos.theta = atan2(position.y, position.x)
satPos.longitude = mod2PI(satPos.theta - thetaGJD(time)) satPos.longitude = mod2PI(satPos.theta - thetaGJD(time))
val r = sqrt(sqr(position.x) + sqr(position.y)) val r = sqrt(sqr(position.x) + sqr(position.y))
val e2 = flatFactor * (2.0 - flatFactor) val e2 = FLAT_FACTOR * (2.0 - FLAT_FACTOR)
satPos.latitude = atan2(position.z, r) satPos.latitude = atan2(position.z, r)
var phi: Double var phi: Double
var c: Double var c: Double
@@ -190,13 +194,13 @@ abstract class Satellite(val data: OrbitalData) {
do { do {
phi = satPos.latitude phi = satPos.latitude
c = invert(sqrt(1.0 - e2 * sqr(sin(phi)))) c = invert(sqrt(1.0 - e2 * sqr(sin(phi))))
satPos.latitude = atan2(position.z + earthRadius * c * e2 * sin(phi), r) satPos.latitude = atan2(position.z + EARTH_RADIUS * c * e2 * sin(phi), r)
converged = abs(satPos.latitude - phi) < epsilon converged = abs(satPos.latitude - phi) < EPSILON
} while (i++ < 10 && !converged) } while (i++ < 10 && !converged)
satPos.altitude = r / cos(satPos.latitude) - earthRadius * c satPos.altitude = r / cos(satPos.latitude) - EARTH_RADIUS * c
var temp = satPos.latitude var temp = satPos.latitude
if (temp > Math.PI / 2.0) { if (temp > Math.PI / 2.0) {
temp -= twoPi temp -= TWO_PI
satPos.latitude = temp satPos.latitude = temp
} }
} }
@@ -258,9 +262,9 @@ abstract class Satellite(val data: OrbitalData) {
// Calculates the modulus of 2 * PI // Calculates the modulus of 2 * PI
internal fun mod2PI(value: Double): Double { internal fun mod2PI(value: Double): Double {
var retVal = value var retVal = value
val i = (retVal / twoPi).toInt() val i = (retVal / TWO_PI).toInt()
retVal -= i * twoPi retVal -= i * TWO_PI
if (retVal < 0.0) retVal += twoPi if (retVal < 0.0) retVal += TWO_PI
return retVal return retVal
} }
@@ -276,7 +280,7 @@ abstract class Satellite(val data: OrbitalData) {
temp[5] = axn * temp[8] temp[5] = axn * temp[8]
temp[6] = ayn * temp[7] temp[6] = ayn * temp[7]
val epw = (capu - temp[4] + temp[3] - temp[2]) / (1.0 - temp[5] - temp[6]) + temp[2] val epw = (capu - temp[4] + temp[3] - temp[2]) / (1.0 - temp[5] - temp[6]) + temp[2]
if (abs(epw - temp[2]) <= epsilon) converged = true else temp[2] = epw if (abs(epw - temp[2]) <= EPSILON) converged = true else temp[2] = epw
} while (i++ < 10 && !converged) } while (i++ < 10 && !converged)
} }
@@ -292,8 +296,8 @@ abstract class Satellite(val data: OrbitalData) {
qoms24 = 1.880279E-09 qoms24 = 1.880279E-09
if (perigee < 156.0) { if (perigee < 156.0) {
s4 = if (perigee <= 98.0) 20.0 else perigee - 78.0 s4 = if (perigee <= 98.0) 20.0 else perigee - 78.0
qoms24 = ((120 - s4) / earthRadius).pow(4.0) qoms24 = ((120 - s4) / EARTH_RADIUS).pow(4.0)
s4 = s4 / earthRadius + 1.0 s4 = s4 / EARTH_RADIUS + 1.0
} }
} }
@@ -314,9 +318,9 @@ abstract class Satellite(val data: OrbitalData) {
private fun modulus(arg1: Double): Double { private fun modulus(arg1: Double): Double {
var returnValue = arg1 var returnValue = arg1
val i = floor(returnValue / secPerDay).toInt() val i = floor(returnValue / SEC_PER_DAY).toInt()
returnValue -= i * secPerDay returnValue -= i * SEC_PER_DAY
if (returnValue < 0.0) returnValue += secPerDay if (returnValue < 0.0) returnValue += SEC_PER_DAY
return returnValue return returnValue
} }
@@ -332,7 +336,7 @@ abstract class Satellite(val data: OrbitalData) {
val aJD = theJD - ut val aJD = theJD - ut
val tu = (aJD - 2451545.0) / 36525.0 val tu = (aJD - 2451545.0) / 36525.0
var gmst = 24110.54841 + tu * (8640184.812866 + tu * (0.093104 - tu * 6.2E-6)) var gmst = 24110.54841 + tu * (8640184.812866 + tu * (0.093104 - tu * 6.2E-6))
gmst = modulus(gmst + secPerDay * earthRotPerSidDay * ut) gmst = modulus(gmst + SEC_PER_DAY * earthRotPerSidDay * ut)
return twoPi * gmst / secPerDay return TWO_PI * gmst / SEC_PER_DAY
} }
} }
@@ -17,23 +17,29 @@
*/ */
package com.rtbishop.look4sat.domain.predict package com.rtbishop.look4sat.domain.predict
import com.rtbishop.look4sat.domain.ISatelliteManager
import com.rtbishop.look4sat.domain.model.SatRadio import com.rtbishop.look4sat.domain.model.SatRadio
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class Predictor(private val predictorDispatcher: CoroutineDispatcher) { class SatelliteManager(private val defaultDispatcher: CoroutineDispatcher) : ISatelliteManager {
private val _calculatedPasses = MutableSharedFlow<List<SatPass>>(replay = 1) private val _calculatedPasses = MutableSharedFlow<List<SatPass>>(replay = 1)
val calculatedPasses: SharedFlow<List<SatPass>> = _calculatedPasses override val calculatedPasses: SharedFlow<List<SatPass>> = _calculatedPasses
suspend fun getSatPos(sat: Satellite, pos: GeoPos, time: Long): SatPos { override suspend fun getPosition(sat: Satellite, pos: GeoPos, time: Long): SatPos {
return withContext(predictorDispatcher) { sat.getPosition(pos, time) } return withContext(defaultDispatcher) { sat.getPosition(pos, time) }
} }
suspend fun getSatTrack(sat: Satellite, pos: GeoPos, start: Long, end: Long): List<SatPos> { override suspend fun getTrack(
return withContext(predictorDispatcher) { sat: Satellite,
pos: GeoPos,
start: Long,
end: Long
): List<SatPos> {
return withContext(defaultDispatcher) {
val positions = mutableListOf<SatPos>() val positions = mutableListOf<SatPos>()
var currentTime = start var currentTime = start
while (currentTime < end) { while (currentTime < end) {
@@ -44,8 +50,13 @@ class Predictor(private val predictorDispatcher: CoroutineDispatcher) {
} }
} }
suspend fun processRadios(sat: Satellite, pos: GeoPos, radios: List<SatRadio>, time: Long): List<SatRadio> { override suspend fun processRadios(
return withContext(predictorDispatcher) { sat: Satellite,
pos: GeoPos,
radios: List<SatRadio>,
time: Long
): List<SatRadio> {
return withContext(defaultDispatcher) {
val satPos = sat.getPosition(pos, time) val satPos = sat.getPosition(pos, time)
val copiedList = radios.map { it.copy() } val copiedList = radios.map { it.copy() }
copiedList.forEach { transmitter -> copiedList.forEach { transmitter ->
@@ -60,8 +71,8 @@ class Predictor(private val predictorDispatcher: CoroutineDispatcher) {
} }
} }
suspend fun processPasses(passList: List<SatPass>, time: Long): List<SatPass> { override suspend fun processPasses(passList: List<SatPass>, time: Long): List<SatPass> {
return withContext(predictorDispatcher) { return withContext(defaultDispatcher) {
passList.forEach { pass -> passList.forEach { pass ->
if (!pass.isDeepSpace) { if (!pass.isDeepSpace) {
val timeStart = pass.aosTime val timeStart = pass.aosTime
@@ -76,17 +87,17 @@ class Predictor(private val predictorDispatcher: CoroutineDispatcher) {
} }
} }
suspend fun forceCalculation( override suspend fun calculatePasses(
satList: List<Satellite>, satList: List<Satellite>,
pos: GeoPos, pos: GeoPos,
time: Long, time: Long,
hoursAhead: Int = 8, hoursAhead: Int,
minElevation: Double = 16.0 minElevation: Double
) { ) {
if (satList.isEmpty()) { if (satList.isEmpty()) {
_calculatedPasses.emit(emptyList()) _calculatedPasses.emit(emptyList())
} else { } else {
withContext(predictorDispatcher) { withContext(defaultDispatcher) {
val allPasses = mutableListOf<SatPass>() val allPasses = mutableListOf<SatPass>()
satList.forEach { satellite -> satList.forEach { satellite ->
allPasses.addAll(satellite.getPasses(pos, time, hoursAhead)) allPasses.addAll(satellite.getPasses(pos, time, hoursAhead))
@@ -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.data package com.rtbishop.look4sat.utility
import com.rtbishop.look4sat.domain.model.SatRadio import com.rtbishop.look4sat.domain.model.SatRadio
import com.rtbishop.look4sat.domain.predict.OrbitalData import com.rtbishop.look4sat.domain.predict.OrbitalData
@@ -26,42 +26,44 @@ import org.json.JSONObject
import java.io.InputStream import java.io.InputStream
import kotlin.math.pow import kotlin.math.pow
class DataParser(private val parserDispatcher: CoroutineDispatcher) { class DataParser(private val defaultDispatcher: CoroutineDispatcher) {
suspend fun parseCSVStream(csvStream: InputStream): List<OrbitalData> = withContext(parserDispatcher) { suspend fun parseCSVStream(csvStream: InputStream): List<OrbitalData> =
val parsedItems = mutableListOf<OrbitalData>() withContext(defaultDispatcher) {
csvStream.bufferedReader().useLines { lines -> val parsedItems = mutableListOf<OrbitalData>()
lines.forEachIndexed { index, line -> csvStream.bufferedReader().useLines { lines ->
if (index != 0) { lines.forEachIndexed { index, line ->
val values = line.split(",") if (index != 0) {
parseCSV(values)?.let { tle -> parsedItems.add(tle) } val values = line.split(",")
parseCSV(values)?.let { tle -> parsedItems.add(tle) }
}
} }
} }
return@withContext parsedItems
} }
return@withContext parsedItems
}
suspend fun parseTLEStream(tleStream: InputStream): List<OrbitalData> = withContext(parserDispatcher) { suspend fun parseTLEStream(tleStream: InputStream): List<OrbitalData> =
val tleStrings = mutableListOf(String(), String(), String()) withContext(defaultDispatcher) {
val parsedItems = mutableListOf<OrbitalData>() val tleStrings = mutableListOf(String(), String(), String())
var lineIndex = 0 val parsedItems = mutableListOf<OrbitalData>()
tleStream.bufferedReader().forEachLine { line -> var lineIndex = 0
tleStrings[lineIndex] = line tleStream.bufferedReader().forEachLine { line ->
if (lineIndex < 2) { tleStrings[lineIndex] = line
lineIndex++ if (lineIndex < 2) {
} else { lineIndex++
val isLineOneValid = tleStrings[1].substring(0, 1) == "1" } else {
val isLineTwoValid = tleStrings[2].substring(0, 1) == "2" val isLineOneValid = tleStrings[1].substring(0, 1) == "1"
if (!isLineOneValid && !isLineTwoValid) return@forEachLine val isLineTwoValid = tleStrings[2].substring(0, 1) == "2"
parseTLE(tleStrings)?.let { tle -> parsedItems.add(tle) } if (!isLineOneValid && !isLineTwoValid) return@forEachLine
lineIndex = 0 parseTLE(tleStrings)?.let { tle -> parsedItems.add(tle) }
lineIndex = 0
}
} }
return@withContext parsedItems
} }
return@withContext parsedItems
}
suspend fun parseJSONStream(jsonStream: InputStream): List<SatRadio> { suspend fun parseJSONStream(jsonStream: InputStream): List<SatRadio> {
return withContext(parserDispatcher) { return withContext(defaultDispatcher) {
val parsedItems = mutableListOf<SatRadio>() val parsedItems = mutableListOf<SatRadio>()
try { try {
val jsonArray = JSONArray(jsonStream.bufferedReader().readText()) val jsonArray = JSONArray(jsonStream.bufferedReader().readText())
@@ -15,16 +15,18 @@
* 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 package com.rtbishop.look4sat.utility
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.channels.SocketChannel import java.nio.channels.SocketChannel
class DataReporter(reporterDispatcher: CoroutineDispatcher) { class DataReporter(private val reporterScope: CoroutineScope) {
private val reporterScope = CoroutineScope(reporterDispatcher)
private var rotationSocketChannel: SocketChannel? = null private var rotationSocketChannel: SocketChannel? = null
private var rotationReporting: Job? = null private var rotationReporting: Job? = null
@@ -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 package com.rtbishop.look4sat.utility
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.net.Socket import java.net.Socket
@@ -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 package com.rtbishop.look4sat.utility
import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.domain.predict.GeoPos
@@ -17,7 +17,7 @@
*/ */
package com.rtbishop.look4sat package com.rtbishop.look4sat
import com.rtbishop.look4sat.data.DataParser import com.rtbishop.look4sat.utility.DataParser
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
@@ -17,7 +17,7 @@
*/ */
package com.rtbishop.look4sat package com.rtbishop.look4sat
import com.rtbishop.look4sat.domain.QthConverter import com.rtbishop.look4sat.utility.QthConverter
import org.junit.Test import org.junit.Test
class QthConverterTest { class QthConverterTest {
+2 -2
View File
@@ -6,8 +6,8 @@ buildscript {
} }
plugins { plugins {
id "com.android.application" version '7.1.2' apply false id "com.android.application" version "7.1.2" apply false
id "com.android.library" version '7.1.2' apply false id "com.android.library" version "7.1.2" apply false
id "org.jetbrains.kotlin.android" version "1.6.10" apply false id "org.jetbrains.kotlin.android" version "1.6.10" apply false
} }
+1 -1
View File
@@ -14,4 +14,4 @@ dependencyResolutionManagement {
} }
rootProject.name = "Look4Sat" rootProject.name = "Look4Sat"
include ":app" include ":app"
include ":data" include ":base"