Created a core module, basic clean architecture setup
This commit is contained in:
1 parent
5f7c3e3786
commit
3db474c763
14 files changed
+1883
-3
No files matched your search
@@ -51,6 +51,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':core')
|
||||
implementation "com.google.android.material:material:$material_version"
|
||||
implementation "androidx.constraintlayout:constraintlayout:$constraint_layout_version"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
|
||||
+4
-3
@@ -1,13 +1,14 @@
|
||||
buildscript {
|
||||
ext {
|
||||
gradle_version = '4.1.3'
|
||||
gradle_plugin_version = '1.4.32'
|
||||
kotlin_version = '1.4.32'
|
||||
coroutines_version = '1.4.1'
|
||||
material_version = '1.3.0'
|
||||
constraint_layout_version = '2.0.4'
|
||||
lifecycle_version = '2.3.1'
|
||||
navigation_version = '2.3.5'
|
||||
preference_version = '1.1.1'
|
||||
room_version = '2.2.6'
|
||||
room_version = '2.3.0'
|
||||
hilt_version = '2.33-beta'
|
||||
retrofit_version = '2.9.0'
|
||||
predict4java_version = '1.3.1'
|
||||
@@ -24,7 +25,7 @@ buildscript {
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:$gradle_version"
|
||||
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$gradle_plugin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,14 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'kotlin'
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
}
|
||||
@@ -0,0 +1,849 @@
|
||||
/*
|
||||
* 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.predict4kotlin
|
||||
|
||||
import kotlin.math.*
|
||||
|
||||
class DeepSpaceSat(tle: TLE) : Satellite(tle) {
|
||||
|
||||
private val c1: Double
|
||||
private val c4: Double
|
||||
private val x1mth2: Double
|
||||
private val x3thm1: Double
|
||||
private val xlcof: Double
|
||||
private val xnodcf: Double
|
||||
private val t2cof: Double
|
||||
private val aycof: Double
|
||||
private val x7thm1: Double
|
||||
private val deep: DeepSpaceCalculator
|
||||
private val dsv = DeepSpaceValueObject()
|
||||
|
||||
init {
|
||||
// Recover original mean motion (xnodp) and semimajor axis (aodp) from input elements
|
||||
val a1 = (xke / super.tle.xno).pow(twoThirds)
|
||||
dsv.cosio = cos(super.tle.xincl)
|
||||
dsv.theta2 = dsv.cosio * dsv.cosio
|
||||
x3thm1 = 3.0 * dsv.theta2 - 1
|
||||
dsv.eosq = super.tle.eccn * super.tle.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.tle.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.tle.eccn) - 1.0) * earthRadius)
|
||||
val pinvsq = invert(dsv.aodp * dsv.aodp * dsv.betao2 * dsv.betao2)
|
||||
dsv.sing = sin(super.tle.omegao)
|
||||
dsv.cosg = cos(super.tle.omegao)
|
||||
val tsi = invert(dsv.aodp - s4)
|
||||
val eta = dsv.aodp * super.tle.eccn * tsi
|
||||
val etasq = eta * eta
|
||||
val eeta = super.tle.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.tle.bstar * c2
|
||||
dsv.sinio = sin(super.tle.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.tle.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.tle.omegao))))
|
||||
val theta4 = dsv.theta2 * dsv.theta2
|
||||
val temp1 = 3.0 * ck2 * pinvsq * dsv.xnodp
|
||||
val temp2 = temp1 * ck2 * pinvsq
|
||||
val temp3 = 1.25 * ck4 * pinvsq * pinvsq * dsv.xnodp
|
||||
dsv.xmdot =
|
||||
dsv.xnodp + 0.5 * temp1 * dsv.betao * x3thm1 + 0.0625 * temp2 * dsv.betao * (13 - 78 * dsv.theta2 + 137 * theta4)
|
||||
val x1m5th = 1.0 - 5 * dsv.theta2
|
||||
dsv.omgdot =
|
||||
-0.5 * temp1 * x1m5th + 0.0625 * temp2 * (7.0 - 114 * dsv.theta2 + 395 * theta4) + temp3 * (3.0 - 36 * dsv.theta2 + 49 * theta4)
|
||||
val xhdot1 = -temp1 * dsv.cosio
|
||||
dsv.xnodot =
|
||||
xhdot1 + (0.5 * temp2 * (4.0 - 19 * dsv.theta2) + 2 * temp3 * (3.0 - 7 * dsv.theta2)) * dsv.cosio
|
||||
xnodcf = 3.5 * dsv.betao2 * xhdot1 * c1
|
||||
t2cof = 1.5 * c1
|
||||
xlcof = 0.125 * a3ovk2 * dsv.sinio * (3.0 + 5 * dsv.cosio) / (1.0 + dsv.cosio)
|
||||
aycof = 0.25 * a3ovk2 * dsv.sinio
|
||||
x7thm1 = 7.0 * dsv.theta2 - 1
|
||||
deep = DeepSpaceCalculator(dsv)
|
||||
}
|
||||
|
||||
fun calculateSDP4(tSince: Double) {
|
||||
synchronized(this) {
|
||||
val temp = DoubleArray(12)
|
||||
val xmdf = tle.xmo + dsv.xmdot * tSince
|
||||
val tsq = tSince * tSince
|
||||
val templ = t2cof * tsq
|
||||
dsv.xll = xmdf + dsv.xnodp * templ
|
||||
dsv.omgadf = tle.omegao + dsv.omgdot * tSince
|
||||
val xnoddf = tle.xnodeo + dsv.xnodot * tSince
|
||||
dsv.xnode = xnoddf + xnodcf * tsq
|
||||
val tempa = 1.0 - c1 * tSince
|
||||
val tempe = tle.bstar * c4 * tSince
|
||||
dsv.xn = dsv.xnodp
|
||||
dsv.t = tSince
|
||||
deep.dpsec(tle)
|
||||
val a = (xke / dsv.xn).pow(twoThirds) * tempa * tempa
|
||||
dsv.em = dsv.em - tempe
|
||||
deep.dpper()
|
||||
val xl = dsv.xll + dsv.omgadf + dsv.xnode
|
||||
val beta = sqrt(1.0 - dsv.em * dsv.em)
|
||||
dsv.xn = xke / a.pow(1.5)
|
||||
|
||||
// Long period periodics
|
||||
val axn = dsv.em * cos(dsv.omgadf)
|
||||
temp[0] = invert(a * beta * beta)
|
||||
val xll = temp[0] * xlcof * axn
|
||||
val aynl = temp[0] * aycof
|
||||
val xlt = xl + xll
|
||||
val ayn = dsv.em * sin(dsv.omgadf) + aynl
|
||||
|
||||
// Solve Kepler's equation
|
||||
val capu = mod2PI(xlt - dsv.xnode)
|
||||
temp[2] = capu
|
||||
converge(temp, axn, ayn, capu)
|
||||
calculatePosAndVel(temp, a, axn, ayn)
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculatePosAndVel(temp: DoubleArray, a: Double, axn: Double, ayn: Double) {
|
||||
val ecose = temp[5] + temp[6]
|
||||
val esine = temp[3] - temp[4]
|
||||
val elsq = axn * axn + ayn * ayn
|
||||
temp[0] = 1.0 - elsq
|
||||
val pl = a * temp[0]
|
||||
temp[9] = a * (1.0 - ecose)
|
||||
temp[1] = invert(temp[9])
|
||||
temp[10] = xke * sqrt(a) * esine * temp[1]
|
||||
temp[11] = xke * sqrt(pl) * temp[1]
|
||||
temp[2] = a * temp[1]
|
||||
val betal = sqrt(temp[0])
|
||||
temp[3] = invert(1.0 + betal)
|
||||
val cosu = temp[2] * (temp[8] - axn + ayn * esine * temp[3])
|
||||
val sinu = temp[2] * (temp[7] - ayn - axn * esine * temp[3])
|
||||
val u = atan2(sinu, cosu)
|
||||
val sin2u = 2.0 * sinu * cosu
|
||||
val cos2u = 2.0 * cosu * cosu - 1
|
||||
temp[0] = invert(pl)
|
||||
temp[1] = ck2 * temp[0]
|
||||
temp[2] = temp[1] * temp[0]
|
||||
|
||||
// Update for short periodics
|
||||
val rk = temp[9] * (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 = dsv.xnode + 1.5 * temp[2] * dsv.cosio * sin2u
|
||||
val xinck = dsv.xinc + 1.5 * temp[2] * dsv.cosio * dsv.sinio * cos2u
|
||||
val rdotk = temp[10] - dsv.xn * temp[1] * x1mth2 * sin2u
|
||||
val rfdotk = temp[11] + dsv.xn * temp[1] * (x1mth2 * cos2u + 1.5 * x3thm1)
|
||||
super.calculatePosAndVel(rk, uk, xnodek, xinck, rdotk, rfdotk)
|
||||
}
|
||||
|
||||
inner class DeepSpaceValueObject {
|
||||
var eosq = 0.0
|
||||
var sinio = 0.0
|
||||
var cosio = 0.0
|
||||
var betao = 0.0
|
||||
var aodp = 0.0
|
||||
var theta2 = 0.0
|
||||
var sing = 0.0
|
||||
var cosg = 0.0
|
||||
var betao2 = 0.0
|
||||
var xmdot = 0.0
|
||||
var omgdot = 0.0
|
||||
var xnodot = 0.0
|
||||
var xnodp = 0.0
|
||||
|
||||
// Used by dpsec and dpper parts of Deep()
|
||||
var xll = 0.0
|
||||
var omgadf = 0.0
|
||||
var xnode = 0.0
|
||||
var em = 0.0
|
||||
var xinc = 0.0
|
||||
var xn = 0.0
|
||||
var t = 0.0
|
||||
|
||||
// Used by thetg and Deep()
|
||||
var ds50 = 0.0
|
||||
}
|
||||
|
||||
inner class DeepSpaceCalculator(private val dsv: DeepSpaceValueObject) {
|
||||
|
||||
private val zSinis = 3.9785416E-1
|
||||
private val zSings = -9.8088458E-1
|
||||
private val zNs = 1.19459E-5
|
||||
private val c1ss = 2.9864797E-6
|
||||
private val zEs = 1.675E-2
|
||||
private val zNl = 1.5835218E-4
|
||||
private val c1l = 4.7968065E-7
|
||||
private val zEl = 5.490E-2
|
||||
private val root22 = 1.7891679E-6
|
||||
private val root32 = 3.7393792E-7
|
||||
private val root44 = 7.3636953E-9
|
||||
private val root52 = 1.1428639E-7
|
||||
private val root54 = 2.1765803E-9
|
||||
private val tHdt = 4.3752691E-3
|
||||
private val q22 = 1.7891679E-6
|
||||
private val q31 = 2.1460748E-6
|
||||
private val q33 = 2.2123015E-7
|
||||
private val g22 = 5.7686396
|
||||
private val g32 = 9.5240898E-1
|
||||
private val g44 = 1.8014998
|
||||
private val g52 = 1.0508330
|
||||
private val g54 = 4.4108898
|
||||
|
||||
private val thgr: Double
|
||||
private val xnq: Double
|
||||
private val xqncl: Double
|
||||
private val omegaq: Double
|
||||
private var zmol = 0.0
|
||||
private var zmos = 0.0
|
||||
|
||||
// Many fields below cannot be final because they are iteratively refined
|
||||
private var savtsn = 0.0
|
||||
private var ee2 = 0.0
|
||||
private var e3 = 0.0
|
||||
private var xi2 = 0.0
|
||||
private var xl2 = 0.0
|
||||
private var xl3 = 0.0
|
||||
private var xl4 = 0.0
|
||||
private var xgh2 = 0.0
|
||||
private var xgh3 = 0.0
|
||||
private var xgh4 = 0.0
|
||||
private var xh2 = 0.0
|
||||
private var xh3 = 0.0
|
||||
private var sse = 0.0
|
||||
private var ssi = 0.0
|
||||
private var ssg = 0.0
|
||||
private var xi3 = 0.0
|
||||
private var se2 = 0.0
|
||||
private var si2 = 0.0
|
||||
private var sl2 = 0.0
|
||||
private var sgh2 = 0.0
|
||||
private var sh2 = 0.0
|
||||
private var se3 = 0.0
|
||||
private var si3 = 0.0
|
||||
private var sl3 = 0.0
|
||||
private var sgh3 = 0.0
|
||||
private var sh3 = 0.0
|
||||
private var sl4 = 0.0
|
||||
private var sgh4 = 0.0
|
||||
private var ssl = 0.0
|
||||
private var ssh = 0.0
|
||||
private var d3210 = 0.0
|
||||
private var d3222 = 0.0
|
||||
private var d4410 = 0.0
|
||||
private var d4422 = 0.0
|
||||
private var d5220 = 0.0
|
||||
private var d5232 = 0.0
|
||||
private var d5421 = 0.0
|
||||
private var d5433 = 0.0
|
||||
private var del1 = 0.0
|
||||
private var del2 = 0.0
|
||||
private var del3 = 0.0
|
||||
private var fasx2 = 0.0
|
||||
private var fasx4 = 0.0
|
||||
private var fasx6 = 0.0
|
||||
private var xlamo = 0.0
|
||||
private val xfact: Double
|
||||
private var xni: Double
|
||||
private var atime: Double
|
||||
private val stepp: Double
|
||||
private val stepn: Double
|
||||
private val step2: Double
|
||||
private var preep = 0.0
|
||||
private var pl = 0.0
|
||||
private var sghs = 0.0
|
||||
private var xli: Double
|
||||
private var d2201 = 0.0
|
||||
private var d2211 = 0.0
|
||||
private var sghl = 0.0
|
||||
private var sh1 = 0.0
|
||||
private var pinc = 0.0
|
||||
private var pe = 0.0
|
||||
private var shs = 0.0
|
||||
private var zsingl = 0.0
|
||||
private var zcosgl = 0.0
|
||||
private var zsinhl = 0.0
|
||||
private var zcoshl = 0.0
|
||||
private var zsinil = 0.0
|
||||
private var zcosil = 0.0
|
||||
private var a1 = 0.0
|
||||
private var a2 = 0.0
|
||||
private var a3 = 0.0
|
||||
private var a4 = 0.0
|
||||
private var a5 = 0.0
|
||||
private var a6 = 0.0
|
||||
private var a7 = 0.0
|
||||
private var a8 = 0.0
|
||||
private var a9 = 0.0
|
||||
private var a10 = 0.0
|
||||
private var ainv2 = 0.0
|
||||
private var alfdp = 0.0
|
||||
private val aqnv: Double
|
||||
private var sgh = 0.0
|
||||
private var sini2 = 0.0
|
||||
private var sinis = 0.0
|
||||
private var sinok = 0.0
|
||||
private var sh = 0.0
|
||||
private var si = 0.0
|
||||
private var sil = 0.0
|
||||
private val day: Double
|
||||
private var betdp = 0.0
|
||||
private var dalf = 0.0
|
||||
private var bfact = 0.0
|
||||
private var c = 0.0
|
||||
private var cc = 0.0
|
||||
private var cosis = 0.0
|
||||
private var cosok = 0.0
|
||||
private val cosq: Double
|
||||
private var ctem = 0.0
|
||||
private var f322 = 0.0
|
||||
private var zx = 0.0
|
||||
private var zy = 0.0
|
||||
private var dbet = 0.0
|
||||
private var dls = 0.0
|
||||
private var eoc = 0.0
|
||||
private val eq: Double
|
||||
private var f2 = 0.0
|
||||
private var f220 = 0.0
|
||||
private var f221 = 0.0
|
||||
private var f3 = 0.0
|
||||
private var f311 = 0.0
|
||||
private var f321 = 0.0
|
||||
private var xnoh = 0.0
|
||||
private var f330 = 0.0
|
||||
private var f441 = 0.0
|
||||
private var f442 = 0.0
|
||||
private var f522 = 0.0
|
||||
private var f523 = 0.0
|
||||
private var f542 = 0.0
|
||||
private var f543 = 0.0
|
||||
private var g200 = 0.0
|
||||
private var g201 = 0.0
|
||||
private var g211 = 0.0
|
||||
private var pgh = 0.0
|
||||
private var ph = 0.0
|
||||
private var s1 = 0.0
|
||||
private var s2 = 0.0
|
||||
private var s3 = 0.0
|
||||
private var s4 = 0.0
|
||||
private var s5 = 0.0
|
||||
private var s6 = 0.0
|
||||
private var s7 = 0.0
|
||||
private var se = 0.0
|
||||
private var sel = 0.0
|
||||
private var ses = 0.0
|
||||
private var xls = 0.0
|
||||
private var g300 = 0.0
|
||||
private var g310 = 0.0
|
||||
private var g322 = 0.0
|
||||
private var g410 = 0.0
|
||||
private var g422 = 0.0
|
||||
private var g520 = 0.0
|
||||
private var g521 = 0.0
|
||||
private var g532 = 0.0
|
||||
private var g533 = 0.0
|
||||
private var gam = 0.0
|
||||
private val sinq: Double
|
||||
private var sinzf = 0.0
|
||||
private var sis = 0.0
|
||||
private var sl = 0.0
|
||||
private var sll = 0.0
|
||||
private var sls = 0.0
|
||||
private var stem = 0.0
|
||||
private var temp = 0.0
|
||||
private var temp1 = 0.0
|
||||
private var x1 = 0.0
|
||||
private var x2 = 0.0
|
||||
private var x2li = 0.0
|
||||
private var x2omi = 0.0
|
||||
private var x3 = 0.0
|
||||
private var x4 = 0.0
|
||||
private var x5 = 0.0
|
||||
private var x6 = 0.0
|
||||
private var x7 = 0.0
|
||||
private var x8 = 0.0
|
||||
private var xl = 0.0
|
||||
private var xldot = 0.0
|
||||
private val xmao: Double
|
||||
private var xnddt = 0.0
|
||||
private var xndot = 0.0
|
||||
private var xno2 = 0.0
|
||||
private var xnodce = 0.0
|
||||
private var xnoi = 0.0
|
||||
private var xomi = 0.0
|
||||
private val xpidot: Double
|
||||
private var z1 = 0.0
|
||||
private var z11 = 0.0
|
||||
private var z12 = 0.0
|
||||
private var z13 = 0.0
|
||||
private var z2 = 0.0
|
||||
private var z21 = 0.0
|
||||
private var z22 = 0.0
|
||||
private var z23 = 0.0
|
||||
private var z3 = 0.0
|
||||
private var z31 = 0.0
|
||||
private var z32 = 0.0
|
||||
private var z33 = 0.0
|
||||
private var ze = 0.0
|
||||
private var zf = 0.0
|
||||
private var zm = 0.0
|
||||
private var zn = 0.0
|
||||
private var zsing = 0.0
|
||||
private var zsinh = 0.0
|
||||
private var zsini = 0.0
|
||||
private var zcosg = 0.0
|
||||
private var zcosh = 0.0
|
||||
private var zcosi = 0.0
|
||||
private var delt = 0.0
|
||||
private var ft = 0.0
|
||||
private var resonance: Boolean
|
||||
private var synchronous: Boolean
|
||||
private var doLoop = false
|
||||
private var epochRestart = false
|
||||
|
||||
init {
|
||||
thgr = thetaG(tle.epoch)
|
||||
eq = tle.eccn
|
||||
xnq = dsv.xnodp
|
||||
aqnv = invert(dsv.aodp)
|
||||
xqncl = tle.xincl
|
||||
xmao = tle.xmo
|
||||
xpidot = dsv.omgdot + dsv.xnodot
|
||||
sinq = sin(tle.xnodeo)
|
||||
cosq = cos(tle.xnodeo)
|
||||
omegaq = tle.omegao
|
||||
// Initialize lunar solar terms, days since 1900 Jan 0.5
|
||||
day = dsv.ds50 + 18261.5
|
||||
if (abs(day - preep) > 1.0E-6) {
|
||||
preep = day
|
||||
xnodce = 4.5236020 - 9.2422029E-4 * day
|
||||
stem = sin(xnodce)
|
||||
ctem = cos(xnodce)
|
||||
zcosil = 0.91375164 - 0.03568096 * ctem
|
||||
zsinil = sqrt(1.0 - zcosil * zcosil)
|
||||
zsinhl = 0.089683511 * stem / zsinil
|
||||
zcoshl = sqrt(1.0 - zsinhl * zsinhl)
|
||||
c = 4.7199672 + 0.22997150 * day
|
||||
gam = 5.8351514 + 0.0019443680 * day
|
||||
zmol = mod2PI(c - gam)
|
||||
zx = 0.39785416 * stem / zsinil
|
||||
zy = zcoshl * ctem + 0.91744867 * zsinhl * stem
|
||||
zx = atan2(zx, zy)
|
||||
zx = gam + zx - xnodce
|
||||
zcosgl = cos(zx)
|
||||
zsingl = sin(zx)
|
||||
zmos = mod2PI(6.2565837 + 0.017201977 * day)
|
||||
} else {
|
||||
zmol = 0.0
|
||||
zmos = 0.0
|
||||
}
|
||||
doSolarTerms()
|
||||
|
||||
// Geopotential resonance initialization for 12 hour orbits
|
||||
resonance = false
|
||||
synchronous = false
|
||||
if (!(xnq < 0.0052359877 && xnq > 0.0034906585)) {
|
||||
if (xnq < 0.00826 || xnq > 0.00924)
|
||||
if (eq < 0.5)
|
||||
// calculateResonance
|
||||
resonance = true
|
||||
eoc = eq * dsv.eosq
|
||||
g201 = -0.306 - (eq - 0.64) * 0.440
|
||||
if (eq <= 0.65) {
|
||||
g211 = 3.616 - 13.247 * eq + 16.290 * dsv.eosq
|
||||
g310 = -19.302 + 117.390 * eq - 228.419 * dsv.eosq + 156.591 * eoc
|
||||
g322 = -18.9068 + 109.7927 * eq - 214.6334 * dsv.eosq + 146.5816 * eoc
|
||||
g410 = -41.122 + 242.694 * eq - 471.094 * dsv.eosq + 313.953 * eoc
|
||||
g422 = -146.407 + 841.880 * eq - 1629.014 * dsv.eosq + 1083.435 * eoc
|
||||
g520 = -532.114 + 3017.977 * eq - 5740 * dsv.eosq + 3708.276 * eoc
|
||||
} else {
|
||||
g211 = -72.099 + 331.819 * eq - 508.738 * dsv.eosq + 266.724 * eoc
|
||||
g310 = -346.844 + 1582.851 * eq - 2415.925 * dsv.eosq + 1246.113 * eoc
|
||||
g322 = -342.585 + 1554.908 * eq - 2366.899 * dsv.eosq + 1215.972 * eoc
|
||||
g410 = -1052.797 + 4758.686 * eq - 7193.992 * dsv.eosq + 3651.957 * eoc
|
||||
g422 = -3581.69 + 16178.11 * eq - 24462.77 * dsv.eosq + 12422.52 * eoc
|
||||
g520 =
|
||||
if (eq <= 0.715) 1464.74 - 4664.75 * eq + 3763.64 * dsv.eosq
|
||||
else -5149.66 + 29936.92 * eq - 54087.36 * dsv.eosq + 31324.56 * eoc
|
||||
}
|
||||
if (eq < 0.7) {
|
||||
g533 = -919.2277 + 4988.61 * eq - 9064.77 * dsv.eosq + 5542.21 * eoc
|
||||
g521 = -822.71072 + 4568.6173 * eq - 8491.4146 * dsv.eosq + 5337.524 * eoc
|
||||
g532 = -853.666 + 4690.25 * eq - 8624.77 * dsv.eosq + 5341.4 * eoc
|
||||
} else {
|
||||
g533 = -37995.78 + 161616.52 * eq - 229838.2 * dsv.eosq + 109377.94 * eoc
|
||||
g521 = -51752.104 + 218913.95 * eq - 309468.16 * dsv.eosq + 146349.42 * eoc
|
||||
g532 = -40023.88 + 170470.89 * eq - 242699.48 * dsv.eosq + 115605.82 * eoc
|
||||
}
|
||||
sini2 = dsv.sinio * dsv.sinio
|
||||
f220 = 0.75 * (1.0 + 2 * dsv.cosio + dsv.theta2)
|
||||
f221 = 1.5 * sini2
|
||||
f321 = 1.875 * dsv.sinio * (1.0 - 2 * dsv.cosio - 3.0 * dsv.theta2)
|
||||
f322 = -1.875 * dsv.sinio * (1.0 + 2 * dsv.cosio - 3.0 * dsv.theta2)
|
||||
f441 = 35 * sini2 * f220
|
||||
f442 = 39.3750 * sini2 * sini2
|
||||
f522 =
|
||||
9.84375 * dsv.sinio * (sini2 * (1.0 - 2 * dsv.cosio - 5 * dsv.theta2) + 0.33333333 * (-2 + 4 * dsv.cosio + 6 * dsv.theta2))
|
||||
f523 =
|
||||
dsv.sinio * (4.92187512 * sini2 * (-2 - 4 * dsv.cosio + 10 * dsv.theta2) + 6.56250012 * (1.0 + 2 * dsv.cosio - 3.0 * dsv.theta2))
|
||||
f542 =
|
||||
29.53125 * dsv.sinio * (2.0 - 8 * dsv.cosio + dsv.theta2 * (-12 + 8 * dsv.cosio + 10 * dsv.theta2))
|
||||
f543 =
|
||||
29.53125 * dsv.sinio * (-2 - 8 * dsv.cosio + dsv.theta2 * (12 + 8 * dsv.cosio - 10 * dsv.theta2))
|
||||
xno2 = xnq * xnq
|
||||
ainv2 = aqnv * aqnv
|
||||
temp1 = 3.0 * xno2 * ainv2
|
||||
temp = temp1 * root22
|
||||
d2201 = temp * f220 * g201
|
||||
d2211 = temp * f221 * g211
|
||||
temp1 *= aqnv
|
||||
temp = temp1 * root32
|
||||
d3210 = temp * f321 * g310
|
||||
d3222 = temp * f322 * g322
|
||||
temp1 *= aqnv
|
||||
temp = 2.0 * temp1 * root44
|
||||
d4410 = temp * f441 * g410
|
||||
d4422 = temp * f442 * g422
|
||||
temp1 *= aqnv
|
||||
temp = temp1 * root52
|
||||
d5220 = temp * f522 * g520
|
||||
d5232 = temp * f523 * g532
|
||||
temp = 2.0 * temp1 * root54
|
||||
d5421 = temp * f542 * g521
|
||||
d5433 = temp * f543 * g533
|
||||
xlamo = xmao + tle.xnodeo + tle.xnodeo - thgr - thgr
|
||||
bfact = dsv.xmdot + dsv.xnodot + dsv.xnodot - tHdt - tHdt
|
||||
bfact += ssl + ssh + ssh
|
||||
} else {
|
||||
// Init synchronous resonance terms
|
||||
resonance = true
|
||||
synchronous = true
|
||||
g200 = 1.0 + dsv.eosq * (-2.5 + 0.8125 * dsv.eosq)
|
||||
g310 = 1.0 + 2 * dsv.eosq
|
||||
g300 = 1.0 + dsv.eosq * (-6 + 6.60937 * dsv.eosq)
|
||||
f220 = 0.75 * (1.0 + dsv.cosio) * (1.0 + dsv.cosio)
|
||||
f311 =
|
||||
0.9375 * dsv.sinio * dsv.sinio * (1.0 + 3.0 * dsv.cosio) - 0.75 * (1.0 + dsv.cosio)
|
||||
f330 = 1.0 + dsv.cosio
|
||||
f330 *= 1.875 * f330 * f330
|
||||
del1 = 3.0 * xnq * xnq * aqnv * aqnv
|
||||
del2 = 2.0 * del1 * f220 * g200 * q22
|
||||
del3 = 3.0 * del1 * f330 * g300 * q33 * aqnv
|
||||
del1 *= f311 * g310 * q31 * aqnv
|
||||
fasx2 = 0.13130908
|
||||
fasx4 = 2.8843198
|
||||
fasx6 = 0.37448087
|
||||
xlamo = xmao + tle.xnodeo + tle.omegao - thgr
|
||||
bfact = dsv.xmdot + xpidot - tHdt
|
||||
bfact += ssl + ssg + ssh
|
||||
}
|
||||
xfact = bfact - xnq
|
||||
// Init integrator
|
||||
xli = xlamo
|
||||
xni = xnq
|
||||
atime = 0.0
|
||||
stepp = 720.0
|
||||
stepn = -720.0
|
||||
step2 = 259200.0
|
||||
}
|
||||
|
||||
// Entrance for lunar-solar periodics
|
||||
fun dpper() {
|
||||
sinis = sin(dsv.xinc)
|
||||
cosis = cos(dsv.xinc)
|
||||
if (abs(savtsn - dsv.t) >= 30) {
|
||||
savtsn = dsv.t
|
||||
zm = zmos + zNs * dsv.t
|
||||
zf = zm + 2 * zEs * sin(zm)
|
||||
sinzf = sin(zf)
|
||||
f2 = 0.5 * sinzf * sinzf - 0.25
|
||||
f3 = -0.5 * sinzf * cos(zf)
|
||||
ses = se2 * f2 + se3 * f3
|
||||
sis = si2 * f2 + si3 * f3
|
||||
sls = sl2 * f2 + sl3 * f3 + sl4 * sinzf
|
||||
sghs = sgh2 * f2 + sgh3 * f3 + sgh4 * sinzf
|
||||
shs = sh2 * f2 + sh3 * f3
|
||||
zm = zmol + zNl * dsv.t
|
||||
zf = zm + 2 * zEl * sin(zm)
|
||||
sinzf = sin(zf)
|
||||
f2 = 0.5 * sinzf * sinzf - 0.25
|
||||
f3 = -0.5 * sinzf * cos(zf)
|
||||
sel = ee2 * f2 + e3 * f3
|
||||
sil = xi2 * f2 + xi3 * f3
|
||||
sll = xl2 * f2 + xl3 * f3 + xl4 * sinzf
|
||||
sghl = xgh2 * f2 + xgh3 * f3 + xgh4 * sinzf
|
||||
sh1 = xh2 * f2 + xh3 * f3
|
||||
pe = ses + sel
|
||||
pinc = sis + sil
|
||||
pl = sls + sll
|
||||
}
|
||||
pgh = sghs + sghl
|
||||
ph = shs + sh1
|
||||
dsv.xinc = dsv.xinc + pinc
|
||||
dsv.em = dsv.em + pe
|
||||
if (xqncl >= 0.2) {
|
||||
/* Apply periodics directly */
|
||||
ph /= dsv.sinio
|
||||
pgh -= dsv.cosio * ph
|
||||
dsv.omgadf = dsv.omgadf + pgh
|
||||
dsv.xnode = dsv.xnode + ph
|
||||
dsv.xll = dsv.xll + pl
|
||||
} else {
|
||||
applyPeriodics()
|
||||
// This is a patch to Lyddane modification suggested by Rob Matson
|
||||
if (abs(xnoh - dsv.xnode) > Math.PI) {
|
||||
if (dsv.xnode < xnoh) dsv.xnode += twoPi else dsv.xnode -= twoPi
|
||||
}
|
||||
dsv.xll = dsv.xll + pl
|
||||
dsv.omgadf = xls - dsv.xll - cos(dsv.xinc) * dsv.xnode
|
||||
}
|
||||
}
|
||||
|
||||
// Entrance for deep space secular effects
|
||||
fun dpsec(tle: TLE) {
|
||||
dsv.xll = dsv.xll + ssl * dsv.t
|
||||
dsv.omgadf = dsv.omgadf + ssg * dsv.t
|
||||
dsv.xnode = dsv.xnode + ssh * dsv.t
|
||||
dsv.em = tle.eccn + sse * dsv.t
|
||||
dsv.xinc = tle.xincl + ssi * dsv.t
|
||||
if (dsv.xinc < 0) {
|
||||
dsv.xinc = -dsv.xinc
|
||||
dsv.xnode = dsv.xnode + Math.PI
|
||||
dsv.omgadf = dsv.omgadf - Math.PI
|
||||
}
|
||||
if (!resonance) return
|
||||
do processEpochRestartLoop() while (doLoop && epochRestart)
|
||||
dsv.xn = xni + xndot * ft + xnddt * ft * ft * 0.5
|
||||
xl = xli + xldot * ft + xndot * ft * ft * 0.5
|
||||
temp = -dsv.xnode + thgr + dsv.t * tHdt
|
||||
if (synchronous) dsv.xll = xl - dsv.omgadf + temp else dsv.xll = xl + temp + temp
|
||||
}
|
||||
|
||||
private fun doSolarTerms() {
|
||||
savtsn = 1E20
|
||||
zcosg = 1.945905E-1
|
||||
zsing = zSings
|
||||
zcosi = 9.1744867E-1
|
||||
zsini = zSinis
|
||||
zcosh = cosq
|
||||
zsinh = sinq
|
||||
cc = c1ss
|
||||
zn = zNs
|
||||
ze = zEs
|
||||
xnoi = invert(xnq)
|
||||
calculateSolarTerms()
|
||||
calculateLunarTerms()
|
||||
calculateSolarTerms() // Solar terms done again after Lunar terms are done
|
||||
sse += se
|
||||
ssi += si
|
||||
ssl += sl
|
||||
ssg = ssg + sgh - dsv.cosio / dsv.sinio * sh
|
||||
ssh += sh / dsv.sinio
|
||||
}
|
||||
|
||||
private fun calculateLunarTerms() {
|
||||
sse = se
|
||||
ssi = si
|
||||
ssl = sl
|
||||
ssh = sh / dsv.sinio
|
||||
ssg = sgh - dsv.cosio * ssh
|
||||
se2 = ee2
|
||||
si2 = xi2
|
||||
sl2 = xl2
|
||||
sgh2 = xgh2
|
||||
sh2 = xh2
|
||||
se3 = e3
|
||||
si3 = xi3
|
||||
sl3 = xl3
|
||||
sgh3 = xgh3
|
||||
sh3 = xh3
|
||||
sl4 = xl4
|
||||
sgh4 = xgh4
|
||||
zcosg = zcosgl
|
||||
zsing = zsingl
|
||||
zcosi = zcosil
|
||||
zsini = zsinil
|
||||
zcosh = zcoshl * cosq + zsinhl * sinq
|
||||
zsinh = sinq * zcoshl - cosq * zsinhl
|
||||
zn = zNl
|
||||
cc = c1l
|
||||
ze = zEl
|
||||
}
|
||||
|
||||
private fun calculateSolarTerms() {
|
||||
a1 = zcosg * zcosh + zsing * zcosi * zsinh
|
||||
a3 = -zsing * zcosh + zcosg * zcosi * zsinh
|
||||
a7 = -zcosg * zsinh + zsing * zcosi * zcosh
|
||||
a8 = zsing * zsini
|
||||
a9 = zsing * zsinh + zcosg * zcosi * zcosh
|
||||
a10 = zcosg * zsini
|
||||
a2 = dsv.cosio * a7 + dsv.sinio * a8
|
||||
a4 = dsv.cosio * a9 + dsv.sinio * a10
|
||||
a5 = -dsv.sinio * a7 + dsv.cosio * a8
|
||||
a6 = -dsv.sinio * a9 + dsv.cosio * a10
|
||||
x1 = a1 * dsv.cosg + a2 * dsv.sing
|
||||
x2 = a3 * dsv.cosg + a4 * dsv.sing
|
||||
x3 = -a1 * dsv.sing + a2 * dsv.cosg
|
||||
x4 = -a3 * dsv.sing + a4 * dsv.cosg
|
||||
x5 = a5 * dsv.sing
|
||||
x6 = a6 * dsv.sing
|
||||
x7 = a5 * dsv.cosg
|
||||
x8 = a6 * dsv.cosg
|
||||
z31 = 12 * x1 * x1 - 3.0 * x3 * x3
|
||||
z32 = 24 * x1 * x2 - 6 * x3 * x4
|
||||
z33 = 12 * x2 * x2 - 3.0 * x4 * x4
|
||||
z1 = 3.0 * (a1 * a1 + a2 * a2) + z31 * dsv.eosq
|
||||
z2 = 6.0 * (a1 * a3 + a2 * a4) + z32 * dsv.eosq
|
||||
z3 = 3.0 * (a3 * a3 + a4 * a4) + z33 * dsv.eosq
|
||||
z11 = -6 * a1 * a5 + dsv.eosq * (-24 * x1 * x7 - 6 * x3 * x5)
|
||||
z12 =
|
||||
-6 * (a1 * a6 + a3 * a5) + dsv.eosq * (-24 * (x2 * x7 + x1 * x8) - 6 * (x3 * x6 + x4 * x5))
|
||||
z13 = -6 * a3 * a6 + dsv.eosq * (-24 * x2 * x8 - 6 * x4 * x6)
|
||||
z21 = 6.0 * a2 * a5 + dsv.eosq * (24 * x1 * x5 - 6 * x3 * x7)
|
||||
z22 =
|
||||
6.0 * (a4 * a5 + a2 * a6) + dsv.eosq * (24 * (x2 * x5 + x1 * x6) - 6 * (x4 * x7 + x3 * x8))
|
||||
z23 = 6.0 * a4 * a6 + dsv.eosq * (24 * x2 * x6 - 6 * x4 * x8)
|
||||
z1 += z1 + dsv.betao2 * z31
|
||||
z2 += z2 + dsv.betao2 * z32
|
||||
z3 += z3 + dsv.betao2 * z33
|
||||
s3 = cc * xnoi
|
||||
s2 = -0.5 * s3 / dsv.betao
|
||||
s4 = s3 * dsv.betao
|
||||
s1 = -15 * eq * s4
|
||||
s5 = x1 * x3 + x2 * x4
|
||||
s6 = x2 * x3 + x1 * x4
|
||||
s7 = x2 * x4 - x1 * x3
|
||||
se = s1 * zn * s5
|
||||
si = s2 * zn * (z11 + z13)
|
||||
sl = -zn * s3 * (z1 + z3 - 14 - 6 * dsv.eosq)
|
||||
sgh = s4 * zn * (z31 + z33 - 6)
|
||||
sh = -zn * s2 * (z21 + z23)
|
||||
if (xqncl < 5.2359877E-2) sh = 0.0
|
||||
ee2 = 2.0 * s1 * s6
|
||||
e3 = 2.0 * s1 * s7
|
||||
xi2 = 2.0 * s2 * z12
|
||||
xi3 = 2.0 * s2 * (z13 - z11)
|
||||
xl2 = -2 * s3 * z2
|
||||
xl3 = -2 * s3 * (z3 - z1)
|
||||
xl4 = -2 * s3 * (-21 - 9 * dsv.eosq) * ze
|
||||
xgh2 = 2.0 * s4 * z32
|
||||
xgh3 = 2.0 * s4 * (z33 - z31)
|
||||
xgh4 = -18 * s4 * ze
|
||||
xh2 = -2 * s2 * z22
|
||||
xh3 = -2 * s2 * (z23 - z21)
|
||||
}
|
||||
|
||||
private fun processEpochRestartLoop() {
|
||||
if (atime == 0.0 || dsv.t >= 0 && atime < 0 || dsv.t < 0 && atime >= 0) {
|
||||
calculateDelta()
|
||||
atime = 0.0
|
||||
xni = xnq
|
||||
xli = xlamo
|
||||
} else if (abs(dsv.t) >= abs(atime)) calculateDelta()
|
||||
processNotEpochRestartLoop()
|
||||
}
|
||||
|
||||
private fun calculateDelta() {
|
||||
delt = if (dsv.t < 0) stepn else stepp
|
||||
}
|
||||
|
||||
private fun processNotEpochRestartLoop() {
|
||||
do {
|
||||
if (abs(dsv.t - atime) >= stepp) {
|
||||
doLoop = true
|
||||
epochRestart = false
|
||||
} else {
|
||||
ft = dsv.t - atime
|
||||
doLoop = false
|
||||
}
|
||||
if (abs(dsv.t) < abs(atime)) {
|
||||
delt = if (dsv.t >= 0) stepn else stepp
|
||||
doLoop = doLoop or epochRestart
|
||||
}
|
||||
if (synchronous) {
|
||||
xndot = del1 * sin(xli - fasx2) + del2 * sin(2.0 * (xli - fasx4))
|
||||
+del3 * sin(3.0 * (xli - fasx6))
|
||||
xnddt = del1 * cos(xli - fasx2) + 2 * del2 * cos(2.0 * (xli - fasx4))
|
||||
+3.0 * del3 * cos(3.0 * (xli - fasx6))
|
||||
} else {
|
||||
xomi = omegaq + dsv.omgdot * atime
|
||||
x2omi = xomi + xomi
|
||||
x2li = xli + xli
|
||||
xndot =
|
||||
d2201 * sin(x2omi + xli - g22) + d2211 * sin(xli - g22) + (d3210
|
||||
* sin(xomi + xli - g32)) + d3222 * sin(-xomi + xli - g32) + (d4410
|
||||
* sin(x2omi + x2li - g44)) + d4422 * sin(x2li - g44) + (d5220
|
||||
* sin(xomi + xli - g52)) + d5232 * sin(-xomi + xli - g52) + (d5421
|
||||
* sin(xomi + x2li - g54)) + d5433 * sin(-xomi + x2li - g54)
|
||||
xnddt =
|
||||
d2201 * cos(x2omi + xli - g22) + d2211 * cos(xli - g22) + (d3210
|
||||
* cos(xomi + xli - g32)) + d3222 * cos(-xomi + xli - g32) + (d5220
|
||||
* cos(xomi + xli - g52)) + d5232 * cos(-xomi + xli - g52) + (2
|
||||
* (d4410 * cos(x2omi + x2li - g44) + d4422 * cos(x2li - g44) + (d5421
|
||||
* cos(xomi + x2li - g54)) + d5433 * cos(-xomi + x2li - g54)))
|
||||
}
|
||||
xldot = xni + xfact
|
||||
xnddt *= xldot
|
||||
if (doLoop) {
|
||||
xli += xldot * delt + xndot * step2
|
||||
xni += xndot * delt + xnddt * step2
|
||||
atime += delt
|
||||
}
|
||||
} while (doLoop && !epochRestart)
|
||||
}
|
||||
|
||||
// Apply periodics with Lyddane modification
|
||||
private fun applyPeriodics() {
|
||||
sinok = sin(dsv.xnode)
|
||||
cosok = cos(dsv.xnode)
|
||||
alfdp = sinis * sinok
|
||||
betdp = sinis * cosok
|
||||
dalf = ph * cosok + pinc * cosis * sinok
|
||||
dbet = -ph * sinok + pinc * cosis * cosok
|
||||
alfdp += dalf
|
||||
betdp += dbet
|
||||
dsv.xnode = mod2PI(dsv.xnode)
|
||||
xls = dsv.xll + dsv.omgadf + cosis * dsv.xnode
|
||||
dls = pl + pgh - pinc * dsv.xnode * sinis
|
||||
xls += dls
|
||||
xnoh = dsv.xnode
|
||||
dsv.xnode = atan2(alfdp, betdp)
|
||||
}
|
||||
|
||||
// Calculates the Greenwich Mean Sidereal Time for an epoch, valid 1957 through 2056
|
||||
private fun thetaG(epoch: Double): Double {
|
||||
var year = floor(epoch * 1E-3)
|
||||
var dayOfYear = (epoch * 1E-3 - year) * 1000.0
|
||||
year = if (year < 57) year + 2000 else year + 1900
|
||||
val dayFloor = floor(dayOfYear)
|
||||
val dayFraction = dayOfYear - dayFloor
|
||||
dayOfYear = dayFloor
|
||||
val jd = julianDateOfYear(year) + dayOfYear
|
||||
dsv.ds50 = jd - 2433281.5 + dayFraction
|
||||
return mod2PI(6.3003880987 * dsv.ds50 + 1.72944494)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.predict4kotlin
|
||||
|
||||
data class GroundPos(val lat: Double, val lon: Double, val alt: Double, val name: String = "base")
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* 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.predict4kotlin
|
||||
|
||||
import kotlin.math.*
|
||||
|
||||
class NearEarthSat(tle: TLE) : Satellite(tle) {
|
||||
|
||||
private val aodp: Double
|
||||
private val aycof: Double
|
||||
private val c1: Double
|
||||
private val c4: Double
|
||||
private val c5: Double
|
||||
private val cosio: Double
|
||||
private var d2 = 0.0
|
||||
private var d3 = 0.0
|
||||
private var d4 = 0.0
|
||||
private val delmo: Double
|
||||
private val omgcof: Double
|
||||
private val eta: Double
|
||||
private val omgdot: Double
|
||||
private val sinio: Double
|
||||
private val xnodp: Double
|
||||
private val sinmo: Double
|
||||
private val t2cof: Double
|
||||
private var t3cof = 0.0
|
||||
private var t4cof = 0.0
|
||||
private var t5cof = 0.0
|
||||
private val x1mth2: Double
|
||||
private val x3thm1: Double
|
||||
private val x7thm1: Double
|
||||
private val xmcof: Double
|
||||
private val xmdot: Double
|
||||
private val xnodcf: Double
|
||||
private val xnodot: Double
|
||||
private val xlcof: Double
|
||||
private val sgp4Simple: Boolean
|
||||
|
||||
init {
|
||||
// Recover original mean motion (xnodp) and semimajor axis (aodp) from input elements
|
||||
val a1 = (xke / super.tle.xno).pow(twoThirds)
|
||||
cosio = cos(super.tle.xincl)
|
||||
val theta2 = sqr(cosio)
|
||||
x3thm1 = 3.0 * theta2 - 1.0
|
||||
val eo = super.tle.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.tle.xno / (1.0 + delo)
|
||||
aodp = ao / (1.0 - delo)
|
||||
|
||||
// For perigee less than 220 kilometers, the "simple" flag is set
|
||||
sgp4Simple = aodp * (1.0 - eo) < 220 / earthRadius + 1.0
|
||||
|
||||
// For perigees below 156 km, the values of S and QOMS2T are altered
|
||||
setPerigee((aodp * (1.0 - eo) - 1.0) * earthRadius)
|
||||
val pinvsq = invert(sqr(aodp) * sqr(betao2))
|
||||
val tsi = invert(aodp - s4)
|
||||
eta = aodp * eo * tsi
|
||||
val etasq = eta * eta
|
||||
val eeta = eo * eta
|
||||
val psisq = abs(1.0 - etasq)
|
||||
val coef = qoms24 * tsi.pow(4.0)
|
||||
val coef1 = coef / psisq.pow(3.5)
|
||||
val bstar = super.tle.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.tle.xincl)
|
||||
val a3ovk2 = -j3Harmonic / ck2
|
||||
val c3 = coef * tsi * a3ovk2 * xnodp * sinio / eo
|
||||
x1mth2 = 1.0 - theta2
|
||||
val omegao = super.tle.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))
|
||||
* cos(2.0 * omegao)))
|
||||
c5 = 2.0 * coef1 * aodp * betao2 * (1.0 + 2.75 * (etasq + eeta) + eeta * etasq)
|
||||
val theta4 = sqr(theta2)
|
||||
val temp1 = 3.0 * ck2 * pinvsq * xnodp
|
||||
val temp2 = temp1 * ck2 * pinvsq
|
||||
val temp3 = 1.25 * ck4 * pinvsq * pinvsq * xnodp
|
||||
xmdot =
|
||||
xnodp + 0.5 * temp1 * betao * x3thm1 + (0.0625 * temp2 * betao * (13.0 - 78.0 * theta2 + 137.0 * theta4))
|
||||
val x1m5th = 1.0 - 5.0 * theta2
|
||||
omgdot =
|
||||
-0.5 * temp1 * x1m5th + 0.0625 * temp2 * (7.0 - 114.0 * theta2 + 395.0 * theta4) + temp3 * (3.0 - 36.0 * theta2 + 49.0 * theta4)
|
||||
val xhdot1 = -temp1 * cosio
|
||||
xnodot =
|
||||
xhdot1 + (0.5 * temp2 * (4.0 - 19.0 * theta2) + 2.0 * temp3 * (3.0 - 7.0 * theta2)) * cosio
|
||||
omgcof = bstar * c3 * cos(omegao)
|
||||
xmcof = -twoThirds * coef * bstar / eeta
|
||||
xnodcf = 3.5 * betao2 * xhdot1 * c1
|
||||
t2cof = 1.5 * c1
|
||||
xlcof = 0.125 * a3ovk2 * sinio * (3.0 + 5 * cosio) / (1.0 + cosio)
|
||||
aycof = 0.25 * a3ovk2 * sinio
|
||||
val xmo = super.tle.xmo
|
||||
delmo = (1.0 + eta * cos(xmo)).pow(3.0)
|
||||
sinmo = sin(xmo)
|
||||
x7thm1 = 7.0 * theta2 - 1
|
||||
if (!sgp4Simple) {
|
||||
val c1sq = sqr(c1)
|
||||
d2 = 4.0 * aodp * tsi * c1sq
|
||||
val temp = d2 * tsi * c1 / 3.0
|
||||
d3 = (17 * aodp + s4) * temp
|
||||
d4 = 0.5 * temp * aodp * tsi * (221 * aodp + 31 * s4) * c1
|
||||
t3cof = d2 + 2 * c1sq
|
||||
t4cof = 0.25 * (3.0 * d3 + c1 * (12 * d2 + 10 * c1sq))
|
||||
t5cof = 0.2 * (3.0 * d4 + 12 * c1 * d3 + 6 * d2 * d2 + 15 * c1sq * (2.0 * d2 + c1sq))
|
||||
} else {
|
||||
d2 = 0.0
|
||||
d3 = 0.0
|
||||
d4 = 0.0
|
||||
t3cof = 0.0
|
||||
t4cof = 0.0
|
||||
t5cof = 0.0
|
||||
}
|
||||
}
|
||||
|
||||
fun calculateSGP4(tSince: Double) {
|
||||
synchronized(this) {
|
||||
val temp = DoubleArray(9)
|
||||
val xmdf = tle.xmo + xmdot * tSince
|
||||
val omgadf = tle.omegao + omgdot * tSince
|
||||
val xnoddf = tle.xnodeo + xnodot * tSince
|
||||
var omega = omgadf
|
||||
var xmp = xmdf
|
||||
val tsq = sqr(tSince)
|
||||
val xnode = xnoddf + xnodcf * tsq
|
||||
val bstar = tle.bstar
|
||||
var tempa = 1.0 - c1 * tSince
|
||||
var tempe = bstar * c4 * tSince
|
||||
var templ = t2cof * tsq
|
||||
if (!sgp4Simple) {
|
||||
val delomg = omgcof * tSince
|
||||
val delm = xmcof * ((1.0 + eta * cos(xmdf)).pow(3.0) - delmo)
|
||||
temp[0] = delomg + delm
|
||||
xmp = xmdf + temp[0]
|
||||
omega = omgadf - temp[0]
|
||||
val tcube = tsq * tSince
|
||||
val tfour = tSince * tcube
|
||||
tempa = tempa - d2 * tsq - d3 * tcube - d4 * tfour
|
||||
tempe += bstar * c5 * (sin(xmp) - sinmo)
|
||||
templ += t3cof * tcube + tfour * (t4cof + tSince * t5cof)
|
||||
}
|
||||
val a = aodp * tempa.pow(2.0)
|
||||
val eo = tle.eccn
|
||||
val e = eo - tempe
|
||||
val xl = xmp + omega + xnode + xnodp * templ
|
||||
val beta = sqrt(1.0 - e * e)
|
||||
val xn = xke / a.pow(1.5)
|
||||
|
||||
// Long period periodics
|
||||
val axn = e * cos(omega)
|
||||
temp[0] = invert(a * sqr(beta))
|
||||
val xll = temp[0] * xlcof * axn
|
||||
val aynl = temp[0] * aycof
|
||||
val xlt = xl + xll
|
||||
val ayn = e * sin(omega) + aynl
|
||||
|
||||
// Solve Kepler's equation
|
||||
val capu = mod2PI(xlt - xnode)
|
||||
temp[2] = capu
|
||||
converge(temp, axn, ayn, capu)
|
||||
calculatePosAndVel(temp, xnode, a, xn, axn, ayn)
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculatePosAndVel(
|
||||
temp: DoubleArray, xnode: Double, a: Double,
|
||||
xn: Double, axn: Double, ayn: Double
|
||||
) {
|
||||
val ecose = temp[5] + temp[6]
|
||||
val esine = temp[3] - temp[4]
|
||||
val elsq = sqr(axn) + sqr(ayn)
|
||||
temp[0] = 1.0 - elsq
|
||||
val pl = a * temp[0]
|
||||
val r = a * (1.0 - ecose)
|
||||
temp[1] = invert(r)
|
||||
val rdot = xke * sqrt(a) * esine * temp[1]
|
||||
val rfdot = xke * sqrt(pl) * temp[1]
|
||||
temp[2] = a * temp[1]
|
||||
val betal = sqrt(temp[0])
|
||||
temp[3] = invert(1.0 + betal)
|
||||
val cosu = temp[2] * (temp[8] - axn + ayn * esine * temp[3])
|
||||
val sinu = temp[2] * (temp[7] - ayn - axn * esine * temp[3])
|
||||
val u = atan2(sinu, cosu)
|
||||
val sin2u = 2.0 * sinu * cosu
|
||||
val cos2u = 2.0 * cosu * cosu - 1
|
||||
temp[0] = invert(pl)
|
||||
temp[1] = ck2 * temp[0]
|
||||
temp[2] = temp[1] * temp[0]
|
||||
|
||||
// Update for short periodics
|
||||
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 = tle.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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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.predict4kotlin
|
||||
|
||||
import java.util.*
|
||||
|
||||
class PassPredictor(private val satellite: Satellite, private val qth: GroundPos) {
|
||||
|
||||
private val oneQuarterOrbitMin = (24.0 * 60.0 / satellite.tle.meanmo / 4.0).toInt()
|
||||
private val speedOfLight = 2.99792458E8
|
||||
|
||||
fun getDownlinkFreq(freq: Long, date: Date): Long {
|
||||
val rangeRate = getSatPos(date).rangeRate
|
||||
return (freq.toDouble() * (speedOfLight - rangeRate * 1000.0) / speedOfLight).toLong()
|
||||
}
|
||||
|
||||
fun getUplinkFreq(freq: Long, date: Date): Long {
|
||||
val rangeRate = getSatPos(date).rangeRate
|
||||
return (freq.toDouble() * (speedOfLight + rangeRate * 1000.0) / speedOfLight).toLong()
|
||||
}
|
||||
|
||||
fun getSatPos(date: Date): SatPos {
|
||||
return satellite.getPosition(qth, date)
|
||||
}
|
||||
|
||||
fun getPositions(refDate: Date, stepSec: Int, minBefore: Int, orbits: Double): List<SatPos> {
|
||||
val positions = mutableListOf<SatPos>()
|
||||
val orbitalPeriod = 24 * 60 / satellite.tle.meanmo
|
||||
val endDate = Date(refDate.time + (orbitalPeriod * orbits * 60L * 1000L).toLong())
|
||||
val startDate = Date(refDate.time - minBefore * 60L * 1000L)
|
||||
var currentDate = startDate
|
||||
while (currentDate.before(endDate)) {
|
||||
positions.add(getSatPos(currentDate))
|
||||
currentDate = Date(currentDate.time + stepSec * 1000)
|
||||
}
|
||||
return positions
|
||||
}
|
||||
|
||||
fun getPasses(refDate: Date, hoursAhead: Int, windBack: Boolean): List<SatPass> {
|
||||
val passes = mutableListOf<SatPass>()
|
||||
val endDate = Date(refDate.time + hoursAhead * 60L * 60L * 1000L)
|
||||
var startDate = refDate
|
||||
var shouldWindBack = windBack
|
||||
var lastAosDate: Date
|
||||
var count = 0
|
||||
if (satellite.willBeSeen(qth)) {
|
||||
if (satellite.tle.isDeepspace) {
|
||||
passes.add(nextDeepSpacePass(refDate))
|
||||
} else {
|
||||
do {
|
||||
if (count > 0) shouldWindBack = false
|
||||
val pass = nextNearEarthPass(startDate, shouldWindBack)
|
||||
lastAosDate = pass.aosDate
|
||||
passes.add(pass)
|
||||
startDate =
|
||||
Date(pass.losDate.time + (oneQuarterOrbitMin * 3) * 60L * 1000L)
|
||||
count++
|
||||
} while (lastAosDate < endDate)
|
||||
}
|
||||
}
|
||||
return passes
|
||||
}
|
||||
|
||||
private fun nextDeepSpacePass(refDate: Date): SatPass {
|
||||
val satPos = getSatPos(refDate)
|
||||
val id = satellite.tle.catnum
|
||||
val name = satellite.tle.name
|
||||
val isDeep = satellite.tle.isDeepspace
|
||||
val aos = Date(refDate.time - 24 * 60L * 60L * 1000L).time
|
||||
val los = Date(refDate.time + 24 * 60L * 60L * 1000L).time
|
||||
val tca = Date((aos + los) / 2).time
|
||||
val az = Math.toDegrees(satPos.azimuth)
|
||||
val elev = Math.toDegrees(satPos.elevation)
|
||||
val alt = satPos.altitude
|
||||
return SatPass(id, name, isDeep, aos, az, los, az, tca, az, alt, elev, this)
|
||||
}
|
||||
|
||||
private fun nextNearEarthPass(refDate: Date, windBack: Boolean = false): SatPass {
|
||||
val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply {
|
||||
clear()
|
||||
timeInMillis = refDate.time
|
||||
}
|
||||
val id = satellite.tle.catnum
|
||||
val name = satellite.tle.name
|
||||
val isDeep = satellite.tle.isDeepspace
|
||||
|
||||
var elevation: Double
|
||||
var maxElevation = 0.0
|
||||
var alt = 0.0
|
||||
var tcaAz = 0.0
|
||||
|
||||
// wind back time 1/4 of an orbit
|
||||
if (windBack) calendar.add(Calendar.MINUTE, -oneQuarterOrbitMin)
|
||||
var satPos = getSatPos(calendar.time)
|
||||
|
||||
if (satPos.elevation > 0.0) {
|
||||
// move forward in 30 second intervals until the sat goes below the horizon
|
||||
do {
|
||||
calendar.add(Calendar.SECOND, 30)
|
||||
satPos = getSatPos(calendar.time)
|
||||
} while (satPos.elevation > 0.0)
|
||||
// move forward 3/4 of an orbit
|
||||
calendar.add(Calendar.MINUTE, oneQuarterOrbitMin * 3)
|
||||
}
|
||||
|
||||
// find the next time sat comes above the horizon
|
||||
do {
|
||||
calendar.add(Calendar.SECOND, 60)
|
||||
satPos = getSatPos(calendar.time)
|
||||
elevation = satPos.elevation
|
||||
if (elevation > maxElevation) {
|
||||
maxElevation = elevation
|
||||
alt = satPos.altitude
|
||||
tcaAz = Math.toDegrees(satPos.azimuth)
|
||||
}
|
||||
} while (satPos.elevation < 0.0)
|
||||
|
||||
// refine to 3 seconds
|
||||
calendar.add(Calendar.SECOND, -60)
|
||||
do {
|
||||
calendar.add(Calendar.SECOND, 3)
|
||||
satPos = getSatPos(calendar.time)
|
||||
elevation = satPos.elevation
|
||||
if (elevation > maxElevation) {
|
||||
maxElevation = elevation
|
||||
alt = satPos.altitude
|
||||
tcaAz = Math.toDegrees(satPos.azimuth)
|
||||
}
|
||||
} while (satPos.elevation < 0.0)
|
||||
|
||||
val aos = satPos.time.time
|
||||
val aosAz = Math.toDegrees(satPos.azimuth)
|
||||
|
||||
// find when sat goes below
|
||||
do {
|
||||
calendar.add(Calendar.SECOND, 30)
|
||||
satPos = getSatPos(calendar.time)
|
||||
elevation = satPos.elevation
|
||||
if (elevation > maxElevation) {
|
||||
maxElevation = elevation
|
||||
alt = satPos.altitude
|
||||
tcaAz = Math.toDegrees(satPos.azimuth)
|
||||
}
|
||||
} while (satPos.elevation > 0.0)
|
||||
|
||||
// refine to 3 seconds
|
||||
calendar.add(Calendar.SECOND, -30)
|
||||
do {
|
||||
calendar.add(Calendar.SECOND, 3)
|
||||
satPos = getSatPos(calendar.time)
|
||||
elevation = satPos.elevation
|
||||
if (elevation > maxElevation) {
|
||||
maxElevation = elevation
|
||||
alt = satPos.altitude
|
||||
tcaAz = Math.toDegrees(satPos.azimuth)
|
||||
}
|
||||
} while (satPos.elevation > 0.0)
|
||||
|
||||
val los = satPos.time.time
|
||||
val losAz = Math.toDegrees(satPos.azimuth)
|
||||
val tca = Date((aos + los) / 2).time
|
||||
val elev = Math.toDegrees(maxElevation)
|
||||
return SatPass(id, name, isDeep, aos, aosAz, los, losAz, tca, tcaAz, alt, elev, this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.predict4kotlin
|
||||
|
||||
data class Position(val lat: Double, val lon: Double)
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.predict4kotlin
|
||||
|
||||
import java.util.*
|
||||
|
||||
data class SatPass(
|
||||
val catNum: Int,
|
||||
val name: String,
|
||||
val isDeepSpace: Boolean,
|
||||
private val aosTime: Long,
|
||||
val aosAzimuth: Double,
|
||||
private val losTime: Long,
|
||||
val losAzimuth: Double,
|
||||
private val tcaTime: Long,
|
||||
val tcaAzimuth: Double,
|
||||
val altitude: Double,
|
||||
val maxElevation: Double,
|
||||
val predictor: PassPredictor,
|
||||
var progress: Int = 0
|
||||
) {
|
||||
val aosDate: Date
|
||||
get() = Date(aosTime)
|
||||
|
||||
val losDate: Date
|
||||
get() = Date(losTime)
|
||||
|
||||
val tcaDate: Date
|
||||
get() = Date(tcaTime)
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.predict4kotlin
|
||||
|
||||
import java.util.*
|
||||
import kotlin.math.*
|
||||
|
||||
class SatPos {
|
||||
|
||||
private val earthRadiusKm = 6.378137E3
|
||||
private val r0 = 6378.16
|
||||
|
||||
// Radians
|
||||
var azimuth = 0.0
|
||||
var elevation = 0.0
|
||||
var latitude = 0.0
|
||||
var longitude = 0.0
|
||||
|
||||
var time = Date()
|
||||
var range = 0.0
|
||||
var rangeRate = 0.0
|
||||
var phase = 0.0
|
||||
var altitude = 0.0
|
||||
var theta = 0.0
|
||||
var eclipseDepth = 0.0
|
||||
var eclipsed = false
|
||||
var aboveHorizon = false
|
||||
|
||||
fun getDate(): Date {
|
||||
return Date(time.time)
|
||||
}
|
||||
|
||||
fun getRangeCircleRadiusKm(): Double {
|
||||
return 0.5 * (12756.33 * acos(earthRadiusKm / (earthRadiusKm + altitude)))
|
||||
}
|
||||
|
||||
fun getRangeCircle(incrementDegrees: Double = 1.0): List<Position> {
|
||||
val positions = mutableListOf<Position>()
|
||||
val radiusKm = this.getRangeCircleRadiusKm()
|
||||
val lat = this.latitude
|
||||
val lon = this.longitude
|
||||
val beta = radiusKm / r0
|
||||
var tempAzimuth = 0
|
||||
while (tempAzimuth < 360) {
|
||||
val azimuth = tempAzimuth / 360.0 * 2.0 * Math.PI
|
||||
var rangelat = asin(sin(lat) * cos(beta) + cos(azimuth) * sin(beta) * cos(lat))
|
||||
val num = (cos(beta) - (sin(lat) * sin(rangelat)))
|
||||
val den = cos(lat) * cos(rangelat)
|
||||
var rangelon = if (tempAzimuth == 0 && (beta > ((Math.PI / 2.0) - lat))) {
|
||||
lon + Math.PI
|
||||
} else if (tempAzimuth == 180 && (beta > ((Math.PI / 2.0) - lat))) {
|
||||
lon + Math.PI
|
||||
} else if (abs(num / den) > 1.0) {
|
||||
lon
|
||||
} else {
|
||||
if ((180 - tempAzimuth) >= 0) {
|
||||
lon - acos(num / den)
|
||||
} else {
|
||||
lon + acos(num / den)
|
||||
}
|
||||
}
|
||||
while (rangelon < 0.0) rangelon += Math.PI * 2.0
|
||||
while (rangelon > Math.PI * 2.0) rangelon -= Math.PI * 2.0
|
||||
rangelat = (rangelat / (2.0 * Math.PI)) * 360.0
|
||||
rangelon = (rangelon / (2.0 * Math.PI)) * 360.0
|
||||
|
||||
// if (rangelong < 180.0) {
|
||||
// rangelong = -rangelong;
|
||||
// }
|
||||
// else if (rangelong > 180.0) {
|
||||
// rangelong = 360.0 - rangelong;
|
||||
// }
|
||||
//
|
||||
// if (rangelat < 90.0) {
|
||||
// rangelat = -rangelat;
|
||||
// }
|
||||
// else if (rangelat > 90.0) {
|
||||
// rangelat = 180.0 - rangelat;
|
||||
// }
|
||||
positions.add(Position(rangelat, rangelon))
|
||||
tempAzimuth += incrementDegrees.toInt()
|
||||
}
|
||||
return positions
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
/*
|
||||
* 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.predict4kotlin
|
||||
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.math.*
|
||||
|
||||
abstract class Satellite(val tle: TLE) {
|
||||
|
||||
private val flatFactor = 3.35281066474748E-3
|
||||
private val deg2Rad = 1.745329251994330E-2
|
||||
private val secPerDay = 8.6400E4
|
||||
private val minPerDay = 1.44E3
|
||||
private val epsilon = 1.0E-12
|
||||
private val position = Vector4()
|
||||
private val velocity = Vector4()
|
||||
private var perigee = 0.0
|
||||
val earthRadius = 6378.137
|
||||
val j3Harmonic = -2.53881E-6
|
||||
val twoPi = Math.PI * 2.0
|
||||
val twoThirds = 2.0 / 3.0
|
||||
val ck2 = 5.413079E-4
|
||||
val ck4 = 6.209887E-7
|
||||
var qoms24 = 0.0
|
||||
var s4 = 0.0
|
||||
val xke = 7.43669161E-2
|
||||
|
||||
fun willBeSeen(pos: GroundPos): Boolean {
|
||||
return if (tle.meanmo < 1e-8) false else {
|
||||
var lin = tle.incl
|
||||
if (lin >= 90.0) lin = 180.0 - lin
|
||||
val sma = 331.25 * exp(ln(1440.0 / tle.meanmo) * (2.0 / 3.0))
|
||||
val apogee = sma * (1.0 + tle.eccn) - earthRadius
|
||||
acos(earthRadius / (apogee + earthRadius)) + lin * deg2Rad > abs(pos.lat * deg2Rad)
|
||||
}
|
||||
}
|
||||
|
||||
fun getPredictor(pos: GroundPos): PassPredictor {
|
||||
return PassPredictor(this, pos)
|
||||
}
|
||||
|
||||
fun getPosition(pos: GroundPos, time: Date): SatPos {
|
||||
val satPos = SatPos()
|
||||
// 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(tle.epoch)
|
||||
val tsince = (julUTC - julEpoch) * minPerDay
|
||||
calculateSDP4orSGP4(tsince)
|
||||
// Scale position and velocity vectors to km and km/sec
|
||||
convertSatState(position, velocity)
|
||||
// Calculate velocity of satellite
|
||||
magnitude(velocity)
|
||||
val squintVector = Vector4()
|
||||
// Angles in rads, dist in km, vel in km/S. Calculate sat Az, El, Range and Range-rate.
|
||||
calculateObs(julUTC, position, velocity, pos, squintVector, satPos)
|
||||
calculateLatLonAlt(julUTC, satPos, position)
|
||||
satPos.time = time
|
||||
return satPos
|
||||
}
|
||||
|
||||
// Read the system clock and return the number of days since 31Dec79 00:00:00 UTC (daynum 0)
|
||||
private fun calcCurrentDaynum(date: Date): Double {
|
||||
val now = date.time
|
||||
val sgp4Epoch = Calendar.getInstance(TimeZone.getTimeZone("UTC:UTC"))
|
||||
sgp4Epoch.clear()
|
||||
sgp4Epoch[1979, 11, 31, 0, 0] = 0
|
||||
val then = sgp4Epoch.timeInMillis
|
||||
val millis = now - then
|
||||
return millis / 1000.0 / 60.0 / 60.0 / 24.0
|
||||
}
|
||||
|
||||
private fun juliandDateOfEpoch(epoch: Double): Double {
|
||||
var year = floor(epoch * 1E-3)
|
||||
val day = (epoch * 1E-3 - year) * 1000.0
|
||||
year = if (year < 57) year + 2000 else year + 1900
|
||||
return julianDateOfYear(year) + day
|
||||
}
|
||||
|
||||
fun julianDateOfYear(theYear: Double): Double {
|
||||
val aYear = theYear - 1
|
||||
var i = floor(aYear / 100).toLong()
|
||||
val a = i
|
||||
i = a / 4
|
||||
val b = 2 - a + i
|
||||
i = floor(365.25 * aYear).toLong()
|
||||
i += (30.6001 * 14).toLong()
|
||||
return i + 1720994.5 + b
|
||||
}
|
||||
|
||||
private fun calculateSDP4orSGP4(tsince: Double) {
|
||||
if (tle.isDeepspace) (this as DeepSpaceSat).calculateSDP4(tsince)
|
||||
else (this as NearEarthSat).calculateSGP4(tsince)
|
||||
}
|
||||
|
||||
// Converts the sat position and velocity vectors to km and km/sec
|
||||
private fun convertSatState(pos: Vector4, vel: Vector4) {
|
||||
scaleVector(earthRadius, pos)
|
||||
scaleVector(earthRadius * minPerDay / secPerDay, vel)
|
||||
}
|
||||
|
||||
// Calculates the topocentric coordinates of the object with ECI pos and vel at time
|
||||
private fun calculateObs(
|
||||
julianUTC: Double,
|
||||
positionVector: Vector4,
|
||||
velocityVector: Vector4,
|
||||
gsPos: GroundPos,
|
||||
squintVector: Vector4,
|
||||
satPos: SatPos
|
||||
) {
|
||||
val obsPos = Vector4()
|
||||
val obsVel = Vector4()
|
||||
val range = Vector4()
|
||||
val rgvel = Vector4()
|
||||
val gsPosTheta = AtomicReference<Double>()
|
||||
calculateUserPosVel(julianUTC, gsPos, gsPosTheta, obsPos, obsVel)
|
||||
range.setXYZ(
|
||||
positionVector.x - obsPos.x,
|
||||
positionVector.y - obsPos.y,
|
||||
positionVector.z - obsPos.z
|
||||
)
|
||||
// Save these values globally for calculating squint angles later
|
||||
squintVector.setXYZ(range.x, range.y, range.z)
|
||||
rgvel.setXYZ(
|
||||
velocityVector.x - obsVel.x,
|
||||
velocityVector.y - obsVel.y,
|
||||
velocityVector.z - obsVel.z
|
||||
)
|
||||
magnitude(range)
|
||||
val sinLat = sin(deg2Rad * gsPos.lat)
|
||||
val cosLat = cos(deg2Rad * gsPos.lat)
|
||||
val sinTheta = sin(gsPosTheta.get())
|
||||
val cosTheta = cos(gsPosTheta.get())
|
||||
val topS = sinLat * cosTheta * range.x + sinLat * sinTheta * range.y - cosLat * range.z
|
||||
val topE = -sinTheta * range.x + cosTheta * range.y
|
||||
val topZ = cosLat * cosTheta * range.x + cosLat * sinTheta * range.y + sinLat * range.z
|
||||
var azim = atan(-topE / topS)
|
||||
if (topS > 0.0) azim += Math.PI
|
||||
if (azim < 0.0) azim += twoPi
|
||||
satPos.azimuth = azim
|
||||
satPos.elevation = asin(topZ / range.w)
|
||||
satPos.range = range.w
|
||||
satPos.rangeRate = dot(range, rgvel) / range.w
|
||||
}
|
||||
|
||||
// Returns the ECI position and velocity of the observer
|
||||
private fun calculateUserPosVel(
|
||||
time: Double,
|
||||
gsPos: GroundPos,
|
||||
gsPosTheta: AtomicReference<Double>,
|
||||
obsPos: Vector4,
|
||||
obsVel: Vector4
|
||||
) {
|
||||
val mFactor = 7.292115E-5
|
||||
gsPosTheta.set(mod2PI(thetaGJD(time) + deg2Rad * gsPos.lon))
|
||||
val c =
|
||||
invert(sqrt(1.0 + flatFactor * (flatFactor - 2) * sqr(sin(deg2Rad * gsPos.lat))))
|
||||
val sq = sqr(1.0 - flatFactor) * c
|
||||
val achcp = (earthRadius * c + gsPos.alt / 1000.0) * cos(deg2Rad * gsPos.lat)
|
||||
obsPos.setXYZ(
|
||||
achcp * cos(gsPosTheta.get()), achcp * sin(gsPosTheta.get()),
|
||||
(earthRadius * sq + gsPos.alt / 1000.0) * sin(deg2Rad * gsPos.lat)
|
||||
)
|
||||
obsVel.setXYZ(-mFactor * obsPos.y, mFactor * obsPos.x, 0.0)
|
||||
magnitude(obsPos)
|
||||
magnitude(obsVel)
|
||||
}
|
||||
|
||||
// Calculate the geodetic position of an object given its ECI pos and time
|
||||
private fun calculateLatLonAlt(
|
||||
time: Double,
|
||||
satPos: SatPos,
|
||||
position: Vector4 = this.position
|
||||
) {
|
||||
satPos.theta = atan2(position.y, position.x)
|
||||
satPos.longitude = mod2PI(satPos.theta - thetaGJD(time))
|
||||
val r = sqrt(sqr(position.x) + sqr(position.y))
|
||||
val e2 = flatFactor * (2.0 - flatFactor)
|
||||
satPos.latitude = atan2(position.z, r)
|
||||
var phi: Double
|
||||
var c: Double
|
||||
var i = 0
|
||||
var converged: Boolean
|
||||
do {
|
||||
phi = satPos.latitude
|
||||
c = invert(sqrt(1.0 - e2 * sqr(sin(phi))))
|
||||
satPos.latitude = atan2(position.z + earthRadius * c * e2 * sin(phi), r)
|
||||
converged = abs(satPos.latitude - phi) < epsilon
|
||||
} while (i++ < 10 && !converged)
|
||||
satPos.altitude = r / cos(satPos.latitude) - earthRadius * c
|
||||
var temp = satPos.latitude
|
||||
if (temp > Math.PI / 2.0) {
|
||||
temp -= twoPi
|
||||
satPos.latitude = temp
|
||||
}
|
||||
}
|
||||
|
||||
fun calculatePosAndVel(
|
||||
rk: Double, uk: Double, xnodek: Double,
|
||||
xinck: Double, rdotk: Double, rfdotk: Double
|
||||
) {
|
||||
// Orientation vectors
|
||||
val sinuk = sin(uk)
|
||||
val cosuk = cos(uk)
|
||||
val sinik = sin(xinck)
|
||||
val cosik = cos(xinck)
|
||||
val sinnok = sin(xnodek)
|
||||
val cosnok = cos(xnodek)
|
||||
val xmx = -sinnok * cosik
|
||||
val xmy = cosnok * cosik
|
||||
val ux = xmx * sinuk + cosnok * cosuk
|
||||
val uy = xmy * sinuk + sinnok * cosuk
|
||||
val uz = sinik * sinuk
|
||||
val vx = xmx * cosuk - cosnok * sinuk
|
||||
val vy = xmy * cosuk - sinnok * sinuk
|
||||
val vz = sinik * cosuk
|
||||
// Position and velocity
|
||||
position.setXYZ(ux, uy, uz)
|
||||
position.multiply(rk)
|
||||
velocity.x = rdotk * ux + rfdotk * vx
|
||||
velocity.y = rdotk * uy + rfdotk * vy
|
||||
velocity.z = rdotk * uz + rfdotk * vz
|
||||
}
|
||||
|
||||
class Vector4 {
|
||||
var w = 0.0
|
||||
var x = 0.0
|
||||
var y = 0.0
|
||||
var z = 0.0
|
||||
|
||||
fun multiply(multiplier: Double) {
|
||||
x *= multiplier
|
||||
y *= multiplier
|
||||
z *= multiplier
|
||||
}
|
||||
|
||||
fun setXYZ(xValue: Double, yValue: Double, zValue: Double) {
|
||||
x = xValue
|
||||
y = yValue
|
||||
z = zValue
|
||||
}
|
||||
}
|
||||
|
||||
fun sqr(arg: Double): Double {
|
||||
return arg * arg
|
||||
}
|
||||
|
||||
fun invert(value: Double): Double {
|
||||
return 1.0 / value
|
||||
}
|
||||
|
||||
// Calculates the modulus of 2 * PI
|
||||
fun mod2PI(value: Double): Double {
|
||||
var retVal = value
|
||||
val i = (retVal / twoPi).toInt()
|
||||
retVal -= i * twoPi
|
||||
if (retVal < 0.0) retVal += twoPi
|
||||
return retVal
|
||||
}
|
||||
|
||||
// Solves Keplers' Equation
|
||||
fun converge(temp: DoubleArray, axn: Double, ayn: Double, capu: Double) {
|
||||
var converged = false
|
||||
var i = 0
|
||||
do {
|
||||
temp[7] = sin(temp[2])
|
||||
temp[8] = cos(temp[2])
|
||||
temp[3] = axn * temp[7]
|
||||
temp[4] = ayn * temp[8]
|
||||
temp[5] = axn * temp[8]
|
||||
temp[6] = ayn * temp[7]
|
||||
val epw = (capu - temp[4] + temp[3] - temp[2]) / (1.0 - temp[5] - temp[6]) + temp[2]
|
||||
if (abs(epw - temp[2]) <= epsilon) converged = true else temp[2] = epw
|
||||
} while (i++ < 10 && !converged)
|
||||
}
|
||||
|
||||
// Sets perigee and checks and adjusts the calculation if the perigee is less tan 156KM
|
||||
fun setPerigee(perigee: Double) {
|
||||
this.perigee = perigee
|
||||
checkPerigee()
|
||||
}
|
||||
|
||||
// Checks and adjusts the calculation if the perigee is less tan 156KM
|
||||
private fun checkPerigee() {
|
||||
s4 = 1.012229
|
||||
qoms24 = 1.880279E-09
|
||||
if (perigee < 156.0) {
|
||||
s4 = if (perigee <= 98.0) 20.0 else perigee - 78.0
|
||||
qoms24 = ((120 - s4) / earthRadius).pow(4.0)
|
||||
s4 = s4 / earthRadius + 1.0
|
||||
}
|
||||
}
|
||||
|
||||
// Calculates the dot product of two vectors
|
||||
private fun dot(v1: Vector4, v2: Vector4): Double {
|
||||
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
|
||||
}
|
||||
|
||||
// Returns fractional part of double argument
|
||||
private fun fraction(arg: Double): Double {
|
||||
return arg - floor(arg)
|
||||
}
|
||||
|
||||
// Calculates scalar magnitude of a vector4 argument
|
||||
private fun magnitude(v: Vector4) {
|
||||
v.w = sqrt(sqr(v.x) + sqr(v.y) + sqr(v.z))
|
||||
}
|
||||
|
||||
private fun modulus(arg1: Double): Double {
|
||||
var returnValue = arg1
|
||||
val i = floor(returnValue / secPerDay).toInt()
|
||||
returnValue -= i * secPerDay
|
||||
if (returnValue < 0.0) returnValue += secPerDay
|
||||
return returnValue
|
||||
}
|
||||
|
||||
// Multiplies the vector v1 by the scalar k
|
||||
private fun scaleVector(k: Double, v: Vector4) {
|
||||
v.multiply(k)
|
||||
magnitude(v)
|
||||
}
|
||||
|
||||
private fun thetaGJD(theJD: Double): Double {
|
||||
val earthRotPerSidDay = 1.00273790934
|
||||
val ut = fraction(theJD + 0.5)
|
||||
val aJD = theJD - ut
|
||||
val tu = (aJD - 2451545.0) / 36525.0
|
||||
var gmst = 24110.54841 + tu * (8640184.812866 + tu * (0.093104 - tu * 6.2E-6))
|
||||
gmst = modulus(gmst + secPerDay * earthRotPerSidDay * ut)
|
||||
return twoPi * gmst / secPerDay
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun createSat(tle: TLE): Satellite {
|
||||
return if (tle.isDeepspace) DeepSpaceSat(tle)
|
||||
else NearEarthSat(tle)
|
||||
}
|
||||
|
||||
fun importTLE(tleStream: InputStream): List<TLE> {
|
||||
val importedTles = mutableListOf<TLE>()
|
||||
val currentTLE = arrayOf(String(), String(), String())
|
||||
var line = 0
|
||||
tleStream.bufferedReader().forEachLine {
|
||||
if (line != 2) {
|
||||
currentTLE[line] = it
|
||||
line++
|
||||
} else {
|
||||
currentTLE[line] = it
|
||||
importedTles.add(parseTLE(currentTLE))
|
||||
line = 0
|
||||
}
|
||||
}
|
||||
return importedTles
|
||||
}
|
||||
|
||||
private fun parseTLE(tle: Array<String>): TLE {
|
||||
val name: String = tle[0].trim()
|
||||
val epoch: Double = tle[1].substring(18, 32).toDouble()
|
||||
val meanmo: Double = tle[2].substring(52, 63).toDouble()
|
||||
val eccn: Double = 1.0e-07 * tle[2].substring(26, 33).toDouble()
|
||||
val incl: Double = tle[2].substring(8, 16).toDouble()
|
||||
val raan: Double = tle[2].substring(17, 25).toDouble()
|
||||
val argper: Double = tle[2].substring(34, 42).toDouble()
|
||||
val meanan: Double = tle[2].substring(43, 51).toDouble()
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.predict4kotlin
|
||||
|
||||
data class TLE(
|
||||
val name: String,
|
||||
val epoch: Double,
|
||||
val meanmo: Double,
|
||||
val eccn: Double,
|
||||
val incl: Double,
|
||||
val raan: Double,
|
||||
val argper: Double,
|
||||
val meanan: Double,
|
||||
val catnum: Int,
|
||||
val bstar: Double
|
||||
) {
|
||||
val xincl: Double = Math.toRadians(incl)
|
||||
val xnodeo: Double = Math.toRadians(raan)
|
||||
val omegao: Double = Math.toRadians(argper)
|
||||
val xmo: Double = Math.toRadians(meanan)
|
||||
val xno: Double = meanmo * Math.PI * 2.0 / 1440
|
||||
val isDeepspace: Boolean = meanmo < 6.4
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
include ':core'
|
||||
include ':app'
|
||||
rootProject.name='Look4Sat'
|
||||
Reference in new issue
Block a user