Destructively migrated to the new database

pull/87/head
Arty Bishop 2022-02-15 13:08:38 +00:00
rodzic 92eec290b1
commit 64b5010a44
53 zmienionych plików z 381 dodań i 901 usunięć

Wyświetl plik

@ -89,4 +89,4 @@ dependencies {
androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
androidTestImplementation "org.mockito:mockito-android:4.3.1"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0"
}
}

Wyświetl plik

@ -1,111 +1,111 @@
{
"formatVersion": 1,
"database": {
"version": 5,
"identityHash": "0b523e732e116bbdcccd8188922898e2",
"version": 1,
"identityHash": "834981e960b712254e56b633f3772115",
"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`))",
"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": [
{
"fieldPath": "isSelected",
"columnName": "isSelected",
"affinity": "INTEGER",
"notNull": true
"fieldPath": "comment",
"columnName": "comment",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "tle.name",
"fieldPath": "data.name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tle.epoch",
"fieldPath": "data.epoch",
"columnName": "epoch",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.meanmo",
"fieldPath": "data.meanmo",
"columnName": "meanmo",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.eccn",
"fieldPath": "data.eccn",
"columnName": "eccn",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.incl",
"fieldPath": "data.incl",
"columnName": "incl",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.raan",
"fieldPath": "data.raan",
"columnName": "raan",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.argper",
"fieldPath": "data.argper",
"columnName": "argper",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.meanan",
"fieldPath": "data.meanan",
"columnName": "meanan",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.catnum",
"fieldPath": "data.catnum",
"columnName": "catnum",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "tle.bstar",
"fieldPath": "data.bstar",
"columnName": "bstar",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.xincl",
"fieldPath": "data.xincl",
"columnName": "xincl",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.xnodeo",
"fieldPath": "data.xnodeo",
"columnName": "xnodeo",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.omegao",
"fieldPath": "data.omegao",
"columnName": "omegao",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.xmo",
"fieldPath": "data.xmo",
"columnName": "xmo",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.xno",
"fieldPath": "data.xno",
"columnName": "xno",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "tle.isDeepspace",
"fieldPath": "data.isDeepspace",
"columnName": "isDeepspace",
"affinity": "INTEGER",
"notNull": true
@ -121,8 +121,8 @@
"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`))",
"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, `comment` TEXT, PRIMARY KEY(`uuid`))",
"fields": [
{
"fieldPath": "uuid",
@ -171,6 +171,12 @@
"columnName": "catnum",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "comment",
"columnName": "comment",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
@ -186,7 +192,7 @@
"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, '0b523e732e116bbdcccd8188922898e2')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '834981e960b712254e56b633f3772115')"
]
}
}

Wyświetl plik

@ -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')"
]
}
}

Wyświetl plik

@ -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')"
]
}
}

Wyświetl plik

@ -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')"
]
}
}

Wyświetl plik

@ -1,4 +1,4 @@
package com.rtbishop.bbctestapp
package com.rtbishop.look4sat
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
@ -6,17 +6,12 @@ import org.junit.Assert.assertEquals
import org.junit.Test
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)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.rtbishop.bbctestapp", appContext.packageName)
}
}
}

Wyświetl plik

@ -23,4 +23,4 @@
</activity>
</application>
</manifest>
</manifest>

Wyświetl plik

@ -19,29 +19,29 @@ package com.rtbishop.look4sat.framework
import com.rtbishop.look4sat.domain.model.SatEntry as DomainEntry
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.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(
this.uuid, this.info, this.isAlive, this.downlink,
this.uplink, this.mode, this.isInverted, this.catnum
fun DomainRadio.toFramework() = FrameworkRadio(
this.uuid, this.info, this.isAlive, this.downlink, this.uplink,
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(
this.uuid, this.info, this.isAlive, this.downlink,
this.uplink, this.mode, this.isInverted, this.catnum
fun FrameworkRadio.toDomain() = DomainRadio(
this.uuid, this.info, this.isAlive, this.downlink, this.uplink,
this.mode, this.isInverted, this.catnum, this.comment
)
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<FrameworkTransmitter>.toDomain() = this.map { it.toDomain() }
fun List<FrameworkRadio>.toDomainRadios() = this.map { it.toDomain() }

Wyświetl plik

@ -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() }
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() }
return catnums?.sorted() ?: emptyList()
}

Wyświetl plik

@ -20,52 +20,77 @@ package com.rtbishop.look4sat.framework.local
import android.content.ContentResolver
import android.net.Uri
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.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.framework.toDomain
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.toFrameworkRadios
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import java.io.InputStream
class LocalSource(
private val entriesDao: SatEntriesDao,
private val radiosDao: SatRadiosDao,
private val settings: ISettingsHandler,
private val resolver: ContentResolver,
private val ioDispatcher: CoroutineDispatcher,
private val entriesDao: EntriesDao,
private val transmittersDao: TransmittersDao,
private val ioDispatcher: CoroutineDispatcher
) : ILocalSource {
override suspend fun getAllSatellites(): List<SatItem> {
return entriesDao.getAllSatellites().toDomainItems()
override suspend fun getEntriesWithModes(): List<SatItem> {
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> {
return entriesDao.getSelectedSatellites(catnums).map { entry -> entry.tle.createSat() }
override suspend fun getSelectedEntries(): List<Satellite> {
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> {
return transmittersDao.getTransmitters(catnum).toDomain()
override suspend fun getRadios(catnum: Int): List<SatRadio> {
return radiosDao.getRadios(catnum).toDomainRadios()
}
@Suppress("BlockingMethodInNonBlockingContext")
override suspend fun getFileStream(uri: String): InputStream? {
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())
}
override suspend fun updateTransmitters(transmitters: List<Transmitter>) {
transmittersDao.updateTransmitters(transmitters.toFramework())
override suspend fun insertRadios(radios: List<SatRadio>) {
radiosDao.insertRadios(radios.toFrameworkRadios())
}
override suspend fun clearAllData() {
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) }
}
}

Wyświetl plik

@ -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
}

Wyświetl plik

@ -22,23 +22,15 @@ import com.rtbishop.look4sat.framework.model.SatEntry
import com.rtbishop.look4sat.framework.model.SatItem
@Dao
interface EntriesDao {
interface SatEntriesDao {
@Transaction
@Query("SELECT catnum, name, isSelected FROM entries ORDER BY name ASC")
suspend fun getAllSatellites(): List<SatItem>
@Query("SELECT catnum, name FROM entries ORDER BY name ASC")
suspend fun getEntriesWithModes(): List<SatItem>
@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)")
suspend fun getSelectedSatellitesChunked(catnums: List<Int>): List<SatEntry>
suspend fun getSelectedEntries(catnums: List<Int>): List<SatEntry>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertEntries(entries: List<SatEntry>)

Wyświetl plik

@ -17,24 +17,21 @@
*/
package com.rtbishop.look4sat.framework.local
import androidx.room.*
import com.rtbishop.look4sat.framework.model.Transmitter
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.rtbishop.look4sat.framework.model.SatRadio
@Dao
interface TransmittersDao {
interface SatRadiosDao {
@Query("SELECT * FROM transmitters WHERE catnum = :catnum AND isAlive = 1")
suspend fun getTransmitters(catnum: Int): List<Transmitter>
@Query("SELECT * FROM radios WHERE catnum = :catnum AND isAlive = 1")
suspend fun getRadios(catnum: Int): List<SatRadio>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertTransmitters(transmitters: List<Transmitter>)
suspend fun insertRadios(radios: List<SatRadio>)
@Transaction
suspend fun updateTransmitters(transmitters: List<Transmitter>) {
deleteTransmitters()
insertTransmitters(transmitters)
}
@Query("DELETE FROM transmitters")
suspend fun deleteTransmitters()
@Query("DELETE FROM radios")
suspend fun deleteRadios()
}

Wyświetl plik

@ -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")
}
}

Wyświetl plik

@ -17,8 +17,4 @@
*/
package com.rtbishop.look4sat.framework.model
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "sources")
data class Source(@PrimaryKey var sourceUrl: String = String())
data class DataSource(var url: String = String())

Wyświetl plik

@ -19,11 +19,7 @@ package com.rtbishop.look4sat.framework.model
import androidx.room.Embedded
import androidx.room.Entity
import com.rtbishop.look4sat.domain.predict.TLE
import com.rtbishop.look4sat.domain.predict.OrbitalData
@Entity(tableName = "entries", primaryKeys = ["catnum"])
data class SatEntry(
@Embedded
val tle: TLE,
var isSelected: Boolean = false
)
data class SatEntry(@Embedded val data: OrbitalData, var comment: String? = null)

Wyświetl plik

@ -22,10 +22,9 @@ import androidx.room.Relation
data class SatItem(
val catnum: Int,
val name: String,
var isSelected: Boolean = false,
@Relation(
parentColumn = "catnum",
entity = Transmitter::class,
entity = SatRadio::class,
entityColumn = "catnum",
projection = ["mode"]
)

Wyświetl plik

@ -20,8 +20,8 @@ package com.rtbishop.look4sat.framework.model
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "transmitters")
data class Transmitter(
@Entity(tableName = "radios")
data class SatRadio(
@PrimaryKey val uuid: String,
val info: String,
val isAlive: Boolean,
@ -29,5 +29,6 @@ data class Transmitter(
var uplink: Long?,
val mode: String?,
val isInverted: Boolean,
val catnum: Int?
val catnum: Int?,
var comment: String? = null
)

Wyświetl plik

@ -8,8 +8,7 @@ import java.net.URL
class RemoteSource(private val ioDispatcher: CoroutineDispatcher) : IRemoteSource {
@Suppress("BlockingMethodInNonBlockingContext")
override suspend fun fetchFileStream(url: String): InputStream? {
override suspend fun getFileStream(url: String): InputStream? {
return withContext(ioDispatcher) { URL(url).openStream() }
}
}

Wyświetl plik

@ -29,7 +29,8 @@ import com.rtbishop.look4sat.domain.ILocationHandler
import com.rtbishop.look4sat.domain.predict.Predictor
import com.rtbishop.look4sat.framework.LocationHandler
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 dagger.Module
import dagger.Provides
@ -53,15 +54,14 @@ object CoreModule {
@IoDispatcher ioDispatcher: CoroutineDispatcher,
@DefaultDispatcher defaultDispatcher: CoroutineDispatcher
): IDataRepository {
val db = Room.databaseBuilder(context, SatelliteDb::class.java, "SatelliteDb")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
val db = Room.databaseBuilder(context, Look4SatDb::class.java, "Look4SatDb")
.fallbackToDestructiveMigration().build()
val dataParser = DataParser(defaultDispatcher)
val resolver = context.contentResolver
val localSource = LocalSource(resolver, ioDispatcher, db.entriesDao(), db.transmittersDao())
val remoteSource = RemoteSource(ioDispatcher)
val local = LocalSource(db.entriesDao(), db.radiosDao(), settings, resolver, ioDispatcher)
val remote = RemoteSource(ioDispatcher)
val repositoryScope = CoroutineScope(SupervisorJob())
return DataRepository(dataParser, settings, localSource, remoteSource, repositoryScope)
return DataRepository(dataParser, local, remote, repositoryScope)
}
@Provides

Wyświetl plik

@ -21,4 +21,4 @@ import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class Look4SatApplication : Application()
class Look4SatApplication : Application()

Wyświetl plik

@ -22,7 +22,7 @@ class MainViewModel @Inject constructor(
timeRef: Long = System.currentTimeMillis()
) {
viewModelScope.launch {
val satellites = dataRepository.getSelectedSatellites()
val satellites = dataRepository.getSelectedEntries()
val stationPos = preferences.loadStationPosition()
predictor.forceCalculation(satellites, stationPos, timeRef, hoursAhead, minElevation)
}

Wyświetl plik

@ -34,7 +34,7 @@ class EntriesViewModel @Inject constructor(
private val transModes = MutableLiveData(preferences.loadModesSelection())
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 ->
itemsFromRepo.map { items -> filterByModes(items, modes) }
}
@ -68,7 +68,7 @@ class EntriesViewModel @Inject constructor(
fun saveSelection() {
itemsFromRepo.value?.let { itemsAll ->
val filteredItems = itemsAll.filter { item -> item.isSelected }
repository.updatesSelection(filteredItems.map { item -> item.catnum })
repository.setEntriesSelection(filteredItems.map { item -> item.catnum })
}
}

Wyświetl plik

@ -130,7 +130,7 @@ class MapFragment : Fragment(R.layout.fragment_map) {
textLabelBackgroundColor = Color.TRANSPARENT
textLabelForegroundColor =
ContextCompat.getColor(requireContext(), R.color.themeAccent)
setTextIcon(it.key.params.name)
setTextIcon(it.key.data.name)
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
try {
position = GeoPoint(it.value.latitude, it.value.longitude)

Wyświetl plik

@ -76,13 +76,13 @@ class MapViewModel @Inject constructor(
fun selectDefaultSatellite(catnum: Int) {
viewModelScope.launch {
dataRepository.getSelectedSatellites().also { satellites ->
dataRepository.getSelectedEntries().also { satellites ->
if (satellites.isNotEmpty()) {
allSatList = satellites
if (catnum == -1) {
selectSatellite(satellites.first())
} 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 qthLoc = QthConverter.positionToQth(osmPos.latitude, osmPos.longitude) ?: "-- --"
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
)
_satData.postValue(satData)

Wyświetl plik

@ -65,7 +65,7 @@ class PassesViewModel @Inject constructor(
viewModelScope.launch {
_passes.postValue(DataState.Loading)
passesProcessing?.cancelAndJoin()
val satellites = repository.getSelectedSatellites()
val satellites = repository.getSelectedEntries()
val stationPos = settings.loadStationPosition()
predictor.forceCalculation(satellites, stationPos, timeRef, hoursAhead, minElevation)
}

Wyświetl plik

@ -53,7 +53,7 @@ class RadarFragment : Fragment(R.layout.fragment_radar) {
private fun setupViews() {
val context = requireContext()
val adapter = TransmittersAdapter()
val adapter = RadiosAdapter()
val layoutManager = LinearLayoutManager(context)
val itemDecoration = DividerItemDecoration(context, layoutManager.orientation)
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 ->
radarView = RadarView(requireContext()).apply {
setShowAim(preferences.getUseCompass())
@ -82,7 +82,7 @@ class RadarFragment : Fragment(R.layout.fragment_radar) {
}
viewModel.transmitters.observe(viewLifecycleOwner) { list ->
if (list.isNotEmpty()) {
transmittersAdapter.submitList(list)
radiosAdapter.submitList(list)
binding.radarRecyclerMsg.text = getString(R.string.trans_data)
} else {
binding.radarRecyclerMsg.text = getString(R.string.trans_no_data)

Wyświetl plik

@ -22,7 +22,7 @@ import androidx.lifecycle.*
import com.rtbishop.look4sat.data.ISettingsHandler
import com.rtbishop.look4sat.domain.DataReporter
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.Predictor
import com.rtbishop.look4sat.domain.predict.SatPass
@ -47,10 +47,10 @@ class RadarViewModel @Inject constructor(
private val stationPos = preferences.loadStationPosition()
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>>()
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
fun getPass(catNum: Int, aosTime: Long) = liveData {
@ -107,7 +107,7 @@ class RadarViewModel @Inject constructor(
private fun processTransmitters(pass: SatPass) {
viewModelScope.launch {
val transmitters = dataRepository.getTransmitters(pass.catNum)
val transmitters = dataRepository.getRadios(pass.catNum)
while (isActive) {
val time = System.currentTimeMillis()
val list = predictor.processRadios(pass.satellite, stationPos, transmitters, time)

Wyświetl plik

@ -24,24 +24,24 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.ItemTransBinding
import com.rtbishop.look4sat.domain.model.Transmitter
import com.rtbishop.look4sat.domain.model.SatRadio
import java.util.*
class TransmittersAdapter : RecyclerView.Adapter<TransmittersAdapter.TransHolder>() {
class RadiosAdapter : RecyclerView.Adapter<RadiosAdapter.TransHolder>() {
private val diffCallback = object : DiffUtil.ItemCallback<Transmitter>() {
override fun areItemsTheSame(oldItem: Transmitter, newItem: Transmitter): Boolean {
private val diffCallback = object : DiffUtil.ItemCallback<SatRadio>() {
override fun areItemsTheSame(oldItem: SatRadio, newItem: SatRadio): Boolean {
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
}
}
private val differ = AsyncListDiffer(this, diffCallback)
fun submitList(transmitters: List<Transmitter>) {
differ.submitList(transmitters)
fun submitList(radios: List<SatRadio>) {
differ.submitList(radios)
}
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 isInverted = itemView.context.getString(R.string.trans_inverted)
fun bind(transmitter: Transmitter) {
binding.transDesc.text = transmitter.info
fun bind(radio: SatRadio) {
binding.transDesc.text = radio.info
transmitter.downlink.let { downlink ->
radio.downlink.let { downlink ->
if (downlink != null) {
val downlinkFreq = downlink / divider
binding.transDownlink.text =
@ -78,7 +78,7 @@ class TransmittersAdapter : RecyclerView.Adapter<TransmittersAdapter.TransHolder
}
}
transmitter.uplink.let { uplink ->
radio.uplink.let { uplink ->
if (uplink != null) {
val uplinkFreq = uplink / divider
binding.transUplink.text = String.format(Locale.ENGLISH, formatLink, uplinkFreq)
@ -87,13 +87,13 @@ class TransmittersAdapter : RecyclerView.Adapter<TransmittersAdapter.TransHolder
}
}
if (transmitter.mode != null) {
binding.transMode.text = String.format(mode, transmitter.mode)
if (radio.mode != null) {
binding.transMode.text = String.format(mode, radio.mode)
} else {
binding.transMode.text = String.format(mode, strNo)
}
if (transmitter.isInverted) {
if (radio.isInverted) {
binding.transInverted.text = String.format(isInverted, strYes)
} else {
binding.transInverted.text = String.format(isInverted, strNo)

Wyświetl plik

@ -15,29 +15,29 @@
* 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.presentation.sourcesScreen
package com.rtbishop.look4sat.presentation.settingsScreen
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.widget.doOnTextChanged
import androidx.recyclerview.widget.RecyclerView
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>() {
fun getSources(): List<Source> {
return sources.filter { it.sourceUrl.contains("https://") }
fun getSources(): List<DataSource> {
return sources.filter { source -> source.url.contains("https://") }
}
fun setSources(list: List<Source>) {
fun setSources(list: List<DataSource>) {
sources.clear()
sources.addAll(list)
}
fun addSource() {
sources.add(Source())
sources.add(DataSource())
notifyItemInserted(itemCount - 1)
}
@ -58,9 +58,9 @@ class SourcesAdapter(private val sources: MutableList<Source> = mutableListOf())
inner class TleSourceHolder(private val binding: ItemSourceBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(source: Source) {
binding.sourceUrl.setText(source.sourceUrl)
binding.sourceUrl.doOnTextChanged { txt, _, _, _ -> source.sourceUrl = txt.toString() }
fun bind(source: DataSource) {
binding.sourceUrl.setText(source.url)
binding.sourceUrl.doOnTextChanged { txt, _, _, _ -> source.url = txt.toString() }
binding.sourceBtn.setOnClickListener {
sources.remove(source)
notifyItemRemoved(adapterPosition)

Wyświetl plik

@ -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.presentation.sourcesScreen
package com.rtbishop.look4sat.presentation.settingsScreen
import android.os.Bundle
import android.view.LayoutInflater
@ -23,11 +23,11 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.lifecycle.lifecycleScope
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.framework.model.Source
import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.framework.model.DataSource
import com.rtbishop.look4sat.presentation.setNavResult
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@ -36,35 +36,37 @@ import javax.inject.Inject
class SourcesDialog : AppCompatDialogFragment() {
@Inject
lateinit var settings: ISettingsHandler
lateinit var repository: IDataRepository
private lateinit var binding: DialogSourcesBinding
override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? {
return inflater.inflate(R.layout.dialog_sources, group, false)
override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View {
binding = DialogSourcesBinding.inflate(inflater, group, false)
return binding.root
}
override fun onViewCreated(view: View, state: Bundle?) {
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(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT
)
sourcesRecycler.apply {
setHasFixedSize(true)
this.adapter = adapter
this.layoutManager = layoutManager
dialog?.window?.setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
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 {
setHasFixedSize(true)
this.adapter = adapter
this.layoutManager = layoutManager
}
sourcesBtnAdd.setOnClickListener { adapter.addSource() }
sourcesBtnNeg.setOnClickListener { dismiss() }
sourcesBtnPos.setOnClickListener {
setNavResult("sources", adapter.getSources().map { source -> source.url })
dismiss()
}
}
sourcesBtnAdd.setOnClickListener {
adapter.addSource()
}
sourcesBtnPos.setOnClickListener {
setNavResult("sources", adapter.getSources().map { it.sourceUrl })
dismiss()
}
sourcesBtnNeg.setOnClickListener { dismiss() }
}
}
}

Wyświetl plik

@ -52,7 +52,7 @@
</fragment>
<dialog
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" />
<action
android:id="@+id/action_global_entriesFragment"

Wyświetl plik

@ -1,16 +1,12 @@
package com.rtbishop.bbctestapp
package com.rtbishop.look4sat
import org.junit.Assert.assertEquals
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 {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
}

Wyświetl plik

@ -13,4 +13,4 @@ plugins {
task clean(type: Delete) {
delete rootProject.buildDir
}
}

Wyświetl plik

@ -18,4 +18,4 @@ dependencies {
testImplementation "junit:junit:4.13.2"
testImplementation "org.mockito:mockito-core:4.3.1"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0"
}
}

Wyświetl plik

@ -21,115 +21,99 @@ import com.rtbishop.look4sat.domain.DataParser
import com.rtbishop.look4sat.domain.IDataRepository
import com.rtbishop.look4sat.domain.model.DataState
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.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.io.InputStream
import java.util.zip.ZipInputStream
import kotlin.system.measureTimeMillis
class DataRepository(
private val dataParser: DataParser,
private val settings: ISettingsHandler,
private val localSource: ILocalSource,
private val remoteSource: IRemoteSource,
private val repositoryScope: CoroutineScope
) : IDataRepository {
private val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("DataRepository: handled $exception")
_updateState.value = DataState.Error(exception.message)
}
private val updateStateDelay = 875L
private val _updateState = MutableStateFlow<DataState<Long>>(DataState.Handled)
override val updateState: StateFlow<DataState<Long>> = _updateState
override suspend fun getAllSatellites(): List<SatItem> {
val selection = settings.loadSatelliteSelection()
val satellites = localSource.getAllSatellites()
satellites.forEach { satItem -> satItem.isSelected = satItem.catnum in selection }
return satellites
override fun setUpdateStateHandled() {
_updateState.value = DataState.Handled
}
override suspend fun getSelectedSatellites(): List<Satellite> {
val selection = settings.loadSatelliteSelection()
return localSource.getSelectedSatellites(selection)
}
override suspend fun getEntriesWithModes() = localSource.getEntriesWithModes()
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) {
repositoryScope.launch(exceptionHandler) {
_updateState.value = DataState.Loading
val updateTimeMillis = measureTimeMillis {
localSource.getFileStream(uri)?.let { fileStream ->
localSource.updateEntries(importSatellites(fileStream))
}
localSource.getFileStream(uri)?.let { fileStream ->
localSource.insertEntries(importSatellites(fileStream))
delay(updateStateDelay)
}
_updateState.value = DataState.Success(updateTimeMillis)
_updateState.value = DataState.Success(0L)
}
}
override fun updateFromWeb(sources: List<String>) {
_updateState.value = DataState.Loading
repositoryScope.launch(exceptionHandler) {
settings.saveDataSources(sources)
localSource.setDataSources(sources)
}
repositoryScope.launch(exceptionHandler) {
val updateTimeMillis = measureTimeMillis {
val jobsMap = mutableMapOf<String, Deferred<InputStream?>>()
val streamsMap = mutableMapOf<String, InputStream?>()
val streams = mutableListOf<InputStream>()
val entries = mutableListOf<SatEntry>()
sources.forEach { jobsMap[it] = async { remoteSource.fetchFileStream(it) } }
jobsMap.forEach { streamsMap[it.key] = it.value.await() }
streamsMap.forEach { stream ->
stream.value?.let { inputStream ->
when {
stream.key.contains("=csv", true) -> {
val tles = dataParser.parseCSVStream(inputStream)
entries.addAll(tles.map { tle -> SatEntry(tle) })
}
stream.key.contains(".zip", true) -> {
streams.add(ZipInputStream(inputStream).apply { nextEntry })
}
else -> streams.add(inputStream)
val jobsMap = mutableMapOf<String, Deferred<InputStream?>>()
val streamsMap = mutableMapOf<String, InputStream?>()
val streams = mutableListOf<InputStream>()
val entries = mutableListOf<SatEntry>()
sources.forEach { jobsMap[it] = async { remoteSource.getFileStream(it) } }
jobsMap.forEach { job -> streamsMap[job.key] = job.value.await() }
streamsMap.forEach { stream ->
stream.value?.let { inputStream ->
when {
stream.key.contains("=csv", true) -> {
val tles = dataParser.parseCSVStream(inputStream)
entries.addAll(tles.map { tle -> SatEntry(tle) })
}
stream.key.contains(".zip", true) -> {
streams.add(ZipInputStream(inputStream).apply { nextEntry })
}
else -> streams.add(inputStream)
}
}
streams.forEach { stream -> entries.addAll(importSatellites(stream)) }
localSource.updateEntries(entries)
}
println("Update from web took $updateTimeMillis ms")
_updateState.value = DataState.Success(updateTimeMillis)
streams.forEach { stream -> entries.addAll(importSatellites(stream)) }
localSource.insertEntries(entries)
_updateState.value = DataState.Success(0L)
}
repositoryScope.launch(exceptionHandler) {
remoteSource.fetchFileStream(settings.transmittersSource)?.let { stream ->
val transmitters = dataParser.parseJSONStream(stream)
localSource.updateTransmitters(transmitters)
remoteSource.getFileStream(remoteSource.radioApi)?.let { stream ->
localSource.insertRadios(dataParser.parseJSONStream(stream))
}
}
}
override fun updatesSelection(catnums: List<Int>) {
repositoryScope.launch {
settings.saveSatelliteSelection(catnums)
}
}
override fun setUpdateStateHandled() {
_updateState.value = DataState.Handled
}
override fun clearAllData() {
repositoryScope.launch {
_updateState.value = DataState.Loading
localSource.clearAllData()
delay(updateStateDelay)
_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> {
return dataParser.parseTLEStream(stream).map { tle -> SatEntry(tle) }
}

Wyświetl plik

@ -19,23 +19,31 @@ package com.rtbishop.look4sat.data
import com.rtbishop.look4sat.domain.model.SatEntry
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 java.io.InputStream
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 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 getDataSources(): List<String>
suspend fun setDataSources(sources: List<String>)
suspend fun getEntriesSelection(): List<Int>
suspend fun setEntriesSelection(catnums: List<Int>)
}

Wyświetl plik

@ -21,5 +21,7 @@ import java.io.InputStream
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?
}

Wyświetl plik

@ -28,8 +28,6 @@ interface ISettingsHandler {
"https://celestrak.com/NORAD/elements/gp.php?GROUP=active&FORMAT=csv",
"https://amsat.org/tle/current/nasabare.txt"
)
val transmittersSource: String
get() = "https://db.satnogs.org/api/transmitters/?format=json"
fun loadStationPosition(): GeoPos
@ -59,9 +57,9 @@ interface ISettingsHandler {
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

Wyświetl plik

@ -17,8 +17,8 @@
*/
package com.rtbishop.look4sat.domain
import com.rtbishop.look4sat.domain.model.Transmitter
import com.rtbishop.look4sat.domain.predict.TLE
import com.rtbishop.look4sat.domain.model.SatRadio
import com.rtbishop.look4sat.domain.predict.OrbitalData
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import org.json.JSONArray
@ -28,8 +28,8 @@ import kotlin.math.pow
class DataParser(private val parserDispatcher: CoroutineDispatcher) {
suspend fun parseCSVStream(csvStream: InputStream): List<TLE> = withContext(parserDispatcher) {
val parsedItems = mutableListOf<TLE>()
suspend fun parseCSVStream(csvStream: InputStream): List<OrbitalData> = withContext(parserDispatcher) {
val parsedItems = mutableListOf<OrbitalData>()
csvStream.bufferedReader().useLines { lines ->
lines.forEachIndexed { index, line ->
if (index != 0) {
@ -41,9 +41,9 @@ class DataParser(private val parserDispatcher: CoroutineDispatcher) {
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 parsedItems = mutableListOf<TLE>()
val parsedItems = mutableListOf<OrbitalData>()
var lineIndex = 0
tleStream.bufferedReader().forEachLine { line ->
tleStrings[lineIndex] = line
@ -60,9 +60,9 @@ class DataParser(private val parserDispatcher: CoroutineDispatcher) {
return@withContext parsedItems
}
suspend fun parseJSONStream(jsonStream: InputStream): List<Transmitter> {
suspend fun parseJSONStream(jsonStream: InputStream): List<SatRadio> {
return withContext(parserDispatcher) {
val parsedItems = mutableListOf<Transmitter>()
val parsedItems = mutableListOf<SatRadio>()
try {
val jsonArray = JSONArray(jsonStream.bufferedReader().readText())
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 {
val name = values[0]
val year = values[2].substring(0, 4)
@ -98,13 +98,13 @@ class DataParser(private val parserDispatcher: CoroutineDispatcher) {
val meanan = values[8].toDouble()
val catnum = values[11].toInt()
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) {
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") {
return null
}
@ -120,13 +120,13 @@ class DataParser(private val parserDispatcher: CoroutineDispatcher) {
val catnum: Int = tle[1].substring(2, 7).trim().toInt()
val bstar: Double = 1.0e-5 * tle[1].substring(53, 59).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) {
return null
}
}
private fun parseJSON(json: JSONObject): Transmitter? {
private fun parseJSON(json: JSONObject): SatRadio? {
try {
val uuid = json.getString("uuid")
val info = json.getString("description")
@ -140,7 +140,7 @@ class DataParser(private val parserDispatcher: CoroutineDispatcher) {
val isInverted = json.getBoolean("invert")
val catnum = if (json.isNull("norad_cat_id")) null
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) {
return null
}

Wyświetl plik

@ -19,7 +19,7 @@ package com.rtbishop.look4sat.domain
import com.rtbishop.look4sat.domain.model.DataState
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 kotlinx.coroutines.flow.StateFlow
@ -27,19 +27,21 @@ interface IDataRepository {
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 updateFromWeb(sources: List<String>)
fun updatesSelection(catnums: List<Int>)
fun setUpdateStateHandled()
fun clearAllData()
suspend fun getDataSources(): List<String>
fun setEntriesSelection(catnums: List<Int>)
}

Wyświetl plik

@ -17,9 +17,6 @@
*/
package com.rtbishop.look4sat.domain.model
import com.rtbishop.look4sat.domain.predict.TLE
import com.rtbishop.look4sat.domain.predict.OrbitalData
data class SatEntry(
val tle: TLE,
var isSelected: Boolean = false
)
data class SatEntry(val data: OrbitalData, var comment: String? = null)

Wyświetl plik

@ -20,6 +20,6 @@ package com.rtbishop.look4sat.domain.model
data class SatItem(
val catnum: Int,
val name: String,
var isSelected: Boolean,
val modes: List<String>
val modes: List<String>,
var isSelected: Boolean
)

Wyświetl plik

@ -17,7 +17,7 @@
*/
package com.rtbishop.look4sat.domain.model
data class Transmitter(
data class SatRadio(
val uuid: String,
val info: String,
val isAlive: Boolean,
@ -25,5 +25,6 @@ data class Transmitter(
var uplink: Long?,
val mode: String?,
val isInverted: Boolean,
val catnum: Int?
val catnum: Int?,
var comment: String? = null
)

Wyświetl plik

@ -19,7 +19,7 @@ package com.rtbishop.look4sat.domain.predict
import kotlin.math.*
class DeepSpaceSat(params: TLE) : Satellite(params) {
class DeepSpaceSat(data: OrbitalData) : Satellite(data) {
private val c1: Double
private val c4: Double
@ -35,41 +35,41 @@ class DeepSpaceSat(params: TLE) : Satellite(params) {
init {
// Recover original mean motion (xnodp) and semimajor axis (aodp) from input elements
val a1 = (xke / super.params.xno).pow(twoThirds)
dsv.cosio = cos(super.params.xincl)
val a1 = (xke / super.data.xno).pow(twoThirds)
dsv.cosio = cos(super.data.xincl)
dsv.theta2 = dsv.cosio * dsv.cosio
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.betao = sqrt(dsv.betao2)
val del1 = 1.5 * ck2 * x3thm1 / (a1 * a1 * dsv.betao * dsv.betao2)
val ao = a1 * (1.0 - del1 * (0.5 * twoThirds + del1 * (1.0 + 134.0 / 81.0 * del1)))
val delo = 1.5 * ck2 * x3thm1 / (ao * ao * dsv.betao * dsv.betao2)
dsv.xnodp = super.params.xno / (1.0 + delo)
dsv.xnodp = super.data.xno / (1.0 + delo)
dsv.aodp = ao / (1.0 - delo)
// For perigee below 156 km, the values of S and QOMS2T are altered
setPerigee((dsv.aodp * (1.0 - super.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)
dsv.sing = sin(super.params.omegao)
dsv.cosg = cos(super.params.omegao)
dsv.sing = sin(super.data.omegao)
dsv.cosg = cos(super.data.omegao)
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 eeta = super.params.eccn * eta
val eeta = super.data.eccn * eta
val psisq = abs(1.0 - etasq)
val coef = qoms24 * tsi.pow(4.0)
val coef1 = coef / psisq.pow(3.5)
val c2 = coef1 * dsv.xnodp * (dsv.aodp * (1.0 + 1.5 * etasq + eeta * (4.0 + etasq))
+ 0.75 * ck2 * tsi / psisq * x3thm1 * (8.0 + 3.0 * etasq * (8.0 + etasq)))
c1 = super.params.bstar * c2
dsv.sinio = sin(super.params.xincl)
c1 = super.data.bstar * c2
dsv.sinio = sin(super.data.xincl)
val a3ovk2 = -j3Harmonic / ck2
x1mth2 = 1.0 - dsv.theta2
c4 =
2 * dsv.xnodp * coef1 * dsv.aodp * dsv.betao2 * (eta * (2.0 + 0.5 * etasq) + super.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)
* (-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 temp1 = 3.0 * ck2 * pinvsq * dsv.xnodp
val temp2 = temp1 * ck2 * pinvsq
@ -93,18 +93,18 @@ class DeepSpaceSat(params: TLE) : Satellite(params) {
internal fun calculateSDP4(tSince: Double) {
synchronized(this) {
val temp = DoubleArray(12)
val xmdf = params.xmo + dsv.xmdot * tSince
val xmdf = data.xmo + dsv.xmdot * tSince
val tsq = tSince * tSince
val templ = t2cof * tsq
dsv.xll = xmdf + dsv.xnodp * templ
dsv.omgadf = params.omegao + dsv.omgdot * tSince
val xnoddf = params.xnodeo + dsv.xnodot * tSince
dsv.omgadf = data.omegao + dsv.omgdot * tSince
val xnoddf = data.xnodeo + dsv.xnodot * tSince
dsv.xnode = xnoddf + xnodcf * tsq
val tempa = 1.0 - c1 * tSince
val tempe = params.bstar * c4 * tSince
val tempe = data.bstar * c4 * tSince
dsv.xn = dsv.xnodp
dsv.t = tSince
deep.dpsec(params)
deep.dpsec(data)
val a = (xke / dsv.xn).pow(twoThirds) * tempa * tempa
dsv.em = dsv.em - tempe
deep.dpper()
@ -425,16 +425,16 @@ class DeepSpaceSat(params: TLE) : Satellite(params) {
private var epochRestart = false
init {
thgr = thetaG(params.epoch)
eq = params.eccn
thgr = thetaG(data.epoch)
eq = data.eccn
xnq = dsv.xnodp
aqnv = invert(dsv.aodp)
xqncl = params.xincl
xmao = params.xmo
xqncl = data.xincl
xmao = data.xmo
xpidot = dsv.omgdot + dsv.xnodot
sinq = sin(params.xnodeo)
cosq = cos(params.xnodeo)
omegaq = params.omegao
sinq = sin(data.xnodeo)
cosq = cos(data.xnodeo)
omegaq = data.omegao
// Initialize lunar solar terms, days since 1900 Jan 0.5
day = dsv.ds50 + 18261.5
if (abs(day - preep) > 1.0E-6) {
@ -534,7 +534,7 @@ class DeepSpaceSat(params: TLE) : Satellite(params) {
temp = 2.0 * temp1 * root54
d5421 = temp * f542 * g521
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 += ssl + ssh + ssh
} else {
@ -556,7 +556,7 @@ class DeepSpaceSat(params: TLE) : Satellite(params) {
fasx2 = 0.13130908
fasx4 = 2.8843198
fasx6 = 0.37448087
xlamo = xmao + params.xnodeo + params.omegao - thgr
xlamo = xmao + data.xnodeo + data.omegao - thgr
bfact = dsv.xmdot + xpidot - tHdt
bfact += ssl + ssg + ssh
}
@ -623,7 +623,7 @@ class DeepSpaceSat(params: TLE) : Satellite(params) {
}
// Entrance for deep space secular effects
fun dpsec(params: TLE) {
fun dpsec(params: OrbitalData) {
dsv.xll = dsv.xll + ssl * dsv.t
dsv.omgadf = dsv.omgadf + ssg * dsv.t
dsv.xnode = dsv.xnode + ssh * dsv.t

Wyświetl plik

@ -19,7 +19,7 @@ package com.rtbishop.look4sat.domain.predict
import kotlin.math.*
class NearEarthSat(params: TLE) : Satellite(params) {
class NearEarthSat(data: OrbitalData) : Satellite(data) {
private val aodp: Double
private val aycof: Double
@ -53,18 +53,18 @@ class NearEarthSat(params: TLE) : Satellite(params) {
init {
// Recover original mean motion (xnodp) and semimajor axis (aodp) from input elements
val a1 = (xke / super.params.xno).pow(twoThirds)
cosio = cos(super.params.xincl)
val a1 = (xke / super.data.xno).pow(twoThirds)
cosio = cos(super.data.xincl)
val theta2 = sqr(cosio)
x3thm1 = 3.0 * theta2 - 1.0
val eo = super.params.eccn
val eo = super.data.eccn
val eosq = sqr(eo)
val betao2 = 1.0 - eosq
val betao = sqrt(betao2)
val del1 = 1.5 * ck2 * x3thm1 / (sqr(a1) * betao * betao2)
val ao = a1 * (1.0 - del1 * (0.5 * twoThirds + del1 * (1.0 + 134.0 / 81.0 * del1)))
val delo = 1.5 * ck2 * x3thm1 / (sqr(ao) * betao * betao2)
xnodp = super.params.xno / (1.0 + delo)
xnodp = super.data.xno / (1.0 + delo)
aodp = ao / (1.0 - delo)
// For perigee less than 220 kilometers, the "simple" flag is set
@ -80,15 +80,15 @@ class NearEarthSat(params: TLE) : Satellite(params) {
val psisq = abs(1.0 - etasq)
val coef = qoms24 * tsi.pow(4.0)
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
* ck2 * tsi / psisq * x3thm1 * (8.0 + 3.0 * etasq * (8.0 + etasq)))
c1 = bstar * c2
sinio = sin(super.params.xincl)
sinio = sin(super.data.xincl)
val a3ovk2 = -j3Harmonic / ck2
val c3 = coef * tsi * a3ovk2 * xnodp * sinio / eo
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)
- 2 * ck2 * tsi / (aodp * psisq) * (-3 * x3thm1 * (1.0 - 2 * eeta + etasq
* (1.5 - 0.5 * eeta)) + 0.75 * x1mth2 * (2.0 * etasq - eeta * (1.0 + etasq))
@ -112,7 +112,7 @@ class NearEarthSat(params: TLE) : Satellite(params) {
t2cof = 1.5 * c1
xlcof = 0.125 * a3ovk2 * sinio * (3.0 + 5 * cosio) / (1.0 + cosio)
aycof = 0.25 * a3ovk2 * sinio
val xmo = super.params.xmo
val xmo = super.data.xmo
delmo = (1.0 + eta * cos(xmo)).pow(3.0)
sinmo = sin(xmo)
x7thm1 = 7.0 * theta2 - 1
@ -138,14 +138,14 @@ class NearEarthSat(params: TLE) : Satellite(params) {
internal fun calculateSGP4(tSince: Double) {
synchronized(this) {
val temp = DoubleArray(9)
val xmdf = params.xmo + xmdot * tSince
val omgadf = params.omegao + omgdot * tSince
val xnoddf = params.xnodeo + xnodot * tSince
val xmdf = data.xmo + xmdot * tSince
val omgadf = data.omegao + omgdot * tSince
val xnoddf = data.xnodeo + xnodot * tSince
var omega = omgadf
var xmp = xmdf
val tsq = sqr(tSince)
val xnode = xnoddf + xnodcf * tsq
val bstar = params.bstar
val bstar = data.bstar
var tempa = 1.0 - c1 * tSince
var tempe = bstar * c4 * tSince
var templ = t2cof * tsq
@ -162,7 +162,7 @@ class NearEarthSat(params: TLE) : Satellite(params) {
templ += t3cof * tcube + tfour * (t4cof + tSince * t5cof)
}
val a = aodp * tempa.pow(2.0)
val eo = params.eccn
val eo = data.eccn
val e = eo - tempe
val xl = xmp + omega + xnode + xnodp * templ
val beta = sqrt(1.0 - e * e)
@ -213,7 +213,7 @@ class NearEarthSat(params: TLE) : Satellite(params) {
val rk = r * (1.0 - 1.5 * temp[2] * betal * x3thm1) + 0.5 * temp[1] * x1mth2 * cos2u
val uk = u - 0.25 * temp[2] * x7thm1 * sin2u
val xnodek = xnode + 1.5 * temp[2] * cosio * sin2u
val xinck = 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 rfdotk = rfdot + xn * temp[1] * (x1mth2 * cos2u + 1.5 * x3thm1)
super.calculatePosAndVel(rk, uk, xnodek, xinck, rdotk, rfdotk)

Wyświetl plik

@ -17,7 +17,7 @@
*/
package com.rtbishop.look4sat.domain.predict
data class TLE(
data class OrbitalData(
val name: String,
val epoch: Double,
val meanmo: Double,

Wyświetl plik

@ -17,7 +17,7 @@
*/
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.flow.MutableSharedFlow
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) {
val satPos = sat.getPosition(pos, time)
val copiedList = radios.map { it.copy() }
@ -106,7 +106,7 @@ class Predictor(private val predictorDispatcher: CoroutineDispatcher) {
var lastAosDate: Long
var count = 0
if (this.willBeSeen(pos)) {
if (this.params.isDeepspace) {
if (this.data.isDeepspace) {
passes.add(getGeoPass(this, pos, time))
} else {
do {

Wyświetl plik

@ -29,7 +29,7 @@ data class SatPass(
val satellite: Satellite,
var progress: Int = 0
) {
val catNum: Int = satellite.params.catnum
val name: String = satellite.params.name
val isDeepSpace: Boolean = satellite.params.isDeepspace
val catNum: Int = satellite.data.catnum
val name: String = satellite.data.name
val isDeepSpace: Boolean = satellite.data.isDeepspace
}

Wyświetl plik

@ -19,7 +19,7 @@ package com.rtbishop.look4sat.domain.predict
import kotlin.math.*
abstract class Satellite(val params: TLE) {
abstract class Satellite(val data: OrbitalData) {
private val flatFactor = 3.35281066474748E-3
private val deg2Rad = 1.745329251994330E-2
@ -30,7 +30,7 @@ abstract class Satellite(val params: TLE) {
private val velocity = Vector4()
private var gsPosTheta = 0.0
private var perigee = 0.0
val orbitalPeriod = 24 * 60 / params.meanmo
val orbitalPeriod = 24 * 60 / data.meanmo
val earthRadius = 6378.137
val j3Harmonic = -2.53881E-6
val twoPi = Math.PI * 2.0
@ -42,11 +42,11 @@ abstract class Satellite(val params: TLE) {
var s4 = 0.0
internal fun willBeSeen(pos: GeoPos): Boolean {
return if (params.meanmo < 1e-8) false
return if (data.meanmo < 1e-8) false
else {
val sma = 331.25 * exp(ln(1440.0 / params.meanmo) * (2.0 / 3.0))
val apogee = sma * (1.0 + params.eccn) - earthRadius
var lin = params.incl
val sma = 331.25 * exp(ln(1440.0 / data.meanmo) * (2.0 / 3.0))
val apogee = sma * (1.0 + data.eccn) - earthRadius
var lin = data.incl
if (lin >= 90.0) lin = 180.0 - lin
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
val julUTC = calcCurrentDaynum(time) + 2444238.5
// 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
calculateSDP4orSGP4(tsince)
// 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) {
if (params.isDeepspace) (this as DeepSpaceSat).calculateSDP4(tsince)
if (data.isDeepspace) (this as DeepSpaceSat).calculateSDP4(tsince)
else (this as NearEarthSat).calculateSGP4(tsince)
}

Wyświetl plik

@ -21,4 +21,4 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
kapt.use.worker.api=true
kapt.use.worker.api=true

Wyświetl plik

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
zipStorePath=wrapper/dists

Wyświetl plik

@ -14,4 +14,4 @@ dependencyResolutionManagement {
}
rootProject.name = "Look4Sat"
include ":app"
include ":core"
include ":core"