Destructively migrated to the new database

This commit is contained in:
Arty Bishop committed 2022-02-19 12:10:30 +00:00
1 parent 92eec290b1
commit 64b5010a44
45 files changed
+344 -864

No files matched your search

@@ -1,111 +1,111 @@
{ {
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 5, "version": 1,
"identityHash": "0b523e732e116bbdcccd8188922898e2", "identityHash": "834981e960b712254e56b633f3772115",
"entities": [ "entities": [
{ {
"tableName": "entries", "tableName": "entries",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`isSelected` INTEGER NOT NULL, `name` TEXT NOT NULL, `epoch` REAL NOT NULL, `meanmo` REAL NOT NULL, `eccn` REAL NOT NULL, `incl` REAL NOT NULL, `raan` REAL NOT NULL, `argper` REAL NOT NULL, `meanan` REAL NOT NULL, `catnum` INTEGER NOT NULL, `bstar` REAL NOT NULL, `xincl` REAL NOT NULL, `xnodeo` REAL NOT NULL, `omegao` REAL NOT NULL, `xmo` REAL NOT NULL, `xno` REAL NOT NULL, `isDeepspace` INTEGER NOT NULL, PRIMARY KEY(`catnum`))", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`comment` TEXT, `name` TEXT NOT NULL, `epoch` REAL NOT NULL, `meanmo` REAL NOT NULL, `eccn` REAL NOT NULL, `incl` REAL NOT NULL, `raan` REAL NOT NULL, `argper` REAL NOT NULL, `meanan` REAL NOT NULL, `catnum` INTEGER NOT NULL, `bstar` REAL NOT NULL, `xincl` REAL NOT NULL, `xnodeo` REAL NOT NULL, `omegao` REAL NOT NULL, `xmo` REAL NOT NULL, `xno` REAL NOT NULL, `isDeepspace` INTEGER NOT NULL, PRIMARY KEY(`catnum`))",
"fields": [ "fields": [
{ {
"fieldPath": "isSelected", "fieldPath": "comment",
"columnName": "isSelected", "columnName": "comment",
"affinity": "INTEGER", "affinity": "TEXT",
"notNull": true "notNull": false
}, },
{ {
"fieldPath": "tle.name", "fieldPath": "data.name",
"columnName": "name", "columnName": "name",
"affinity": "TEXT", "affinity": "TEXT",
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "tle.epoch", "fieldPath": "data.epoch",
"columnName": "epoch", "columnName": "epoch",
"affinity": "REAL", "affinity": "REAL",
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "tle.meanmo", "fieldPath": "data.meanmo",
"columnName": "meanmo", "columnName": "meanmo",
"affinity": "REAL", "affinity": "REAL",
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "tle.eccn", "fieldPath": "data.eccn",
"columnName": "eccn", "columnName": "eccn",
"affinity": "REAL", "affinity": "REAL",
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "tle.incl", "fieldPath": "data.incl",
"columnName": "incl", "columnName": "incl",
"affinity": "REAL", "affinity": "REAL",
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "tle.raan", "fieldPath": "data.raan",
"columnName": "raan", "columnName": "raan",
"affinity": "REAL", "affinity": "REAL",
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "tle.argper", "fieldPath": "data.argper",
"columnName": "argper", "columnName": "argper",
"affinity": "REAL", "affinity": "REAL",
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "tle.meanan", "fieldPath": "data.meanan",
"columnName": "meanan", "columnName": "meanan",
"affinity": "REAL", "affinity": "REAL",
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "tle.catnum", "fieldPath": "data.catnum",
"columnName": "catnum", "columnName": "catnum",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "tle.bstar", "fieldPath": "data.bstar",
"columnName": "bstar", "columnName": "bstar",
"affinity": "REAL", "affinity": "REAL",
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "tle.xincl", "fieldPath": "data.xincl",
"columnName": "xincl", "columnName": "xincl",
"affinity": "REAL", "affinity": "REAL",
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "tle.xnodeo", "fieldPath": "data.xnodeo",
"columnName": "xnodeo", "columnName": "xnodeo",
"affinity": "REAL", "affinity": "REAL",
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "tle.omegao", "fieldPath": "data.omegao",
"columnName": "omegao", "columnName": "omegao",
"affinity": "REAL", "affinity": "REAL",
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "tle.xmo", "fieldPath": "data.xmo",
"columnName": "xmo", "columnName": "xmo",
"affinity": "REAL", "affinity": "REAL",
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "tle.xno", "fieldPath": "data.xno",
"columnName": "xno", "columnName": "xno",
"affinity": "REAL", "affinity": "REAL",
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "tle.isDeepspace", "fieldPath": "data.isDeepspace",
"columnName": "isDeepspace", "columnName": "isDeepspace",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true "notNull": true
@@ -121,8 +121,8 @@
"foreignKeys": [] "foreignKeys": []
}, },
{ {
"tableName": "transmitters", "tableName": "radios",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `info` TEXT NOT NULL, `isAlive` INTEGER NOT NULL, `downlink` INTEGER, `uplink` INTEGER, `mode` TEXT, `isInverted` INTEGER NOT NULL, `catnum` INTEGER, PRIMARY KEY(`uuid`))", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `info` TEXT NOT NULL, `isAlive` INTEGER NOT NULL, `downlink` INTEGER, `uplink` INTEGER, `mode` TEXT, `isInverted` INTEGER NOT NULL, `catnum` INTEGER, `comment` TEXT, PRIMARY KEY(`uuid`))",
"fields": [ "fields": [
{ {
"fieldPath": "uuid", "fieldPath": "uuid",
@@ -171,6 +171,12 @@
"columnName": "catnum", "columnName": "catnum",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": false "notNull": false
},
{
"fieldPath": "comment",
"columnName": "comment",
"affinity": "TEXT",
"notNull": false
} }
], ],
"primaryKey": { "primaryKey": {
@@ -186,7 +192,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0b523e732e116bbdcccd8188922898e2')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '834981e960b712254e56b633f3772115')"
] ]
} }
} }
@@ -1,134 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "555494c464b5a29285eb5493cc8aa5f6",
"entities": [
{
"tableName": "entries",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tle` TEXT NOT NULL, `catNum` INTEGER NOT NULL, `name` TEXT NOT NULL, `isSelected` INTEGER NOT NULL, PRIMARY KEY(`catNum`))",
"fields": [
{
"fieldPath": "tle",
"columnName": "tle",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "catNum",
"columnName": "catNum",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isSelected",
"columnName": "isSelected",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"catNum"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "transmitters",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `info` TEXT NOT NULL, `isAlive` INTEGER NOT NULL, `downlink` INTEGER, `uplink` INTEGER, `mode` TEXT, `isInverted` INTEGER NOT NULL, `catNum` INTEGER, PRIMARY KEY(`uuid`))",
"fields": [
{
"fieldPath": "uuid",
"columnName": "uuid",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "info",
"columnName": "info",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isAlive",
"columnName": "isAlive",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "downlink",
"columnName": "downlink",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "uplink",
"columnName": "uplink",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mode",
"columnName": "mode",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isInverted",
"columnName": "isInverted",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "catNum",
"columnName": "catNum",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"uuid"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, PRIMARY KEY(`sourceUrl`))",
"fields": [
{
"fieldPath": "sourceUrl",
"columnName": "sourceUrl",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"sourceUrl"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '555494c464b5a29285eb5493cc8aa5f6')"
]
}
}
@@ -1,134 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "555494c464b5a29285eb5493cc8aa5f6",
"entities": [
{
"tableName": "entries",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tle` TEXT NOT NULL, `catNum` INTEGER NOT NULL, `name` TEXT NOT NULL, `isSelected` INTEGER NOT NULL, PRIMARY KEY(`catNum`))",
"fields": [
{
"fieldPath": "tle",
"columnName": "tle",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "catNum",
"columnName": "catNum",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isSelected",
"columnName": "isSelected",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"catNum"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "transmitters",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `info` TEXT NOT NULL, `isAlive` INTEGER NOT NULL, `downlink` INTEGER, `uplink` INTEGER, `mode` TEXT, `isInverted` INTEGER NOT NULL, `catNum` INTEGER, PRIMARY KEY(`uuid`))",
"fields": [
{
"fieldPath": "uuid",
"columnName": "uuid",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "info",
"columnName": "info",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isAlive",
"columnName": "isAlive",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "downlink",
"columnName": "downlink",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "uplink",
"columnName": "uplink",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mode",
"columnName": "mode",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isInverted",
"columnName": "isInverted",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "catNum",
"columnName": "catNum",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"uuid"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, PRIMARY KEY(`sourceUrl`))",
"fields": [
{
"fieldPath": "sourceUrl",
"columnName": "sourceUrl",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"sourceUrl"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '555494c464b5a29285eb5493cc8aa5f6')"
]
}
}
@@ -1,212 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "0f08defa24a78647801005e9277faac7",
"entities": [
{
"tableName": "entries",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`isSelected` INTEGER NOT NULL, `name` TEXT NOT NULL, `epoch` REAL NOT NULL, `meanmo` REAL NOT NULL, `eccn` REAL NOT NULL, `incl` REAL NOT NULL, `raan` REAL NOT NULL, `argper` REAL NOT NULL, `meanan` REAL NOT NULL, `catnum` INTEGER NOT NULL, `bstar` REAL NOT NULL, `xincl` REAL NOT NULL, `xnodeo` REAL NOT NULL, `omegao` REAL NOT NULL, `xmo` REAL NOT NULL, `xno` REAL NOT NULL, `isDeepspace` INTEGER NOT NULL, PRIMARY KEY(`catnum`))",
"fields": [
{
"fieldPath": "isSelected",
"columnName": "isSelected",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "tle.name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tle.epoch",
"columnName": "epoch",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.meanmo",
"columnName": "meanmo",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.eccn",
"columnName": "eccn",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.incl",
"columnName": "incl",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.raan",
"columnName": "raan",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.argper",
"columnName": "argper",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.meanan",
"columnName": "meanan",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.catnum",
"columnName": "catnum",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "tle.bstar",
"columnName": "bstar",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.xincl",
"columnName": "xincl",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.xnodeo",
"columnName": "xnodeo",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.omegao",
"columnName": "omegao",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.xmo",
"columnName": "xmo",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.xno",
"columnName": "xno",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.isDeepspace",
"columnName": "isDeepspace",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"catnum"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "transmitters",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `info` TEXT NOT NULL, `isAlive` INTEGER NOT NULL, `downlink` INTEGER, `uplink` INTEGER, `mode` TEXT, `isInverted` INTEGER NOT NULL, `catnum` INTEGER, PRIMARY KEY(`uuid`))",
"fields": [
{
"fieldPath": "uuid",
"columnName": "uuid",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "info",
"columnName": "info",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isAlive",
"columnName": "isAlive",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "downlink",
"columnName": "downlink",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "uplink",
"columnName": "uplink",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mode",
"columnName": "mode",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isInverted",
"columnName": "isInverted",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "catnum",
"columnName": "catnum",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"uuid"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, PRIMARY KEY(`sourceUrl`))",
"fields": [
{
"fieldPath": "sourceUrl",
"columnName": "sourceUrl",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"sourceUrl"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0f08defa24a78647801005e9277faac7')"
]
}
}
@@ -1,4 +1,4 @@
package com.rtbishop.bbctestapp package com.rtbishop.look4sat
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
@@ -6,16 +6,11 @@ import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest { class ExampleInstrumentedTest {
@Test @Test
fun useAppContext() { fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.rtbishop.bbctestapp", appContext.packageName) assertEquals("com.rtbishop.bbctestapp", appContext.packageName)
} }
@@ -19,29 +19,29 @@ package com.rtbishop.look4sat.framework
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.Transmitter as DomainTransmitter 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.Transmitter as FrameworkTransmitter import com.rtbishop.look4sat.framework.model.SatRadio as FrameworkRadio
fun DomainEntry.toFramework() = FrameworkEntry(this.tle, this.isSelected) fun DomainEntry.toFramework() = FrameworkEntry(this.data, this.comment)
fun DomainTransmitter.toFramework() = FrameworkTransmitter( fun DomainRadio.toFramework() = FrameworkRadio(
this.uuid, this.info, this.isAlive, this.downlink, this.uuid, this.info, this.isAlive, this.downlink, this.uplink,
this.uplink, this.mode, this.isInverted, this.catnum this.mode, this.isInverted, this.catnum, this.comment
) )
fun FrameworkItem.toDomain() = DomainItem(this.catnum, this.name, this.isSelected, this.modes) fun FrameworkItem.toDomain() = DomainItem(this.catnum, this.name, this.modes, false)
fun FrameworkTransmitter.toDomain() = DomainTransmitter( fun FrameworkRadio.toDomain() = DomainRadio(
this.uuid, this.info, this.isAlive, this.downlink, this.uuid, this.info, this.isAlive, this.downlink, this.uplink,
this.uplink, this.mode, this.isInverted, this.catnum this.mode, this.isInverted, this.catnum, this.comment
) )
fun List<DomainEntry>.toFrameworkEntries() = this.map { it.toFramework() } fun List<DomainEntry>.toFrameworkEntries() = this.map { it.toFramework() }
fun List<DomainTransmitter>.toFramework() = this.map { it.toFramework() } fun List<DomainRadio>.toFrameworkRadios() = this.map { it.toFramework() }
fun List<FrameworkItem>.toDomainItems() = this.map { it.toDomain() } fun List<FrameworkItem>.toDomainItems() = this.map { it.toDomain() }
fun List<FrameworkTransmitter>.toDomain() = this.map { it.toDomain() } fun List<FrameworkRadio>.toDomainRadios() = this.map { it.toDomain() }
@@ -61,12 +61,12 @@ class SettingsHandler @Inject constructor(private val prefs: SharedPreferences)
} }
} }
override fun saveSatelliteSelection(catnums: List<Int>) { override fun saveEntriesSelection(catnums: List<Int>) {
val stringList = catnums.map { catnum -> catnum.toString() } val stringList = catnums.map { catnum -> catnum.toString() }
prefs.edit { putStringSet(keySelection, stringList.toSet()) } prefs.edit { putStringSet(keySelection, stringList.toSet()) }
} }
override fun loadSatelliteSelection(): List<Int> { override fun loadEntriesSelection(): List<Int> {
val catnums = prefs.getStringSet(keySelection, emptySet())?.map { catnum -> catnum.toInt() } val catnums = prefs.getStringSet(keySelection, emptySet())?.map { catnum -> catnum.toInt() }
return catnums?.sorted() ?: emptyList() return catnums?.sorted() ?: emptyList()
} }
@@ -20,52 +20,77 @@ package com.rtbishop.look4sat.framework.local
import android.content.ContentResolver import android.content.ContentResolver
import android.net.Uri import android.net.Uri
import com.rtbishop.look4sat.data.ILocalSource import com.rtbishop.look4sat.data.ILocalSource
import com.rtbishop.look4sat.data.ISettingsHandler
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.Transmitter import com.rtbishop.look4sat.domain.model.SatRadio
import com.rtbishop.look4sat.domain.predict.Satellite import com.rtbishop.look4sat.domain.predict.Satellite
import com.rtbishop.look4sat.framework.toDomain
import com.rtbishop.look4sat.framework.toDomainItems import com.rtbishop.look4sat.framework.toDomainItems
import com.rtbishop.look4sat.framework.toFramework import com.rtbishop.look4sat.framework.toDomainRadios
import com.rtbishop.look4sat.framework.toFrameworkEntries import com.rtbishop.look4sat.framework.toFrameworkEntries
import com.rtbishop.look4sat.framework.toFrameworkRadios
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.InputStream import java.io.InputStream
class LocalSource( class LocalSource(
private val entriesDao: SatEntriesDao,
private val radiosDao: SatRadiosDao,
private val settings: ISettingsHandler,
private val resolver: ContentResolver, private val resolver: ContentResolver,
private val ioDispatcher: CoroutineDispatcher, private val ioDispatcher: CoroutineDispatcher
private val entriesDao: EntriesDao,
private val transmittersDao: TransmittersDao,
) : ILocalSource { ) : ILocalSource {
override suspend fun getAllSatellites(): List<SatItem> { override suspend fun getEntriesWithModes(): List<SatItem> {
return entriesDao.getAllSatellites().toDomainItems() val selectedCatnums = getEntriesSelection()
val entriesWithModes = entriesDao.getEntriesWithModes().toDomainItems()
entriesWithModes.forEach { entry -> entry.isSelected = entry.catnum in selectedCatnums }
return entriesWithModes
} }
override suspend fun getSelectedSatellites(catnums: List<Int>): List<Satellite> { override suspend fun getSelectedEntries(): List<Satellite> {
return entriesDao.getSelectedSatellites(catnums).map { entry -> entry.tle.createSat() } val selectedSatellites = mutableListOf<Satellite>()
getEntriesSelection().chunked(999).forEach { catnums ->
val entries = entriesDao.getSelectedEntries(catnums)
selectedSatellites.addAll(entries.map { entry -> entry.data.createSat() })
}
return selectedSatellites
} }
override suspend fun getTransmitters(catnum: Int): List<Transmitter> { override suspend fun getRadios(catnum: Int): List<SatRadio> {
return transmittersDao.getTransmitters(catnum).toDomain() return radiosDao.getRadios(catnum).toDomainRadios()
} }
@Suppress("BlockingMethodInNonBlockingContext")
override suspend fun getFileStream(uri: String): InputStream? { override suspend fun getFileStream(uri: String): InputStream? {
return withContext(ioDispatcher) { resolver.openInputStream(Uri.parse(uri)) } return withContext(ioDispatcher) { resolver.openInputStream(Uri.parse(uri)) }
} }
override suspend fun updateEntries(entries: List<SatEntry>) { override suspend fun insertEntries(entries: List<SatEntry>) {
entriesDao.insertEntries(entries.toFrameworkEntries()) entriesDao.insertEntries(entries.toFrameworkEntries())
} }
override suspend fun updateTransmitters(transmitters: List<Transmitter>) { override suspend fun insertRadios(radios: List<SatRadio>) {
transmittersDao.updateTransmitters(transmitters.toFramework()) radiosDao.insertRadios(radios.toFrameworkRadios())
} }
override suspend fun clearAllData() { override suspend fun clearAllData() {
entriesDao.deleteEntries() entriesDao.deleteEntries()
transmittersDao.deleteTransmitters() radiosDao.deleteRadios()
}
override suspend fun getDataSources(): List<String> {
return withContext(ioDispatcher) { settings.loadDataSources() }
}
override suspend fun setDataSources(sources: List<String>) {
withContext(ioDispatcher) { settings.saveDataSources(sources) }
}
override suspend fun getEntriesSelection(): List<Int> {
return withContext(ioDispatcher) { settings.loadEntriesSelection() }
}
override suspend fun setEntriesSelection(catnums: List<Int>) {
withContext(ioDispatcher) { settings.saveEntriesSelection(catnums) }
} }
} }
@@ -0,0 +1,31 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rtbishop.look4sat.framework.local
import androidx.room.Database
import androidx.room.RoomDatabase
import com.rtbishop.look4sat.framework.model.SatEntry
import com.rtbishop.look4sat.framework.model.SatRadio
@Database(entities = [SatEntry::class, SatRadio::class], version = 1, exportSchema = true)
abstract class Look4SatDb : RoomDatabase() {
abstract fun entriesDao(): SatEntriesDao
abstract fun radiosDao(): SatRadiosDao
}
@@ -22,23 +22,15 @@ import com.rtbishop.look4sat.framework.model.SatEntry
import com.rtbishop.look4sat.framework.model.SatItem import com.rtbishop.look4sat.framework.model.SatItem
@Dao @Dao
interface EntriesDao { interface SatEntriesDao {
@Transaction @Transaction
@Query("SELECT catnum, name, isSelected FROM entries ORDER BY name ASC") @Query("SELECT catnum, name FROM entries ORDER BY name ASC")
suspend fun getAllSatellites(): List<SatItem> suspend fun getEntriesWithModes(): List<SatItem>
@Transaction @Transaction
suspend fun getSelectedSatellites(catnums: List<Int>): List<SatEntry> {
val selectedSatellites = mutableListOf<SatEntry>()
catnums.chunked(999).forEach { chunkedList ->
selectedSatellites.addAll(getSelectedSatellitesChunked(chunkedList))
}
return selectedSatellites
}
@Query("SELECT * FROM entries WHERE catnum IN (:catnums)") @Query("SELECT * FROM entries WHERE catnum IN (:catnums)")
suspend fun getSelectedSatellitesChunked(catnums: List<Int>): List<SatEntry> suspend fun getSelectedEntries(catnums: List<Int>): List<SatEntry>
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertEntries(entries: List<SatEntry>) suspend fun insertEntries(entries: List<SatEntry>)
@@ -17,24 +17,21 @@
*/ */
package com.rtbishop.look4sat.framework.local package com.rtbishop.look4sat.framework.local
import androidx.room.* import androidx.room.Dao
import com.rtbishop.look4sat.framework.model.Transmitter import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.rtbishop.look4sat.framework.model.SatRadio
@Dao @Dao
interface TransmittersDao { interface SatRadiosDao {
@Query("SELECT * FROM transmitters WHERE catnum = :catnum AND isAlive = 1") @Query("SELECT * FROM radios WHERE catnum = :catnum AND isAlive = 1")
suspend fun getTransmitters(catnum: Int): List<Transmitter> suspend fun getRadios(catnum: Int): List<SatRadio>
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertTransmitters(transmitters: List<Transmitter>) suspend fun insertRadios(radios: List<SatRadio>)
@Transaction @Query("DELETE FROM radios")
suspend fun updateTransmitters(transmitters: List<Transmitter>) { suspend fun deleteRadios()
deleteTransmitters()
insertTransmitters(transmitters)
}
@Query("DELETE FROM transmitters")
suspend fun deleteTransmitters()
} }
@@ -1,67 +0,0 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rtbishop.look4sat.framework.local
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.rtbishop.look4sat.framework.model.SatEntry
import com.rtbishop.look4sat.framework.model.Transmitter
@Database(entities = [SatEntry::class, Transmitter::class], version = 5, exportSchema = true)
abstract class SatelliteDb : RoomDatabase() {
abstract fun entriesDao(): EntriesDao
abstract fun transmittersDao(): TransmittersDao
}
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE trans_backup (uuid TEXT NOT NULL, info TEXT NOT NULL, isAlive INTEGER NOT NULL, downlink INTEGER, uplink INTEGER, mode TEXT, isInverted INTEGER NOT NULL, catNum INTEGER, PRIMARY KEY(uuid))")
database.execSQL("INSERT INTO trans_backup (uuid, info, isAlive, downlink, uplink, mode, isInverted, catNum) SELECT uuid, info, isAlive, downlink, uplink, mode, isInverted, catNum FROM transmitters")
database.execSQL("DROP TABLE transmitters")
database.execSQL("ALTER TABLE trans_backup RENAME TO transmitters")
}
}
val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE sources (sourceUrl TEXT NOT NULL, PRIMARY KEY(sourceUrl))")
}
}
val MIGRATION_3_4 = object : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE entries_backup (isSelected INTEGER NOT NULL, name TEXT NOT NULL, epoch REAL NOT NULL, meanmo REAL NOT NULL, eccn REAL NOT NULL, incl REAL NOT NULL, raan REAL NOT NULL, argper REAL NOT NULL, meanan REAL NOT NULL, catnum INTEGER NOT NULL, bstar REAL NOT NULL, xincl REAL NOT NULL, xnodeo REAL NOT NULL, omegao REAL NOT NULL, xmo REAL NOT NULL, xno REAL NOT NULL, isDeepspace INTEGER NOT NULL, PRIMARY KEY(catnum))")
database.execSQL("INSERT INTO entries_backup (isSelected, name, epoch, meanmo, eccn, incl, raan, argper, meanan, catnum, bstar, xincl, xnodeo, omegao, xmo, xno, isDeepspace) SELECT isSelected, 'name', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, catNum, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0 FROM entries")
database.execSQL("DROP TABLE entries")
database.execSQL("ALTER TABLE entries_backup RENAME TO entries")
database.execSQL("CREATE TABLE trans_backup (uuid TEXT NOT NULL, info TEXT NOT NULL, isAlive INTEGER NOT NULL, downlink INTEGER, uplink INTEGER, mode TEXT, isInverted INTEGER NOT NULL, catnum INTEGER, PRIMARY KEY(uuid))")
database.execSQL("INSERT INTO trans_backup (uuid, info, isAlive, downlink, uplink, mode, isInverted, catnum) SELECT uuid, info, isAlive, downlink, uplink, mode, isInverted, catNum FROM transmitters")
database.execSQL("DROP TABLE transmitters")
database.execSQL("ALTER TABLE trans_backup RENAME TO transmitters")
}
}
val MIGRATION_4_5 = object : Migration(4, 5) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE sources")
}
}
@@ -17,8 +17,4 @@
*/ */
package com.rtbishop.look4sat.framework.model package com.rtbishop.look4sat.framework.model
import androidx.room.Entity data class DataSource(var url: String = String())
import androidx.room.PrimaryKey
@Entity(tableName = "sources")
data class Source(@PrimaryKey var sourceUrl: String = String())
@@ -19,11 +19,7 @@ package com.rtbishop.look4sat.framework.model
import androidx.room.Embedded import androidx.room.Embedded
import androidx.room.Entity import androidx.room.Entity
import com.rtbishop.look4sat.domain.predict.TLE import com.rtbishop.look4sat.domain.predict.OrbitalData
@Entity(tableName = "entries", primaryKeys = ["catnum"]) @Entity(tableName = "entries", primaryKeys = ["catnum"])
data class SatEntry( data class SatEntry(@Embedded val data: OrbitalData, var comment: String? = null)
@Embedded
val tle: TLE,
var isSelected: Boolean = false
)
@@ -22,10 +22,9 @@ import androidx.room.Relation
data class SatItem( data class SatItem(
val catnum: Int, val catnum: Int,
val name: String, val name: String,
var isSelected: Boolean = false,
@Relation( @Relation(
parentColumn = "catnum", parentColumn = "catnum",
entity = Transmitter::class, entity = SatRadio::class,
entityColumn = "catnum", entityColumn = "catnum",
projection = ["mode"] projection = ["mode"]
) )
@@ -20,8 +20,8 @@ package com.rtbishop.look4sat.framework.model
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@Entity(tableName = "transmitters") @Entity(tableName = "radios")
data class Transmitter( data class SatRadio(
@PrimaryKey val uuid: String, @PrimaryKey val uuid: String,
val info: String, val info: String,
val isAlive: Boolean, val isAlive: Boolean,
@@ -29,5 +29,6 @@ data class Transmitter(
var uplink: Long?, var uplink: Long?,
val mode: String?, val mode: String?,
val isInverted: Boolean, val isInverted: Boolean,
val catnum: Int? val catnum: Int?,
var comment: String? = null
) )
@@ -8,8 +8,7 @@ import java.net.URL
class RemoteSource(private val ioDispatcher: CoroutineDispatcher) : IRemoteSource { class RemoteSource(private val ioDispatcher: CoroutineDispatcher) : IRemoteSource {
@Suppress("BlockingMethodInNonBlockingContext") override suspend fun getFileStream(url: String): InputStream? {
override suspend fun fetchFileStream(url: String): InputStream? {
return withContext(ioDispatcher) { URL(url).openStream() } return withContext(ioDispatcher) { URL(url).openStream() }
} }
} }
@@ -29,7 +29,8 @@ import com.rtbishop.look4sat.domain.ILocationHandler
import com.rtbishop.look4sat.domain.predict.Predictor import com.rtbishop.look4sat.domain.predict.Predictor
import com.rtbishop.look4sat.framework.LocationHandler import com.rtbishop.look4sat.framework.LocationHandler
import com.rtbishop.look4sat.framework.SettingsHandler import com.rtbishop.look4sat.framework.SettingsHandler
import com.rtbishop.look4sat.framework.local.* import com.rtbishop.look4sat.framework.local.LocalSource
import com.rtbishop.look4sat.framework.local.Look4SatDb
import com.rtbishop.look4sat.framework.remote.RemoteSource import com.rtbishop.look4sat.framework.remote.RemoteSource
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@@ -53,15 +54,14 @@ object CoreModule {
@IoDispatcher ioDispatcher: CoroutineDispatcher, @IoDispatcher ioDispatcher: CoroutineDispatcher,
@DefaultDispatcher defaultDispatcher: CoroutineDispatcher @DefaultDispatcher defaultDispatcher: CoroutineDispatcher
): IDataRepository { ): IDataRepository {
val db = Room.databaseBuilder(context, SatelliteDb::class.java, "SatelliteDb") val db = Room.databaseBuilder(context, Look4SatDb::class.java, "Look4SatDb")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
.fallbackToDestructiveMigration().build() .fallbackToDestructiveMigration().build()
val dataParser = DataParser(defaultDispatcher) val dataParser = DataParser(defaultDispatcher)
val resolver = context.contentResolver val resolver = context.contentResolver
val localSource = LocalSource(resolver, ioDispatcher, db.entriesDao(), db.transmittersDao()) val local = LocalSource(db.entriesDao(), db.radiosDao(), settings, resolver, ioDispatcher)
val remoteSource = RemoteSource(ioDispatcher) val remote = RemoteSource(ioDispatcher)
val repositoryScope = CoroutineScope(SupervisorJob()) val repositoryScope = CoroutineScope(SupervisorJob())
return DataRepository(dataParser, settings, localSource, remoteSource, repositoryScope) return DataRepository(dataParser, local, remote, repositoryScope)
} }
@Provides @Provides
@@ -22,7 +22,7 @@ class MainViewModel @Inject constructor(
timeRef: Long = System.currentTimeMillis() timeRef: Long = System.currentTimeMillis()
) { ) {
viewModelScope.launch { viewModelScope.launch {
val satellites = dataRepository.getSelectedSatellites() val satellites = dataRepository.getSelectedEntries()
val stationPos = preferences.loadStationPosition() val stationPos = preferences.loadStationPosition()
predictor.forceCalculation(satellites, stationPos, timeRef, hoursAhead, minElevation) predictor.forceCalculation(satellites, stationPos, timeRef, hoursAhead, minElevation)
} }
@@ -34,7 +34,7 @@ class EntriesViewModel @Inject constructor(
private val transModes = MutableLiveData(preferences.loadModesSelection()) private val transModes = MutableLiveData(preferences.loadModesSelection())
private val currentQuery = MutableLiveData(String()) private val currentQuery = MutableLiveData(String())
private val itemsFromRepo = liveData { emit(repository.getAllSatellites()) } as MutableLiveData private val itemsFromRepo = liveData { emit(repository.getEntriesWithModes()) } as MutableLiveData
private val itemsWithModes = transModes.switchMap { modes -> private val itemsWithModes = transModes.switchMap { modes ->
itemsFromRepo.map { items -> filterByModes(items, modes) } itemsFromRepo.map { items -> filterByModes(items, modes) }
} }
@@ -68,7 +68,7 @@ class EntriesViewModel @Inject constructor(
fun saveSelection() { fun saveSelection() {
itemsFromRepo.value?.let { itemsAll -> itemsFromRepo.value?.let { itemsAll ->
val filteredItems = itemsAll.filter { item -> item.isSelected } val filteredItems = itemsAll.filter { item -> item.isSelected }
repository.updatesSelection(filteredItems.map { item -> item.catnum }) repository.setEntriesSelection(filteredItems.map { item -> item.catnum })
} }
} }
@@ -130,7 +130,7 @@ class MapFragment : Fragment(R.layout.fragment_map) {
textLabelBackgroundColor = Color.TRANSPARENT textLabelBackgroundColor = Color.TRANSPARENT
textLabelForegroundColor = textLabelForegroundColor =
ContextCompat.getColor(requireContext(), R.color.themeAccent) ContextCompat.getColor(requireContext(), R.color.themeAccent)
setTextIcon(it.key.params.name) setTextIcon(it.key.data.name)
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER) setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
try { try {
position = GeoPoint(it.value.latitude, it.value.longitude) position = GeoPoint(it.value.latitude, it.value.longitude)
@@ -76,13 +76,13 @@ class MapViewModel @Inject constructor(
fun selectDefaultSatellite(catnum: Int) { fun selectDefaultSatellite(catnum: Int) {
viewModelScope.launch { viewModelScope.launch {
dataRepository.getSelectedSatellites().also { satellites -> dataRepository.getSelectedEntries().also { satellites ->
if (satellites.isNotEmpty()) { if (satellites.isNotEmpty()) {
allSatList = satellites allSatList = satellites
if (catnum == -1) { if (catnum == -1) {
selectSatellite(satellites.first()) selectSatellite(satellites.first())
} else { } else {
satellites.find { it.params.catnum == catnum }?.let { selectSatellite(it) } satellites.find { it.data.catnum == catnum }?.let { selectSatellite(it) }
} }
} }
} }
@@ -164,7 +164,7 @@ class MapViewModel @Inject constructor(
val osmPos = GeoPos(osmLat, osmLon) val osmPos = GeoPos(osmLat, osmLon)
val qthLoc = QthConverter.positionToQth(osmPos.latitude, osmPos.longitude) ?: "-- --" val qthLoc = QthConverter.positionToQth(osmPos.latitude, osmPos.longitude) ?: "-- --"
val satData = MapData( val satData = MapData(
satellite, satellite.params.catnum, satellite.params.name, satPos.range, satellite, satellite.data.catnum, satellite.data.name, satPos.range,
satPos.altitude, satPos.getOrbitalVelocity(), qthLoc, osmPos satPos.altitude, satPos.getOrbitalVelocity(), qthLoc, osmPos
) )
_satData.postValue(satData) _satData.postValue(satData)
@@ -65,7 +65,7 @@ class PassesViewModel @Inject constructor(
viewModelScope.launch { viewModelScope.launch {
_passes.postValue(DataState.Loading) _passes.postValue(DataState.Loading)
passesProcessing?.cancelAndJoin() passesProcessing?.cancelAndJoin()
val satellites = repository.getSelectedSatellites() val satellites = repository.getSelectedEntries()
val stationPos = settings.loadStationPosition() val stationPos = settings.loadStationPosition()
predictor.forceCalculation(satellites, stationPos, timeRef, hoursAhead, minElevation) predictor.forceCalculation(satellites, stationPos, timeRef, hoursAhead, minElevation)
} }
@@ -53,7 +53,7 @@ class RadarFragment : Fragment(R.layout.fragment_radar) {
private fun setupViews() { private fun setupViews() {
val context = requireContext() val context = requireContext()
val adapter = TransmittersAdapter() val adapter = RadiosAdapter()
val layoutManager = LinearLayoutManager(context) val layoutManager = LinearLayoutManager(context)
val itemDecoration = DividerItemDecoration(context, layoutManager.orientation) val itemDecoration = DividerItemDecoration(context, layoutManager.orientation)
binding.run { binding.run {
@@ -68,7 +68,7 @@ class RadarFragment : Fragment(R.layout.fragment_radar) {
} }
} }
private fun setupObservers(transmittersAdapter: TransmittersAdapter) { private fun setupObservers(radiosAdapter: RadiosAdapter) {
viewModel.getPass(args.catNum, args.aosTime).observe(viewLifecycleOwner) { pass -> viewModel.getPass(args.catNum, args.aosTime).observe(viewLifecycleOwner) { pass ->
radarView = RadarView(requireContext()).apply { radarView = RadarView(requireContext()).apply {
setShowAim(preferences.getUseCompass()) setShowAim(preferences.getUseCompass())
@@ -82,7 +82,7 @@ class RadarFragment : Fragment(R.layout.fragment_radar) {
} }
viewModel.transmitters.observe(viewLifecycleOwner) { list -> viewModel.transmitters.observe(viewLifecycleOwner) { list ->
if (list.isNotEmpty()) { if (list.isNotEmpty()) {
transmittersAdapter.submitList(list) radiosAdapter.submitList(list)
binding.radarRecyclerMsg.text = getString(R.string.trans_data) binding.radarRecyclerMsg.text = getString(R.string.trans_data)
} else { } else {
binding.radarRecyclerMsg.text = getString(R.string.trans_no_data) binding.radarRecyclerMsg.text = getString(R.string.trans_no_data)
@@ -22,7 +22,7 @@ import androidx.lifecycle.*
import com.rtbishop.look4sat.data.ISettingsHandler import com.rtbishop.look4sat.data.ISettingsHandler
import com.rtbishop.look4sat.domain.DataReporter import com.rtbishop.look4sat.domain.DataReporter
import com.rtbishop.look4sat.domain.IDataRepository import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.domain.model.Transmitter 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.Predictor
import com.rtbishop.look4sat.domain.predict.SatPass import com.rtbishop.look4sat.domain.predict.SatPass
@@ -47,10 +47,10 @@ class RadarViewModel @Inject constructor(
private val stationPos = preferences.loadStationPosition() private val stationPos = preferences.loadStationPosition()
private val _passData = MutableLiveData<RadarData>() private val _passData = MutableLiveData<RadarData>()
private val _transmitters = MutableLiveData<List<Transmitter>>() private val _transmitters = MutableLiveData<List<SatRadio>>()
private val _orientation = MutableLiveData<Triple<Float, Float, Float>>() private val _orientation = MutableLiveData<Triple<Float, Float, Float>>()
val radarData: LiveData<RadarData> = _passData val radarData: LiveData<RadarData> = _passData
val transmitters: LiveData<List<Transmitter>> = _transmitters val transmitters: LiveData<List<SatRadio>> = _transmitters
val orientation: LiveData<Triple<Float, Float, Float>> = _orientation val orientation: LiveData<Triple<Float, Float, Float>> = _orientation
fun getPass(catNum: Int, aosTime: Long) = liveData { fun getPass(catNum: Int, aosTime: Long) = liveData {
@@ -107,7 +107,7 @@ class RadarViewModel @Inject constructor(
private fun processTransmitters(pass: SatPass) { private fun processTransmitters(pass: SatPass) {
viewModelScope.launch { viewModelScope.launch {
val transmitters = dataRepository.getTransmitters(pass.catNum) val transmitters = dataRepository.getRadios(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 = predictor.processRadios(pass.satellite, stationPos, transmitters, time)
@@ -24,24 +24,24 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.ItemTransBinding import com.rtbishop.look4sat.databinding.ItemTransBinding
import com.rtbishop.look4sat.domain.model.Transmitter import com.rtbishop.look4sat.domain.model.SatRadio
import java.util.* import java.util.*
class TransmittersAdapter : RecyclerView.Adapter<TransmittersAdapter.TransHolder>() { class RadiosAdapter : RecyclerView.Adapter<RadiosAdapter.TransHolder>() {
private val diffCallback = object : DiffUtil.ItemCallback<Transmitter>() { private val diffCallback = object : DiffUtil.ItemCallback<SatRadio>() {
override fun areItemsTheSame(oldItem: Transmitter, newItem: Transmitter): Boolean { override fun areItemsTheSame(oldItem: SatRadio, newItem: SatRadio): Boolean {
return oldItem.uuid == newItem.uuid return oldItem.uuid == newItem.uuid
} }
override fun areContentsTheSame(oldItem: Transmitter, newItem: Transmitter): Boolean { override fun areContentsTheSame(oldItem: SatRadio, newItem: SatRadio): Boolean {
return oldItem.downlink == newItem.downlink return oldItem.downlink == newItem.downlink
} }
} }
private val differ = AsyncListDiffer(this, diffCallback) private val differ = AsyncListDiffer(this, diffCallback)
fun submitList(transmitters: List<Transmitter>) { fun submitList(radios: List<SatRadio>) {
differ.submitList(transmitters) differ.submitList(radios)
} }
override fun getItemCount() = differ.currentList.size override fun getItemCount() = differ.currentList.size
@@ -65,10 +65,10 @@ class TransmittersAdapter : RecyclerView.Adapter<TransmittersAdapter.TransHolder
private val formatLinkNull = itemView.context.getString(R.string.trans_no_link) private val formatLinkNull = itemView.context.getString(R.string.trans_no_link)
private val isInverted = itemView.context.getString(R.string.trans_inverted) private val isInverted = itemView.context.getString(R.string.trans_inverted)
fun bind(transmitter: Transmitter) { fun bind(radio: SatRadio) {
binding.transDesc.text = transmitter.info binding.transDesc.text = radio.info
transmitter.downlink.let { downlink -> radio.downlink.let { downlink ->
if (downlink != null) { if (downlink != null) {
val downlinkFreq = downlink / divider val downlinkFreq = downlink / divider
binding.transDownlink.text = binding.transDownlink.text =
@@ -78,7 +78,7 @@ class TransmittersAdapter : RecyclerView.Adapter<TransmittersAdapter.TransHolder
} }
} }
transmitter.uplink.let { uplink -> radio.uplink.let { uplink ->
if (uplink != null) { if (uplink != null) {
val uplinkFreq = uplink / divider val uplinkFreq = uplink / divider
binding.transUplink.text = String.format(Locale.ENGLISH, formatLink, uplinkFreq) binding.transUplink.text = String.format(Locale.ENGLISH, formatLink, uplinkFreq)
@@ -87,13 +87,13 @@ class TransmittersAdapter : RecyclerView.Adapter<TransmittersAdapter.TransHolder
} }
} }
if (transmitter.mode != null) { if (radio.mode != null) {
binding.transMode.text = String.format(mode, transmitter.mode) binding.transMode.text = String.format(mode, radio.mode)
} else { } else {
binding.transMode.text = String.format(mode, strNo) binding.transMode.text = String.format(mode, strNo)
} }
if (transmitter.isInverted) { if (radio.isInverted) {
binding.transInverted.text = String.format(isInverted, strYes) binding.transInverted.text = String.format(isInverted, strYes)
} else { } else {
binding.transInverted.text = String.format(isInverted, strNo) binding.transInverted.text = String.format(isInverted, strNo)
@@ -15,29 +15,29 @@
* 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.presentation.sourcesScreen package com.rtbishop.look4sat.presentation.settingsScreen
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.rtbishop.look4sat.databinding.ItemSourceBinding import com.rtbishop.look4sat.databinding.ItemSourceBinding
import com.rtbishop.look4sat.framework.model.Source import com.rtbishop.look4sat.framework.model.DataSource
class SourcesAdapter(private val sources: MutableList<Source> = mutableListOf()) : class SourcesAdapter(private val sources: MutableList<DataSource> = mutableListOf()) :
RecyclerView.Adapter<SourcesAdapter.TleSourceHolder>() { RecyclerView.Adapter<SourcesAdapter.TleSourceHolder>() {
fun getSources(): List<Source> { fun getSources(): List<DataSource> {
return sources.filter { it.sourceUrl.contains("https://") } return sources.filter { source -> source.url.contains("https://") }
} }
fun setSources(list: List<Source>) { fun setSources(list: List<DataSource>) {
sources.clear() sources.clear()
sources.addAll(list) sources.addAll(list)
} }
fun addSource() { fun addSource() {
sources.add(Source()) sources.add(DataSource())
notifyItemInserted(itemCount - 1) notifyItemInserted(itemCount - 1)
} }
@@ -58,9 +58,9 @@ class SourcesAdapter(private val sources: MutableList<Source> = mutableListOf())
inner class TleSourceHolder(private val binding: ItemSourceBinding) : inner class TleSourceHolder(private val binding: ItemSourceBinding) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
fun bind(source: Source) { fun bind(source: DataSource) {
binding.sourceUrl.setText(source.sourceUrl) binding.sourceUrl.setText(source.url)
binding.sourceUrl.doOnTextChanged { txt, _, _, _ -> source.sourceUrl = txt.toString() } binding.sourceUrl.doOnTextChanged { txt, _, _, _ -> source.url = txt.toString() }
binding.sourceBtn.setOnClickListener { binding.sourceBtn.setOnClickListener {
sources.remove(source) sources.remove(source)
notifyItemRemoved(adapterPosition) notifyItemRemoved(adapterPosition)
@@ -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.presentation.sourcesScreen package com.rtbishop.look4sat.presentation.settingsScreen
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@@ -23,11 +23,11 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import androidx.appcompat.app.AppCompatDialogFragment import androidx.appcompat.app.AppCompatDialogFragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.ISettingsHandler
import com.rtbishop.look4sat.databinding.DialogSourcesBinding import com.rtbishop.look4sat.databinding.DialogSourcesBinding
import com.rtbishop.look4sat.framework.model.Source import com.rtbishop.look4sat.domain.IDataRepository
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
import javax.inject.Inject import javax.inject.Inject
@@ -36,35 +36,37 @@ import javax.inject.Inject
class SourcesDialog : AppCompatDialogFragment() { class SourcesDialog : AppCompatDialogFragment() {
@Inject @Inject
lateinit var settings: ISettingsHandler lateinit var repository: IDataRepository
private lateinit var binding: DialogSourcesBinding
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_sources, group, false) binding = DialogSourcesBinding.inflate(inflater, group, false)
return binding.root
} }
override fun onViewCreated(view: View, state: Bundle?) { override fun onViewCreated(view: View, state: Bundle?) {
super.onViewCreated(view, state) super.onViewCreated(view, state)
val sources = settings.loadDataSources()
val adapter = SourcesAdapter().apply { setSources(sources.map { Source(it) }) }
val layoutManager = LinearLayoutManager(requireContext())
DialogSourcesBinding.bind(view).apply {
dialog?.window?.setLayout( dialog?.window?.setLayout(
WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT WindowManager.LayoutParams.WRAP_CONTENT
) )
lifecycleScope.launchWhenResumed {
val adapter = SourcesAdapter()
val layoutManager = LinearLayoutManager(requireContext())
adapter.setSources(repository.getDataSources().map { url -> DataSource(url) })
binding.run {
sourcesRecycler.apply { sourcesRecycler.apply {
setHasFixedSize(true) setHasFixedSize(true)
this.adapter = adapter this.adapter = adapter
this.layoutManager = layoutManager this.layoutManager = layoutManager
} }
sourcesBtnAdd.setOnClickListener { sourcesBtnAdd.setOnClickListener { adapter.addSource() }
adapter.addSource() sourcesBtnNeg.setOnClickListener { dismiss() }
}
sourcesBtnPos.setOnClickListener { sourcesBtnPos.setOnClickListener {
setNavResult("sources", adapter.getSources().map { it.sourceUrl }) setNavResult("sources", adapter.getSources().map { source -> source.url })
dismiss() dismiss()
} }
sourcesBtnNeg.setOnClickListener { dismiss() } }
} }
} }
} }
@@ -52,7 +52,7 @@
</fragment> </fragment>
<dialog <dialog
android:id="@+id/nav_sources" android:id="@+id/nav_sources"
android:name="com.rtbishop.look4sat.presentation.sourcesScreen.SourcesDialog" android:name="com.rtbishop.look4sat.presentation.settingsScreen.SourcesDialog"
tools:layout="@layout/dialog_sources" /> tools:layout="@layout/dialog_sources" />
<action <action
android:id="@+id/action_global_entriesFragment" android:id="@+id/action_global_entriesFragment"
@@ -1,14 +1,10 @@
package com.rtbishop.bbctestapp package com.rtbishop.look4sat
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest { class ExampleUnitTest {
@Test @Test
fun addition_isCorrect() { fun addition_isCorrect() {
assertEquals(4, 2 + 2) assertEquals(4, 2 + 2)
@@ -21,69 +21,59 @@ import com.rtbishop.look4sat.domain.DataParser
import com.rtbishop.look4sat.domain.IDataRepository 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.domain.model.SatItem
import com.rtbishop.look4sat.domain.predict.Satellite
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
import kotlin.system.measureTimeMillis
class DataRepository( class DataRepository(
private val dataParser: DataParser, private val dataParser: DataParser,
private val settings: ISettingsHandler,
private val localSource: ILocalSource, private val localSource: ILocalSource,
private val remoteSource: IRemoteSource, private val remoteSource: IRemoteSource,
private val repositoryScope: CoroutineScope private val repositoryScope: CoroutineScope
) : IDataRepository { ) : IDataRepository {
private val exceptionHandler = CoroutineExceptionHandler { _, exception -> private val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("DataRepository: handled $exception")
_updateState.value = DataState.Error(exception.message) _updateState.value = DataState.Error(exception.message)
} }
private val updateStateDelay = 875L
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 suspend fun getAllSatellites(): List<SatItem> { override fun setUpdateStateHandled() {
val selection = settings.loadSatelliteSelection() _updateState.value = DataState.Handled
val satellites = localSource.getAllSatellites()
satellites.forEach { satItem -> satItem.isSelected = satItem.catnum in selection }
return satellites
} }
override suspend fun getSelectedSatellites(): List<Satellite> { override suspend fun getEntriesWithModes() = localSource.getEntriesWithModes()
val selection = settings.loadSatelliteSelection()
return localSource.getSelectedSatellites(selection)
}
override suspend fun getTransmitters(catnum: Int) = localSource.getTransmitters(catnum) override suspend fun getSelectedEntries() = localSource.getSelectedEntries()
override suspend fun getRadios(catnum: Int) = localSource.getRadios(catnum)
override fun updateFromFile(uri: String) { override fun updateFromFile(uri: String) {
repositoryScope.launch(exceptionHandler) { repositoryScope.launch(exceptionHandler) {
_updateState.value = DataState.Loading _updateState.value = DataState.Loading
val updateTimeMillis = measureTimeMillis {
localSource.getFileStream(uri)?.let { fileStream -> localSource.getFileStream(uri)?.let { fileStream ->
localSource.updateEntries(importSatellites(fileStream)) localSource.insertEntries(importSatellites(fileStream))
delay(updateStateDelay)
} }
} _updateState.value = DataState.Success(0L)
_updateState.value = DataState.Success(updateTimeMillis)
} }
} }
override fun updateFromWeb(sources: List<String>) { override fun updateFromWeb(sources: List<String>) {
_updateState.value = DataState.Loading _updateState.value = DataState.Loading
repositoryScope.launch(exceptionHandler) { repositoryScope.launch(exceptionHandler) {
settings.saveDataSources(sources) localSource.setDataSources(sources)
} }
repositoryScope.launch(exceptionHandler) { repositoryScope.launch(exceptionHandler) {
val updateTimeMillis = measureTimeMillis {
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>()
sources.forEach { jobsMap[it] = async { remoteSource.fetchFileStream(it) } } sources.forEach { jobsMap[it] = async { remoteSource.getFileStream(it) } }
jobsMap.forEach { streamsMap[it.key] = it.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 ->
when { when {
@@ -99,37 +89,31 @@ class DataRepository(
} }
} }
streams.forEach { stream -> entries.addAll(importSatellites(stream)) } streams.forEach { stream -> entries.addAll(importSatellites(stream)) }
localSource.updateEntries(entries) localSource.insertEntries(entries)
} _updateState.value = DataState.Success(0L)
println("Update from web took $updateTimeMillis ms")
_updateState.value = DataState.Success(updateTimeMillis)
} }
repositoryScope.launch(exceptionHandler) { repositoryScope.launch(exceptionHandler) {
remoteSource.fetchFileStream(settings.transmittersSource)?.let { stream -> remoteSource.getFileStream(remoteSource.radioApi)?.let { stream ->
val transmitters = dataParser.parseJSONStream(stream) localSource.insertRadios(dataParser.parseJSONStream(stream))
localSource.updateTransmitters(transmitters)
} }
} }
} }
override fun updatesSelection(catnums: List<Int>) {
repositoryScope.launch {
settings.saveSatelliteSelection(catnums)
}
}
override fun setUpdateStateHandled() {
_updateState.value = DataState.Handled
}
override fun clearAllData() { override fun clearAllData() {
repositoryScope.launch { repositoryScope.launch {
_updateState.value = DataState.Loading _updateState.value = DataState.Loading
localSource.clearAllData() localSource.clearAllData()
delay(updateStateDelay)
_updateState.value = DataState.Success(0L) _updateState.value = DataState.Success(0L)
} }
} }
override suspend fun getDataSources() = localSource.getDataSources()
override fun setEntriesSelection(catnums: List<Int>) {
repositoryScope.launch { localSource.setEntriesSelection(catnums) }
}
private suspend fun importSatellites(stream: InputStream): List<SatEntry> { private suspend fun importSatellites(stream: InputStream): List<SatEntry> {
return dataParser.parseTLEStream(stream).map { tle -> SatEntry(tle) } return dataParser.parseTLEStream(stream).map { tle -> SatEntry(tle) }
} }
@@ -19,23 +19,31 @@ package com.rtbishop.look4sat.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.Transmitter import com.rtbishop.look4sat.domain.model.SatRadio
import com.rtbishop.look4sat.domain.predict.Satellite import com.rtbishop.look4sat.domain.predict.Satellite
import java.io.InputStream import java.io.InputStream
interface ILocalSource { interface ILocalSource {
suspend fun getAllSatellites(): List<SatItem> suspend fun getEntriesWithModes(): List<SatItem>
suspend fun getSelectedSatellites(catnums: List<Int>): List<Satellite> suspend fun getSelectedEntries(): List<Satellite>
suspend fun getTransmitters(catnum: Int): List<Transmitter> suspend fun getRadios(catnum: Int): List<SatRadio>
suspend fun getFileStream(uri: String): InputStream? suspend fun getFileStream(uri: String): InputStream?
suspend fun updateEntries(entries: List<SatEntry>) suspend fun insertEntries(entries: List<SatEntry>)
suspend fun updateTransmitters(transmitters: List<Transmitter>) suspend fun insertRadios(radios: List<SatRadio>)
suspend fun clearAllData() suspend fun clearAllData()
suspend fun getDataSources(): List<String>
suspend fun setDataSources(sources: List<String>)
suspend fun getEntriesSelection(): List<Int>
suspend fun setEntriesSelection(catnums: List<Int>)
} }
@@ -21,5 +21,7 @@ import java.io.InputStream
interface IRemoteSource { interface IRemoteSource {
suspend fun fetchFileStream(url: String): InputStream? val radioApi: String get() = "https://db.satnogs.org/api/transmitters/?format=json"
suspend fun getFileStream(url: String): InputStream?
} }
@@ -28,8 +28,6 @@ interface ISettingsHandler {
"https://celestrak.com/NORAD/elements/gp.php?GROUP=active&FORMAT=csv", "https://celestrak.com/NORAD/elements/gp.php?GROUP=active&FORMAT=csv",
"https://amsat.org/tle/current/nasabare.txt" "https://amsat.org/tle/current/nasabare.txt"
) )
val transmittersSource: String
get() = "https://db.satnogs.org/api/transmitters/?format=json"
fun loadStationPosition(): GeoPos fun loadStationPosition(): GeoPos
@@ -59,9 +57,9 @@ interface ISettingsHandler {
fun loadModesSelection(): List<String> fun loadModesSelection(): List<String>
fun saveSatelliteSelection(catnums: List<Int>) fun saveEntriesSelection(catnums: List<Int>)
fun loadSatelliteSelection(): List<Int> fun loadEntriesSelection(): List<Int>
fun getRotatorEnabled(): Boolean fun getRotatorEnabled(): Boolean
@@ -17,8 +17,8 @@
*/ */
package com.rtbishop.look4sat.domain package com.rtbishop.look4sat.domain
import com.rtbishop.look4sat.domain.model.Transmitter import com.rtbishop.look4sat.domain.model.SatRadio
import com.rtbishop.look4sat.domain.predict.TLE import com.rtbishop.look4sat.domain.predict.OrbitalData
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.json.JSONArray import org.json.JSONArray
@@ -28,8 +28,8 @@ import kotlin.math.pow
class DataParser(private val parserDispatcher: CoroutineDispatcher) { class DataParser(private val parserDispatcher: CoroutineDispatcher) {
suspend fun parseCSVStream(csvStream: InputStream): List<TLE> = withContext(parserDispatcher) { suspend fun parseCSVStream(csvStream: InputStream): List<OrbitalData> = withContext(parserDispatcher) {
val parsedItems = mutableListOf<TLE>() val parsedItems = mutableListOf<OrbitalData>()
csvStream.bufferedReader().useLines { lines -> csvStream.bufferedReader().useLines { lines ->
lines.forEachIndexed { index, line -> lines.forEachIndexed { index, line ->
if (index != 0) { if (index != 0) {
@@ -41,9 +41,9 @@ class DataParser(private val parserDispatcher: CoroutineDispatcher) {
return@withContext parsedItems return@withContext parsedItems
} }
suspend fun parseTLEStream(tleStream: InputStream): List<TLE> = withContext(parserDispatcher) { suspend fun parseTLEStream(tleStream: InputStream): List<OrbitalData> = withContext(parserDispatcher) {
val tleStrings = mutableListOf(String(), String(), String()) val tleStrings = mutableListOf(String(), String(), String())
val parsedItems = mutableListOf<TLE>() val parsedItems = mutableListOf<OrbitalData>()
var lineIndex = 0 var lineIndex = 0
tleStream.bufferedReader().forEachLine { line -> tleStream.bufferedReader().forEachLine { line ->
tleStrings[lineIndex] = line tleStrings[lineIndex] = line
@@ -60,9 +60,9 @@ class DataParser(private val parserDispatcher: CoroutineDispatcher) {
return@withContext parsedItems return@withContext parsedItems
} }
suspend fun parseJSONStream(jsonStream: InputStream): List<Transmitter> { suspend fun parseJSONStream(jsonStream: InputStream): List<SatRadio> {
return withContext(parserDispatcher) { return withContext(parserDispatcher) {
val parsedItems = mutableListOf<Transmitter>() val parsedItems = mutableListOf<SatRadio>()
try { try {
val jsonArray = JSONArray(jsonStream.bufferedReader().readText()) val jsonArray = JSONArray(jsonStream.bufferedReader().readText())
for (index in 0 until jsonArray.length()) { for (index in 0 until jsonArray.length()) {
@@ -76,7 +76,7 @@ class DataParser(private val parserDispatcher: CoroutineDispatcher) {
} }
} }
private fun parseCSV(values: List<String>): TLE? { private fun parseCSV(values: List<String>): OrbitalData? {
try { try {
val name = values[0] val name = values[0]
val year = values[2].substring(0, 4) val year = values[2].substring(0, 4)
@@ -98,13 +98,13 @@ class DataParser(private val parserDispatcher: CoroutineDispatcher) {
val meanan = values[8].toDouble() val meanan = values[8].toDouble()
val catnum = values[11].toInt() val catnum = values[11].toInt()
val bstar = values[14].toDouble() val bstar = values[14].toDouble()
return TLE(name, epoch, meanmo, eccn, incl, raan, argper, meanan, catnum, bstar) return OrbitalData(name, epoch, meanmo, eccn, incl, raan, argper, meanan, catnum, bstar)
} catch (exception: Exception) { } catch (exception: Exception) {
return null return null
} }
} }
private fun parseTLE(tle: List<String>): TLE? { private fun parseTLE(tle: List<String>): OrbitalData? {
if (tle[1].substring(0, 1) != "1" && tle[2].substring(0, 1) != "2") { if (tle[1].substring(0, 1) != "1" && tle[2].substring(0, 1) != "2") {
return null return null
} }
@@ -120,13 +120,13 @@ class DataParser(private val parserDispatcher: CoroutineDispatcher) {
val catnum: Int = tle[1].substring(2, 7).trim().toInt() val catnum: Int = tle[1].substring(2, 7).trim().toInt()
val bstar: Double = 1.0e-5 * tle[1].substring(53, 59).toDouble() / val bstar: Double = 1.0e-5 * tle[1].substring(53, 59).toDouble() /
10.0.pow(tle[1].substring(60, 61).toDouble()) 10.0.pow(tle[1].substring(60, 61).toDouble())
return TLE(name, epoch, meanmo, eccn, incl, raan, argper, meanan, catnum, bstar) return OrbitalData(name, epoch, meanmo, eccn, incl, raan, argper, meanan, catnum, bstar)
} catch (exception: Exception) { } catch (exception: Exception) {
return null return null
} }
} }
private fun parseJSON(json: JSONObject): Transmitter? { private fun parseJSON(json: JSONObject): SatRadio? {
try { try {
val uuid = json.getString("uuid") val uuid = json.getString("uuid")
val info = json.getString("description") val info = json.getString("description")
@@ -140,7 +140,7 @@ class DataParser(private val parserDispatcher: CoroutineDispatcher) {
val isInverted = json.getBoolean("invert") val isInverted = json.getBoolean("invert")
val catnum = if (json.isNull("norad_cat_id")) null val catnum = if (json.isNull("norad_cat_id")) null
else json.getInt("norad_cat_id") else json.getInt("norad_cat_id")
return Transmitter(uuid, info, isAlive, downlink, uplink, mode, isInverted, catnum) return SatRadio(uuid, info, isAlive, downlink, uplink, mode, isInverted, catnum)
} catch (exception: Exception) { } catch (exception: Exception) {
return null return null
} }
@@ -19,7 +19,7 @@ package com.rtbishop.look4sat.domain
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 com.rtbishop.look4sat.domain.model.Transmitter 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.StateFlow import kotlinx.coroutines.flow.StateFlow
@@ -27,19 +27,21 @@ interface IDataRepository {
val updateState: StateFlow<DataState<Long>> val updateState: StateFlow<DataState<Long>>
suspend fun getAllSatellites(): List<SatItem> fun setUpdateStateHandled()
suspend fun getSelectedSatellites(): List<Satellite> suspend fun getEntriesWithModes(): List<SatItem>
suspend fun getTransmitters(catnum: Int): List<Transmitter> suspend fun getSelectedEntries(): List<Satellite>
suspend fun getRadios(catnum: Int): List<SatRadio>
fun updateFromFile(uri: String) fun updateFromFile(uri: String)
fun updateFromWeb(sources: List<String>) fun updateFromWeb(sources: List<String>)
fun updatesSelection(catnums: List<Int>)
fun setUpdateStateHandled()
fun clearAllData() fun clearAllData()
suspend fun getDataSources(): List<String>
fun setEntriesSelection(catnums: List<Int>)
} }
@@ -17,9 +17,6 @@
*/ */
package com.rtbishop.look4sat.domain.model package com.rtbishop.look4sat.domain.model
import com.rtbishop.look4sat.domain.predict.TLE import com.rtbishop.look4sat.domain.predict.OrbitalData
data class SatEntry( data class SatEntry(val data: OrbitalData, var comment: String? = null)
val tle: TLE,
var isSelected: Boolean = false
)
@@ -20,6 +20,6 @@ package com.rtbishop.look4sat.domain.model
data class SatItem( data class SatItem(
val catnum: Int, val catnum: Int,
val name: String, val name: String,
var isSelected: Boolean, val modes: List<String>,
val modes: List<String> var isSelected: Boolean
) )
@@ -17,7 +17,7 @@
*/ */
package com.rtbishop.look4sat.domain.model package com.rtbishop.look4sat.domain.model
data class Transmitter( data class SatRadio(
val uuid: String, val uuid: String,
val info: String, val info: String,
val isAlive: Boolean, val isAlive: Boolean,
@@ -25,5 +25,6 @@ data class Transmitter(
var uplink: Long?, var uplink: Long?,
val mode: String?, val mode: String?,
val isInverted: Boolean, val isInverted: Boolean,
val catnum: Int? val catnum: Int?,
var comment: String? = null
) )
@@ -19,7 +19,7 @@ package com.rtbishop.look4sat.domain.predict
import kotlin.math.* import kotlin.math.*
class DeepSpaceSat(params: TLE) : Satellite(params) { class DeepSpaceSat(data: OrbitalData) : Satellite(data) {
private val c1: Double private val c1: Double
private val c4: Double private val c4: Double
@@ -35,41 +35,41 @@ class DeepSpaceSat(params: TLE) : Satellite(params) {
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.params.xno).pow(twoThirds) val a1 = (xke / super.data.xno).pow(twoThirds)
dsv.cosio = cos(super.params.xincl) dsv.cosio = cos(super.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.params.eccn * super.params.eccn dsv.eosq = super.data.eccn * super.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 * twoThirds + 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.params.xno / (1.0 + delo) dsv.xnodp = super.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.params.eccn) - 1.0) * earthRadius) setPerigee((dsv.aodp * (1.0 - super.data.eccn) - 1.0) * earthRadius)
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.params.omegao) dsv.sing = sin(super.data.omegao)
dsv.cosg = cos(super.params.omegao) dsv.cosg = cos(super.data.omegao)
val tsi = invert(dsv.aodp - s4) val tsi = invert(dsv.aodp - s4)
val eta = dsv.aodp * super.params.eccn * tsi val eta = dsv.aodp * super.data.eccn * tsi
val etasq = eta * eta val etasq = eta * eta
val eeta = super.params.eccn * eta val eeta = super.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.params.bstar * c2 c1 = super.data.bstar * c2
dsv.sinio = sin(super.params.xincl) dsv.sinio = sin(super.data.xincl)
val a3ovk2 = -j3Harmonic / ck2 val a3ovk2 = -j3Harmonic / 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.params.eccn 2 * dsv.xnodp * coef1 * dsv.aodp * dsv.betao2 * (eta * (2.0 + 0.5 * etasq) + super.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.params.omegao)))) * (2.0 * etasq - eeta * (1.0 + etasq)) * cos(2.0 * super.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
@@ -93,18 +93,18 @@ class DeepSpaceSat(params: TLE) : Satellite(params) {
internal fun calculateSDP4(tSince: Double) { internal fun calculateSDP4(tSince: Double) {
synchronized(this) { synchronized(this) {
val temp = DoubleArray(12) val temp = DoubleArray(12)
val xmdf = params.xmo + dsv.xmdot * tSince val xmdf = data.xmo + dsv.xmdot * tSince
val tsq = tSince * tSince val tsq = tSince * tSince
val templ = t2cof * tsq val templ = t2cof * tsq
dsv.xll = xmdf + dsv.xnodp * templ dsv.xll = xmdf + dsv.xnodp * templ
dsv.omgadf = params.omegao + dsv.omgdot * tSince dsv.omgadf = data.omegao + dsv.omgdot * tSince
val xnoddf = params.xnodeo + dsv.xnodot * tSince val xnoddf = data.xnodeo + dsv.xnodot * tSince
dsv.xnode = xnoddf + xnodcf * tsq dsv.xnode = xnoddf + xnodcf * tsq
val tempa = 1.0 - c1 * tSince val tempa = 1.0 - c1 * tSince
val tempe = params.bstar * c4 * tSince val tempe = data.bstar * c4 * tSince
dsv.xn = dsv.xnodp dsv.xn = dsv.xnodp
dsv.t = tSince dsv.t = tSince
deep.dpsec(params) deep.dpsec(data)
val a = (xke / dsv.xn).pow(twoThirds) * tempa * tempa val a = (xke / dsv.xn).pow(twoThirds) * tempa * tempa
dsv.em = dsv.em - tempe dsv.em = dsv.em - tempe
deep.dpper() deep.dpper()
@@ -425,16 +425,16 @@ class DeepSpaceSat(params: TLE) : Satellite(params) {
private var epochRestart = false private var epochRestart = false
init { init {
thgr = thetaG(params.epoch) thgr = thetaG(data.epoch)
eq = params.eccn eq = data.eccn
xnq = dsv.xnodp xnq = dsv.xnodp
aqnv = invert(dsv.aodp) aqnv = invert(dsv.aodp)
xqncl = params.xincl xqncl = data.xincl
xmao = params.xmo xmao = data.xmo
xpidot = dsv.omgdot + dsv.xnodot xpidot = dsv.omgdot + dsv.xnodot
sinq = sin(params.xnodeo) sinq = sin(data.xnodeo)
cosq = cos(params.xnodeo) cosq = cos(data.xnodeo)
omegaq = params.omegao omegaq = data.omegao
// Initialize lunar solar terms, days since 1900 Jan 0.5 // Initialize lunar solar terms, days since 1900 Jan 0.5
day = dsv.ds50 + 18261.5 day = dsv.ds50 + 18261.5
if (abs(day - preep) > 1.0E-6) { if (abs(day - preep) > 1.0E-6) {
@@ -534,7 +534,7 @@ class DeepSpaceSat(params: TLE) : Satellite(params) {
temp = 2.0 * temp1 * root54 temp = 2.0 * temp1 * root54
d5421 = temp * f542 * g521 d5421 = temp * f542 * g521
d5433 = temp * f543 * g533 d5433 = temp * f543 * g533
xlamo = xmao + params.xnodeo + params.xnodeo - thgr - thgr xlamo = xmao + data.xnodeo + data.xnodeo - thgr - thgr
bfact = dsv.xmdot + dsv.xnodot + dsv.xnodot - tHdt - tHdt bfact = dsv.xmdot + dsv.xnodot + dsv.xnodot - tHdt - tHdt
bfact += ssl + ssh + ssh bfact += ssl + ssh + ssh
} else { } else {
@@ -556,7 +556,7 @@ class DeepSpaceSat(params: TLE) : Satellite(params) {
fasx2 = 0.13130908 fasx2 = 0.13130908
fasx4 = 2.8843198 fasx4 = 2.8843198
fasx6 = 0.37448087 fasx6 = 0.37448087
xlamo = xmao + params.xnodeo + params.omegao - thgr xlamo = xmao + data.xnodeo + data.omegao - thgr
bfact = dsv.xmdot + xpidot - tHdt bfact = dsv.xmdot + xpidot - tHdt
bfact += ssl + ssg + ssh bfact += ssl + ssg + ssh
} }
@@ -623,7 +623,7 @@ class DeepSpaceSat(params: TLE) : Satellite(params) {
} }
// Entrance for deep space secular effects // Entrance for deep space secular effects
fun dpsec(params: TLE) { fun dpsec(params: OrbitalData) {
dsv.xll = dsv.xll + ssl * dsv.t dsv.xll = dsv.xll + ssl * dsv.t
dsv.omgadf = dsv.omgadf + ssg * dsv.t dsv.omgadf = dsv.omgadf + ssg * dsv.t
dsv.xnode = dsv.xnode + ssh * dsv.t dsv.xnode = dsv.xnode + ssh * dsv.t
@@ -19,7 +19,7 @@ package com.rtbishop.look4sat.domain.predict
import kotlin.math.* import kotlin.math.*
class NearEarthSat(params: TLE) : Satellite(params) { class NearEarthSat(data: OrbitalData) : Satellite(data) {
private val aodp: Double private val aodp: Double
private val aycof: Double private val aycof: Double
@@ -53,18 +53,18 @@ class NearEarthSat(params: TLE) : Satellite(params) {
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.params.xno).pow(twoThirds) val a1 = (xke / super.data.xno).pow(twoThirds)
cosio = cos(super.params.xincl) cosio = cos(super.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.params.eccn val eo = super.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 * twoThirds + 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.params.xno / (1.0 + delo) xnodp = super.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
@@ -80,15 +80,15 @@ class NearEarthSat(params: TLE) : Satellite(params) {
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.params.bstar val bstar = super.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.params.xincl) sinio = sin(super.data.xincl)
val a3ovk2 = -j3Harmonic / ck2 val a3ovk2 = -j3Harmonic / 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.params.omegao val omegao = super.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))
@@ -112,7 +112,7 @@ class NearEarthSat(params: TLE) : Satellite(params) {
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.params.xmo val xmo = super.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
@@ -138,14 +138,14 @@ class NearEarthSat(params: TLE) : Satellite(params) {
internal fun calculateSGP4(tSince: Double) { internal fun calculateSGP4(tSince: Double) {
synchronized(this) { synchronized(this) {
val temp = DoubleArray(9) val temp = DoubleArray(9)
val xmdf = params.xmo + xmdot * tSince val xmdf = data.xmo + xmdot * tSince
val omgadf = params.omegao + omgdot * tSince val omgadf = data.omegao + omgdot * tSince
val xnoddf = params.xnodeo + xnodot * tSince val xnoddf = data.xnodeo + xnodot * tSince
var omega = omgadf var omega = omgadf
var xmp = xmdf var xmp = xmdf
val tsq = sqr(tSince) val tsq = sqr(tSince)
val xnode = xnoddf + xnodcf * tsq val xnode = xnoddf + xnodcf * tsq
val bstar = params.bstar val bstar = data.bstar
var tempa = 1.0 - c1 * tSince var tempa = 1.0 - c1 * tSince
var tempe = bstar * c4 * tSince var tempe = bstar * c4 * tSince
var templ = t2cof * tsq var templ = t2cof * tsq
@@ -162,7 +162,7 @@ class NearEarthSat(params: TLE) : Satellite(params) {
templ += t3cof * tcube + tfour * (t4cof + tSince * t5cof) templ += t3cof * tcube + tfour * (t4cof + tSince * t5cof)
} }
val a = aodp * tempa.pow(2.0) val a = aodp * tempa.pow(2.0)
val eo = params.eccn val eo = data.eccn
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)
@@ -213,7 +213,7 @@ class NearEarthSat(params: TLE) : Satellite(params) {
val rk = r * (1.0 - 1.5 * temp[2] * betal * x3thm1) + 0.5 * temp[1] * x1mth2 * cos2u val rk = r * (1.0 - 1.5 * temp[2] * betal * x3thm1) + 0.5 * temp[1] * x1mth2 * cos2u
val uk = u - 0.25 * temp[2] * x7thm1 * sin2u val uk = u - 0.25 * temp[2] * x7thm1 * sin2u
val xnodek = xnode + 1.5 * temp[2] * cosio * sin2u val xnodek = xnode + 1.5 * temp[2] * cosio * sin2u
val xinck = params.xincl + 1.5 * temp[2] * cosio * sinio * cos2u val xinck = data.xincl + 1.5 * temp[2] * cosio * sinio * cos2u
val rdotk = rdot - xn * temp[1] * x1mth2 * sin2u val rdotk = rdot - xn * temp[1] * x1mth2 * sin2u
val rfdotk = rfdot + xn * temp[1] * (x1mth2 * cos2u + 1.5 * x3thm1) val rfdotk = rfdot + xn * temp[1] * (x1mth2 * cos2u + 1.5 * x3thm1)
super.calculatePosAndVel(rk, uk, xnodek, xinck, rdotk, rfdotk) super.calculatePosAndVel(rk, uk, xnodek, xinck, rdotk, rfdotk)
@@ -17,7 +17,7 @@
*/ */
package com.rtbishop.look4sat.domain.predict package com.rtbishop.look4sat.domain.predict
data class TLE( data class OrbitalData(
val name: String, val name: String,
val epoch: Double, val epoch: Double,
val meanmo: Double, val meanmo: Double,
@@ -17,7 +17,7 @@
*/ */
package com.rtbishop.look4sat.domain.predict package com.rtbishop.look4sat.domain.predict
import com.rtbishop.look4sat.domain.model.Transmitter 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
@@ -44,7 +44,7 @@ class Predictor(private val predictorDispatcher: CoroutineDispatcher) {
} }
} }
suspend fun processRadios(sat: Satellite, pos: GeoPos, radios: List<Transmitter>, time: Long): List<Transmitter> { suspend fun processRadios(sat: Satellite, pos: GeoPos, radios: List<SatRadio>, time: Long): List<SatRadio> {
return withContext(predictorDispatcher) { return withContext(predictorDispatcher) {
val satPos = sat.getPosition(pos, time) val satPos = sat.getPosition(pos, time)
val copiedList = radios.map { it.copy() } val copiedList = radios.map { it.copy() }
@@ -106,7 +106,7 @@ class Predictor(private val predictorDispatcher: CoroutineDispatcher) {
var lastAosDate: Long var lastAosDate: Long
var count = 0 var count = 0
if (this.willBeSeen(pos)) { if (this.willBeSeen(pos)) {
if (this.params.isDeepspace) { if (this.data.isDeepspace) {
passes.add(getGeoPass(this, pos, time)) passes.add(getGeoPass(this, pos, time))
} else { } else {
do { do {
@@ -29,7 +29,7 @@ data class SatPass(
val satellite: Satellite, val satellite: Satellite,
var progress: Int = 0 var progress: Int = 0
) { ) {
val catNum: Int = satellite.params.catnum val catNum: Int = satellite.data.catnum
val name: String = satellite.params.name val name: String = satellite.data.name
val isDeepSpace: Boolean = satellite.params.isDeepspace val isDeepSpace: Boolean = satellite.data.isDeepspace
} }
@@ -19,7 +19,7 @@ package com.rtbishop.look4sat.domain.predict
import kotlin.math.* import kotlin.math.*
abstract class Satellite(val params: TLE) { abstract class Satellite(val data: OrbitalData) {
private val flatFactor = 3.35281066474748E-3 private val flatFactor = 3.35281066474748E-3
private val deg2Rad = 1.745329251994330E-2 private val deg2Rad = 1.745329251994330E-2
@@ -30,7 +30,7 @@ abstract class Satellite(val params: TLE) {
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 / params.meanmo val orbitalPeriod = 24 * 60 / data.meanmo
val earthRadius = 6378.137 val earthRadius = 6378.137
val j3Harmonic = -2.53881E-6 val j3Harmonic = -2.53881E-6
val twoPi = Math.PI * 2.0 val twoPi = Math.PI * 2.0
@@ -42,11 +42,11 @@ abstract class Satellite(val params: TLE) {
var s4 = 0.0 var s4 = 0.0
internal fun willBeSeen(pos: GeoPos): Boolean { internal fun willBeSeen(pos: GeoPos): Boolean {
return if (params.meanmo < 1e-8) false return if (data.meanmo < 1e-8) false
else { else {
val sma = 331.25 * exp(ln(1440.0 / params.meanmo) * (2.0 / 3.0)) val sma = 331.25 * exp(ln(1440.0 / data.meanmo) * (2.0 / 3.0))
val apogee = sma * (1.0 + params.eccn) - earthRadius val apogee = sma * (1.0 + data.eccn) - earthRadius
var lin = params.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(earthRadius / (apogee + earthRadius)) + lin * deg2Rad > abs(pos.latitude * deg2Rad)
} }
@@ -57,7 +57,7 @@ abstract class Satellite(val params: TLE) {
// Date/time at which the position and velocity were calculated // Date/time at which the position and velocity were calculated
val julUTC = calcCurrentDaynum(time) + 2444238.5 val julUTC = calcCurrentDaynum(time) + 2444238.5
// 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(params.epoch) val julEpoch = juliandDateOfEpoch(data.epoch)
val tsince = (julUTC - julEpoch) * minPerDay val tsince = (julUTC - julEpoch) * minPerDay
calculateSDP4orSGP4(tsince) calculateSDP4orSGP4(tsince)
// Scale position and velocity vectors to km and km/sec // Scale position and velocity vectors to km and km/sec
@@ -97,7 +97,7 @@ abstract class Satellite(val params: TLE) {
} }
private fun calculateSDP4orSGP4(tsince: Double) { private fun calculateSDP4orSGP4(tsince: Double) {
if (params.isDeepspace) (this as DeepSpaceSat).calculateSDP4(tsince) if (data.isDeepspace) (this as DeepSpaceSat).calculateSDP4(tsince)
else (this as NearEarthSat).calculateSGP4(tsince) else (this as NearEarthSat).calculateSGP4(tsince)
} }