Slightly changed classes structure for readability

This commit is contained in:
Arty Bishop committed 2025-12-27 15:01:45 +00:00
1 parent 19d0c1b108
commit 549a915b24
40 files changed
+309 -381

No files matched your search

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rtbishop.look4sat.data.database.dao
package com.rtbishop.look4sat.data.database
import androidx.room.Dao
import androidx.room.Insert
@@ -23,17 +23,16 @@ import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import com.rtbishop.look4sat.data.database.entity.SatEntry
import com.rtbishop.look4sat.data.database.entity.SatItem
import com.rtbishop.look4sat.data.database.entity.SatRadio
import com.rtbishop.look4sat.domain.model.SatItem
@Dao
interface StorageDao {
interface Look4SatDao {
@Query("SELECT COUNT(*) FROM entries")
suspend fun getEntriesTotal(): Int
//@Transaction
@Query("SELECT catnum, name FROM entries ORDER BY name ASC")
@Query("SELECT catnum, name, 0 as isSelected FROM entries ORDER BY name ASC")
suspend fun getEntriesList(): List<SatItem>
@Transaction
@@ -19,13 +19,12 @@ package com.rtbishop.look4sat.data.database
import androidx.room.Database
import androidx.room.RoomDatabase
import com.rtbishop.look4sat.data.database.dao.StorageDao
import com.rtbishop.look4sat.data.database.entity.SatEntry
import com.rtbishop.look4sat.data.database.entity.SatRadio
@Database(entities = [SatEntry::class, SatRadio::class], version = 1, exportSchema = false)
abstract class MainDatabase : RoomDatabase() {
abstract fun storageDao(): StorageDao
abstract class Look4SatDb : RoomDatabase() {
abstract fun look4SatDao(): Look4SatDao
}
//val MIGRATION_1_2 = object : Migration(1, 2) {
@@ -17,9 +17,18 @@
*/
package com.rtbishop.look4sat.data.database.entity
import androidx.room.Embedded
import androidx.room.Entity
import com.rtbishop.look4sat.domain.predict.OrbitalData
@Entity(tableName = "entries", primaryKeys = ["catnum"])
data class SatEntry(@Embedded val data: OrbitalData, var comment: String? = null)
data class SatEntry(
val name: String,
val epoch: Double,
val meanmo: Double,
val eccn: Double,
val incl: Double,
val raan: Double,
val argper: Double,
val meanan: Double,
val catnum: Int,
val bstar: Double
)
@@ -1,20 +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.data.database.entity
data class SatItem(val catnum: Int, val name: String)
@@ -32,6 +32,5 @@ data class SatRadio(
var uplinkHigh: Long?,
val uplinkMode: String?,
val isInverted: Boolean,
val catnum: Int?,
var comment: String? = null
val catnum: Int?
)
@@ -18,15 +18,14 @@
package com.rtbishop.look4sat.data.repository
import com.rtbishop.look4sat.domain.model.DatabaseState
import com.rtbishop.look4sat.domain.model.SatEntry
import com.rtbishop.look4sat.domain.model.SatRadio
import com.rtbishop.look4sat.domain.predict.OrbitalData
import com.rtbishop.look4sat.domain.repository.IDatabaseRepo
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
import com.rtbishop.look4sat.domain.source.IFileSource
import com.rtbishop.look4sat.domain.source.ILocalSource
import com.rtbishop.look4sat.domain.source.IRemoteSource
import com.rtbishop.look4sat.domain.source.Sources
import com.rtbishop.look4sat.domain.utility.DataParser
import java.io.InputStream
import java.util.zip.ZipInputStream
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.async
@@ -35,32 +34,31 @@ import kotlinx.coroutines.withContext
class DatabaseRepo(
private val dispatcher: CoroutineDispatcher,
private val dataParser: DataParser,
private val fileSource: IFileSource,
private val localSource: ILocalSource,
private val remoteSource: IRemoteSource,
private val settingsRepo: ISettingsRepo
) : IDatabaseRepo {
override suspend fun updateFromFile(uri: String) = withContext(dispatcher) {
val importedSatellites = fileSource.getFileStream(uri)?.let { importSatellites(it) }
val importedSatellites = remoteSource.getFileStream(uri)?.let { dataParser.parseTLEStream(it) }
importedSatellites?.let { localSource.insertEntries(it) }
setUpdateSuccessful(System.currentTimeMillis())
}
override suspend fun updateFromRemote() = withContext(dispatcher) {
val importedEntries = mutableListOf<SatEntry>()
val importedEntries = mutableListOf<OrbitalData>()
val importedRadios = mutableListOf<SatRadio>()
// fetch
val jobsMap = settingsRepo.satelliteSourcesMap.mapValues { async { remoteSource.getRemoteStream(it.value) } }
val jobRadios = async { remoteSource.getRemoteStream(settingsRepo.radioSourceUrl) }
val jobsMap = Sources.satelliteDataUrls.mapValues { async { remoteSource.getNetworkStream(it.value) } }
val jobRadios = async { remoteSource.getNetworkStream(Sources.radioDataUrl) }
// parse
jobsMap.mapValues { job -> job.value.await() }.forEach { entry ->
entry.value?.let { stream ->
when (val type = entry.key) {
"Amsat", "R4UAB" -> {
// parse tle stream
val satellites = importSatellites(stream)
val catnums = satellites.map { it.data.catnum }
val satellites = dataParser.parseTLEStream(stream)
val catnums = satellites.map { it.catnum }
settingsRepo.setSatelliteTypeIds(type, catnums)
importedEntries.addAll(satellites)
}
@@ -68,17 +66,16 @@ class DatabaseRepo(
"McCants", "Classified" -> {
// unzip and parse tle stream
val unzipped = ZipInputStream(stream).apply { nextEntry }
val satellites = importSatellites(unzipped)
val catnums = satellites.map { it.data.catnum }
val satellites = dataParser.parseTLEStream(unzipped)
val catnums = satellites.map { it.catnum }
settingsRepo.setSatelliteTypeIds(type, catnums)
importedEntries.addAll(satellites)
}
else -> {
// parse csv stream
val parsed = dataParser.parseCSVStream(stream)
val satellites = parsed.map { data -> SatEntry(data) }
val catnums = satellites.map { it.data.catnum }
val satellites = dataParser.parseCSVStream(stream)
val catnums = satellites.map { it.catnum }
settingsRepo.setSatelliteTypeIds(type, catnums)
importedEntries.addAll(satellites)
}
@@ -103,8 +100,4 @@ class DatabaseRepo(
val numberOfSatellites = localSource.getEntriesTotal()
settingsRepo.updateDatabaseState(DatabaseState(numberOfRadios, numberOfSatellites, timestamp))
}
private suspend fun importSatellites(stream: InputStream): List<SatEntry> {
return dataParser.parseTLEStream(stream).map { data -> SatEntry(data) }
}
}
@@ -19,9 +19,9 @@ package com.rtbishop.look4sat.data.repository
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 com.rtbishop.look4sat.domain.predict.OrbitalObject
import com.rtbishop.look4sat.domain.predict.OrbitalPass
import com.rtbishop.look4sat.domain.predict.OrbitalPos
import com.rtbishop.look4sat.domain.repository.ISatelliteRepo
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
import com.rtbishop.look4sat.domain.source.ILocalSource
@@ -39,11 +39,11 @@ class SatelliteRepo(
private val settingsRepo: ISettingsRepo
) : ISatelliteRepo {
private val _passes = MutableStateFlow<List<SatPass>>(emptyList())
override val passes: StateFlow<List<SatPass>> = _passes
private val _passes = MutableStateFlow<List<OrbitalPass>>(emptyList())
override val passes: StateFlow<List<OrbitalPass>> = _passes
private val _satellites = MutableStateFlow<List<Satellite>>(emptyList())
override val satellites: StateFlow<List<Satellite>> = _satellites
private val _satellites = MutableStateFlow<List<OrbitalObject>>(emptyList())
override val satellites: StateFlow<List<OrbitalObject>> = _satellites
override suspend fun getRadiosWithId(id: Int) = localStorage.getRadiosWithId(id)
@@ -55,13 +55,13 @@ class SatelliteRepo(
}
}
override suspend fun getPosition(sat: Satellite, pos: GeoPos, time: Long): SatPos {
override suspend fun getPosition(sat: OrbitalObject, pos: GeoPos, time: Long): OrbitalPos {
return withContext(dispatcher) { sat.getPosition(pos, time) }
}
override suspend fun getTrack(sat: Satellite, pos: GeoPos, start: Long, end: Long): List<SatPos> {
override suspend fun getTrack(sat: OrbitalObject, pos: GeoPos, start: Long, end: Long): List<OrbitalPos> {
return withContext(dispatcher) {
val positions = mutableListOf<SatPos>()
val positions = mutableListOf<OrbitalPos>()
var currentTime = start
while (currentTime < end) {
positions.add(sat.getPosition(pos, currentTime))
@@ -71,7 +71,12 @@ class SatelliteRepo(
}
}
override suspend fun getRadios(sat: Satellite, pos: GeoPos, radios: List<SatRadio>, time: Long): List<SatRadio> {
override suspend fun getRadios(
sat: OrbitalObject,
pos: GeoPos,
radios: List<SatRadio>,
time: Long
): List<SatRadio> {
return withContext(dispatcher) {
val satPos = sat.getPosition(pos, time)
val copiedList = radios.map { it.copy() }
@@ -83,7 +88,7 @@ class SatelliteRepo(
}
}
override suspend fun processPasses(passList: List<SatPass>, time: Long): List<SatPass> {
override suspend fun processPasses(passList: List<OrbitalPass>, time: Long): List<OrbitalPass> {
return withContext(dispatcher) {
passList.forEach { pass ->
if (!pass.isDeepSpace) {
@@ -102,7 +107,7 @@ class SatelliteRepo(
override suspend fun calculatePasses(time: Long, hoursAhead: Int, minElevation: Double, modes: List<String>) {
if (_satellites.value.isNotEmpty()) {
withContext(dispatcher) {
val newPasses = mutableListOf<SatPass>()
val newPasses = mutableListOf<OrbitalPass>()
val idsWithModes = localStorage.getIdsWithModes(modes)
_satellites.value.forEach { satellite ->
if (idsWithModes.isEmpty() || satellite.data.catnum in idsWithModes) {
@@ -116,8 +121,8 @@ class SatelliteRepo(
}
}
private fun Satellite.getPasses(pos: GeoPos, time: Long, hours: Int): List<SatPass> {
val passes = mutableListOf<SatPass>()
private fun OrbitalObject.getPasses(pos: GeoPos, time: Long, hours: Int): List<OrbitalPass> {
val passes = mutableListOf<OrbitalPass>()
val endDate = time + hours * 60L * 60L * 1000L
val quarterOrbitMin = (this.data.orbitalPeriod / 4.0).toInt()
var startDate = time
@@ -141,23 +146,23 @@ class SatelliteRepo(
return passes
}
private fun List<SatPass>.filter(time: Long, hoursAhead: Int, minElev: Double): List<SatPass> {
private fun List<OrbitalPass>.filter(time: Long, hoursAhead: Int, minElev: Double): List<OrbitalPass> {
val timeFuture = time + (hoursAhead * 60L * 60L * 1000L)
return this.filter { it.losTime > time }.filter { it.aosTime < timeFuture }
.filter { it.maxElevation > minElev }.sortedBy { it.aosTime }
}
private fun getGeoPass(sat: Satellite, pos: GeoPos, time: Long): SatPass {
private fun getGeoPass(sat: OrbitalObject, pos: GeoPos, time: Long): OrbitalPass {
val satPos = sat.getPosition(pos, time)
val aos = time - 24 * 60L * 60L * 1000L
val los = time + 24 * 60L * 60L * 1000L // val tca = (aos + los) / 2
val az = satPos.azimuth.toDegrees().round(1)
val elev = satPos.elevation.toDegrees().round(1)
val alt = satPos.altitude
return SatPass(aos, az, los, az, alt.toInt(), elev, sat)
return OrbitalPass(aos, az, los, az, alt.toInt(), elev, sat)
}
private fun getLeoPass(sat: Satellite, pos: GeoPos, time: Long, rewind: Boolean): SatPass {
private fun getLeoPass(sat: OrbitalObject, pos: GeoPos, time: Long, rewind: Boolean): OrbitalPass {
val quarterOrbitMin = (sat.data.orbitalPeriod / 4.0).toInt()
var calendarTimeMillis = time
var elevation: Double
@@ -229,6 +234,6 @@ class SatelliteRepo(
val los = satPos.time // val tca = (aos + los) / 2
val losAz = satPos.azimuth.toDegrees().round(1)
val elev = maxElevation.toDegrees().round(1)
return SatPass(aos, aosAz, los, losAz, alt.toInt(), elev, sat)
return OrbitalPass(aos, aosAz, los, losAz, alt.toInt(), elev, sat)
}
}
@@ -4,6 +4,7 @@ import com.rtbishop.look4sat.domain.model.SatItem
import com.rtbishop.look4sat.domain.repository.ISelectionRepo
import com.rtbishop.look4sat.domain.repository.ISettingsRepo
import com.rtbishop.look4sat.domain.source.ILocalSource
import com.rtbishop.look4sat.domain.source.Sources
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -30,7 +31,7 @@ class SelectionRepo(
override fun getCurrentType() = currentType.value
override fun getTypesList() = settingsRepo.satelliteSourcesMap.keys.sorted()
override fun getTypesList() = Sources.satelliteDataUrls.keys.sorted()
override suspend fun getEntriesFlow() = withContext(dispatcher) {
val selectedIds = settingsRepo.selectedIds.value
@@ -15,20 +15,20 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rtbishop.look4sat.data.source
package com.rtbishop.look4sat.data.repository
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import com.rtbishop.look4sat.domain.predict.RAD2DEG
import com.rtbishop.look4sat.domain.source.ISensorSource
import com.rtbishop.look4sat.domain.repository.ISensorsRepo
import kotlin.math.round
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlin.math.round
class SensorSource(private val sensorManager: SensorManager, private val sensor: Sensor?) :
SensorEventListener, ISensorSource {
class SensorsRepo(private val sensorManager: SensorManager, private val sensor: Sensor?) :
SensorEventListener, ISensorsRepo {
private val _orientation = MutableStateFlow(Pair(0f, 0f))
private val rotationMatrix = FloatArray(9)
@@ -1,16 +0,0 @@
package com.rtbishop.look4sat.data.source
import android.content.ContentResolver
import android.net.Uri
import com.rtbishop.look4sat.domain.source.IFileSource
import java.io.InputStream
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
class FileSource(
private val contentResolver: ContentResolver, private val dispatcher: CoroutineDispatcher
) : IFileSource {
override suspend fun getFileStream(uri: String): InputStream? {
return withContext(dispatcher) { contentResolver.openInputStream(Uri.parse(uri)) }
}
}
@@ -18,75 +18,75 @@
package com.rtbishop.look4sat.data.source
import com.rtbishop.look4sat.data.database.entity.SatEntry as FrameworkEntry
import com.rtbishop.look4sat.data.database.entity.SatItem as FrameworkItem
import com.rtbishop.look4sat.data.database.entity.SatRadio as FrameworkRadio
import com.rtbishop.look4sat.domain.model.SatEntry as DomainEntry
import com.rtbishop.look4sat.domain.model.SatItem as DomainItem
import com.rtbishop.look4sat.domain.model.SatRadio as DomainRadio
import com.rtbishop.look4sat.data.database.dao.StorageDao
import com.rtbishop.look4sat.data.database.Look4SatDao
import com.rtbishop.look4sat.domain.model.SatRadio
import com.rtbishop.look4sat.domain.predict.Satellite
import com.rtbishop.look4sat.domain.predict.OrbitalData
import com.rtbishop.look4sat.domain.predict.OrbitalObject
import com.rtbishop.look4sat.domain.source.ILocalSource
class LocalSource(private val storageDao: StorageDao) : ILocalSource {
class LocalSource(private val look4SatDao: Look4SatDao) : ILocalSource {
//region # Entries region
override suspend fun getEntriesTotal() = storageDao.getEntriesTotal()
override suspend fun getEntriesTotal() = look4SatDao.getEntriesTotal()
override suspend fun getEntriesList(): List<DomainItem> {
return storageDao.getEntriesList().toDomainItems()
}
override suspend fun getEntriesList() = look4SatDao.getEntriesList()
override suspend fun getEntriesWithIds(ids: List<Int>): List<Satellite> {
val selectedSatellites = mutableListOf<Satellite>()
override suspend fun getEntriesWithIds(ids: List<Int>): List<OrbitalObject> {
val selectedOrbitalObjects = mutableListOf<OrbitalObject>()
ids.chunked(999).forEach { idsPart ->
val entries = storageDao.getEntriesWithIds(idsPart)
selectedSatellites.addAll(entries.map { entry -> entry.data.getSatellite() })
val entries = look4SatDao.getEntriesWithIds(idsPart)
selectedOrbitalObjects.addAll(entries.toDomain().map { entry -> entry.getObject() })
}
return selectedSatellites
return selectedOrbitalObjects
}
override suspend fun insertEntries(entries: List<DomainEntry>) {
storageDao.insertEntries(entries.toFrameworkEntries())
}
override suspend fun insertEntries(entries: List<OrbitalData>) = look4SatDao.insertEntries(entries.toEntity())
override suspend fun deleteEntries() = storageDao.deleteEntries()
override suspend fun deleteEntries() = look4SatDao.deleteEntries()
override suspend fun getIdsWithModes(modes: List<String>) = storageDao.getIdsWithModes(modes)
override suspend fun getIdsWithModes(modes: List<String>) = look4SatDao.getIdsWithModes(modes)
private fun DomainEntry.toFramework() = FrameworkEntry(this.data, this.comment)
private fun FrameworkEntry.toDomain() = OrbitalData(
this.name, this.epoch, this.meanmo, this.eccn, this.incl,
this.raan, this.argper, this.meanan, this.catnum, this.bstar
)
private fun FrameworkItem.toDomain() = DomainItem(this.catnum, this.name, false)
private fun OrbitalData.toEntity() = FrameworkEntry(
this.name, this.epoch, this.meanmo, this.eccn, this.incl,
this.raan, this.argper, this.meanan, this.catnum, this.bstar
)
private fun List<DomainEntry>.toFrameworkEntries() = this.map { entry -> entry.toFramework() }
private fun List<OrbitalData>.toEntity() = this.map { item -> item.toEntity() }
private fun List<FrameworkItem>.toDomainItems() = this.map { item -> item.toDomain() }
private fun List<FrameworkEntry>.toDomain() = this.map { item -> item.toDomain() }
//endregion
//region # Radios region
override suspend fun getRadiosTotal() = storageDao.getRadiosTotal()
override suspend fun getRadiosTotal() = look4SatDao.getRadiosTotal()
override suspend fun getRadiosWithId(id: Int): List<SatRadio> {
return storageDao.getRadiosWithId(id).toDomainRadios()
return look4SatDao.getRadiosWithId(id).toDomainRadios()
}
override suspend fun insertRadios(radios: List<SatRadio>) {
storageDao.insertRadios(radios.toFrameworkRadios())
look4SatDao.insertRadios(radios.toFrameworkRadios())
}
override suspend fun deleteRadios() = storageDao.deleteRadios()
override suspend fun deleteRadios() = look4SatDao.deleteRadios()
private fun DomainRadio.toFramework() = FrameworkRadio(
this.uuid, this.info, this.isAlive, this.downlinkLow, this.downlinkHigh, this.downlinkMode,
this.uplinkLow, this.uplinkHigh, this.uplinkMode, this.isInverted, this.catnum, this.comment
this.uplinkLow, this.uplinkHigh, this.uplinkMode, this.isInverted, this.catnum
)
private fun FrameworkRadio.toDomain() = DomainRadio(
this.uuid, this.info, this.isAlive, this.downlinkLow, this.downlinkHigh, this.downlinkMode,
this.uplinkLow, this.uplinkHigh, this.uplinkMode, this.isInverted, this.catnum, this.comment
this.uplinkLow, this.uplinkHigh, this.uplinkMode, this.isInverted, this.catnum
)
private fun List<DomainRadio>.toFrameworkRadios() = this.map { radio -> radio.toFramework() }
@@ -17,6 +17,8 @@
*/
package com.rtbishop.look4sat.data.source
import android.content.ContentResolver
import android.net.Uri
import com.rtbishop.look4sat.domain.source.IRemoteSource
import java.io.InputStream
import kotlinx.coroutines.CoroutineDispatcher
@@ -25,15 +27,27 @@ import okhttp3.OkHttpClient
import okhttp3.Request
class RemoteSource(
private val httpClient: OkHttpClient, private val dispatcher: CoroutineDispatcher
private val contentResolver: ContentResolver,
private val httpClient: OkHttpClient,
private val dispatcher: CoroutineDispatcher
) : IRemoteSource {
override suspend fun getRemoteStream(url: String): InputStream? = withContext(dispatcher) {
override suspend fun getFileStream(uri: String): InputStream? = withContext(dispatcher) {
try {
val request = Request.Builder().url(url).build()
httpClient.newCall(request).execute().body?.byteStream()
val fileUri = Uri.parse(uri)
contentResolver.openInputStream(fileUri)
} catch (exception: Exception) {
println("RemoteSource fetching stream exception: $exception")
println("RemoteSource file stream exception: $exception")
null
}
}
override suspend fun getNetworkStream(url: String): InputStream? = withContext(dispatcher) {
try {
val networkRequest = Request.Builder().url(url).build()
httpClient.newCall(networkRequest).execute().body?.byteStream()
} catch (exception: Exception) {
println("RemoteSource network stream exception: $exception")
null
}
}