This commit is contained in:
Дмитрий Соловьев
2026-05-25 14:23:52 +03:00
parent b3a6012ebb
commit d48ddd2657
1066 changed files with 104601 additions and 3 deletions
@@ -0,0 +1,368 @@
package ballistics
import ballistics.flightLine.FlightLineCalculator
import ballistics.mpl.MPLCalculator
import ballistics.orbitalPoints.AbstractOrbPointsCalculator
import ballistics.orbitalPoints.OrbitalPointsIntegrator
import ballistics.orbitalPoints.OrbitalPointsTLE
import ballistics.orbitalPoints.timeStepper.AbstractStepper
import ballistics.orbitalPoints.timeStepper.TLEStepper
import ballistics.types.BallisticsError
import ballistics.types.EarthType
import ballistics.types.FleghtLineSector
import ballistics.types.FlightLine
import ballistics.types.InitialConditions
import ballistics.types.IntegrationType
import ballistics.types.KeplerParams
import ballistics.types.ModDVType
import ballistics.types.OPKatObj
import ballistics.types.OrbitalPoint
import ballistics.types.PPI
import ballistics.types.PointViewParams
import ballistics.types.RevolutionParameter
import ballistics.types.TLE
import ballistics.types.TLEParams
import ballistics.types.TangageType
import ballistics.types.WorkCSType
import ballistics.types.ZRV
import ballistics.utils.astro.AstronomerJ2000
import ballistics.utils.toDateTime
import ballistics.zrv.ZRVStepperCalculator
import ballistics.zrv.ZRVStepperCalculatorAsync
import org.nstart.dep265.tletools.zeptomoby.core.Globals.sqr
import reactor.core.publisher.Flux
import kotlin.math.PI
import kotlin.math.asin
import kotlin.math.atan
import kotlin.math.atan2
import kotlin.math.sin
import kotlin.math.sqrt
class Ballistics {
var kaId: Int = -1
var kaUmn: Int = -1
val ic : MutableList<InitialConditions> = mutableListOf()
/**
* Модель движения
*/
var modDVType: ModDVType = ModDVType.FOTO
/**
* Метод интегрирования
*/
var integrationType: IntegrationType = IntegrationType.ADAMS7
/**
* Тип модели Земли
*/
var earthType: EarthType = EarthType.PZ90d02
/**
* тип базовой системы координат (для расчета трасс полета, полособзора и матрицы планирования)
*/
var workCoordinateSystem: WorkCSType = WorkCSType.WCSOrbit
/**
* Минимальный угол крена (для расчета полосы обзора)
*/
var rollMin: Double = 0.0
/**
* Максимальный угол крена (для расчета полосы обзора и покрытия земной поверхности)
*/
var rollMax: Double = PI / 6.0
var rollByModule : Boolean = true
/**
* Граничный угол Солнца для КА (для определения попадания момента наблюдения объекта в матрицу планирования)
*/
var sunAngleMin: Double = 0.0
/**
* Признак использования ограничений на попадание в матрицу планирования из параметров объекта
*/
var useObjConstraints: Boolean = false
/**
* Объект класса расчета точек орбиты
*/
private var orbitCalculator: AbstractOrbPointsCalculator? = null
/**
* Объект класса расчета параметров ЗРВ
*/
private var zrvCalculator: ZRVStepperCalculator? = null
/**
* Объект класса расчета параметров трассы полета и полосы обзора
*/
private var flightLineCalculator: FlightLineCalculator? = null
/**
* Объект класса расчета матрицы планирования
*/
private var mplCalculator: MPLCalculator? = null
/**
* Массив точек орбиты
*/
val points: Iterable<OrbitalPoint>
get() = if (orbitCalculator != null) orbitCalculator!!.points else listOf<OrbitalPoint>()
val revolutions: Iterable<RevolutionParameter>
get() = if (orbitCalculator != null) orbitCalculator!!.revolutions else listOf<RevolutionParameter>()
/**
* Массив результатов расчета параметров ЗРВ
*/
val zrv: Iterable<ZRV>
get() = if (zrvCalculator != null) zrvCalculator!!.zrv else listOf<ZRV>()
val flightLine: Iterable<FlightLine>
get() = if (flightLineCalculator != null) flightLineCalculator!!.flightLine else listOf<FlightLine>()
val mpl: Iterable<PointViewParams>
get() = if (mplCalculator != null) mplCalculator!!.mpl else listOf<PointViewParams>()
val coverings : Map<Int, MutableList<FleghtLineSector>>
get() = flightLineCalculator?.earthCoveringCalculator?.coverings?:mapOf()
/**
* Расчет точек орбиты методом интегрирования
* @param nu начальные условия движения центра масс КА
* @param tn время начала расчета
* @param tk время конца расчета
* @return код завершения расчета
*/
fun calculateOrbPoints(
nu: InitialConditions,
tn: Double,
tk: Double,
): BallisticsError {
orbitCalculator = OrbitalPointsIntegrator(nu, modDVType, integrationType, earthType)
return orbitCalculator!!.calculate(tn, tk)
}
fun setOrbitalPoints(
points: Iterable<OrbitalPoint>,
earthType: EarthType = EarthType.PZ90d02,
) {
orbitCalculator = OrbitalPointsIntegrator(arrayOf(), ModDVType.FOTO, IntegrationType.ADAMS7, earthType)
orbitCalculator?.points?.addAll(points)
}
fun setEarthCoverage(covs : Map<Int, MutableList<FleghtLineSector>>){
if (orbitCalculator != null){
flightLineCalculator = FlightLineCalculator(orbitCalculator!!, rollMin, rollMax, workCoordinateSystem)
flightLineCalculator?.earthCoveringCalculator?.coverings?.putAll(covs)
}
}
/**
* Расчет точек орбиты методом интегрирования
* @param nu массив начальных условий движения центра масс КА
* @param tn время начала расчета
* @param tk время конца расчета
* @return код завершения расчета
*/
fun calculateOrbPoints(
nu: Array<InitialConditions>,
tn: Double,
tk: Double,
): BallisticsError {
nu.sortBy { it.point.t }
orbitCalculator = OrbitalPointsIntegrator(nu, modDVType, integrationType, earthType)
return orbitCalculator!!.calculate(tn, tk)
}
/**
* Расчет параметров ЗРВ
* @param ppi список ППИ
* @param tn время начала расчета
* @param tk время конца расчета
* @return код завершения расчета
*/
fun calculateZRV(
ppi: List<PPI>,
tn: Double,
tk: Double,
): BallisticsError {
return orbitCalculator?.let {
zrvCalculator = ZRVStepperCalculator(it, ppi)
zrvCalculator?.calculate(tn, tk)
} ?: BallisticsError.EMPTY_ORBITAL_POINTS
}
fun calculateZRVAsync(
ppi: List<PPI>,
tn: Double,
tk: Double,
): Flux<ZRV> =
if (orbitCalculator != null) {
ZRVStepperCalculatorAsync(orbitCalculator!!, ppi).calculate(tn, tk)
} else {
Flux.empty()
}
fun calculateFlightLine(
tn: Double,
tk: Double,
): BallisticsError {
return orbitCalculator?.let {
flightLineCalculator = FlightLineCalculator(it, rollMin, rollMax, workCoordinateSystem)
flightLineCalculator?.calculate(tn, tk)
} ?: BallisticsError.EMPTY_ORBITAL_POINTS
}
fun calculateMPL(
tn: Double,
tk: Double,
objs: Iterable<OPKatObj>,
tangage: Double = 0.0,
tangageType: TangageType = TangageType.TTProactive,
): BallisticsError {
return flightLineCalculator?.let {
mplCalculator =
MPLCalculator(it, tangage, tangageType).apply {
this.sunAngleMin = this@Ballistics.sunAngleMin
this.useObjConstraints = this@Ballistics.useObjConstraints
}
mplCalculator?.krenMax = rollMax
mplCalculator?.krenMin = rollMin
mplCalculator?.krenByModule = rollByModule
mplCalculator?.calculate(tn, tk, objs)
} ?: BallisticsError.EMPTY_FLIGHTLINE_POINTS
}
/**
* Объект расчета выхода на заданное время
*/
fun getStepper(): AbstractStepper? {
return orbitCalculator?.getStepper()
}
fun parseTLE(
tle1: String,
tle2: String,
): OrbitalPoint {
try {
val stepper = TLEStepper(tle1, tle2, EarthType.PZ90d02)
return stepper.calculate(stepper.baseEpoch)
}catch (ex : Exception){
throw ex
}
}
fun parseTLE(tle: TLE): OrbitalPoint {
val stepper = TLEStepper(tle.tle1, tle.tle2, EarthType.PZ90d02)
return stepper.calculate(stepper.baseEpoch)
}
fun getTLEParams(tle : TLE, capt : String = "") : TLEParams{
try {
val stepper = TLEStepper(tle.tle1, tle.tle2, EarthType.PZ90d02)
val orb = stepper.calculate(stepper.baseEpoch)
return TLEParams(
capt, stepper.satellite.orbit.satId.toLong(),
stepper.satellite.orbit.orbitNum.toLong(),
toDateTime(orb.t),
stepper.satellite.orbit.inclination,
stepper.satellite.orbit.perigee,
stepper.satellite.orbit.apogee,
stepper.satellite.orbit.argPerigee,
stepper.satellite.orbit.eccentricity,
stepper.satellite.orbit.major,
stepper.satellite.orbit.minor,
stepper.satellite.orbit.meanAnomaly,
stepper.satellite.orbit.period,
stepper.satellite.orbit.semiMajor,
stepper.satellite.orbit.semiMinor,
stepper.satellite.orbit.raan,
stepper.satellite.orbit.meanMotion,
stepper.satellite.orbit.meanMotionTle,
)
}catch (ex : Exception){
throw ex
}
}
fun calculateOrbPoints(
tle: TLE,
tn: Double,
tk: Double,
): BallisticsError {
orbitCalculator = OrbitalPointsTLE(tle.tle1, tle.tle2, earthType)
return orbitCalculator!!.calculate(tn, tk)
}
fun calculateOrbPoints(
tle: TLE,
duration: Double,
): BallisticsError {
orbitCalculator = OrbitalPointsTLE(tle.tle1, tle.tle2, earthType)
val tnu = (orbitCalculator as OrbitalPointsTLE).stepper.baseEpoch
return orbitCalculator!!.calculate(tnu, tnu + duration)
}
fun calculateKeplerParams(point: OrbitalPoint): KeplerParams {
val res = KeplerParams()
val aL00 = 6.25648106E+7
val astro = AstronomerJ2000(earthType)
val ask = astro.grinvToASK(point)
val r = sqrt(sqr(ask.r.x) + sqr(ask.r.y) + sqr(ask.r.z))
val v = sqrt(sqr(ask.v.x) + sqr(ask.v.y) + sqr(ask.v.z))
val mu = astro.earth.fM
val k = (r * sqr(v)) / mu
val sinO = (ask.r.x * ask.v.x + ask.r.y * ask.v.y + ask.r.z * ask.v.z) / (r * v)
val cosO = sqrt(1 - sqr(sinO))
val c1 = ask.r.y * ask.v.z - ask.r.z * ask.v.y
val c2 = ask.r.z * ask.v.x - ask.r.x * ask.v.z
val c3 = ask.r.x * ask.v.y - ask.r.y * ask.v.x
val c = sqrt(sqr(c1) + sqr(c2) + sqr(c3))
res.ael = r / (2 - k)
val lambda = (1 / res.ael) * sqrt(mu / res.ael)
res.t = (2 * PI * res.ael * sqrt(res.ael)) / sqrt(astro.earth.middleRadius * aL00)
res.e = sqrt(1 - k * (2 - k) * (1 - sinO * sinO))
res.v = atan((k * sinO * cosO) / (k * sqr(cosO) - 1))
res.u = atan2((ask.r.z * c), (ask.r.y * c1 - ask.r.x * c2))
res.eA = atan((sqrt(1 - sqr(res.e)) * k * sinO * cosO) / (sqr(res.e) + (k * sqr(cosO) - 1)))
res.tau = ask.t + ((res.e * sin(res.eA) - res.eA) / lambda)
res.nakl = 0.5 * PI - asin(c3 / c)
res.omegab = atan2(c1, -c2)
res.omegam = res.u - res.v
res.o = asin(sinO)
res.rA = res.ael * (1 + res.e)
res.rP = res.ael * (1 - res.e)
res.dmv = point.t
if (res.omegam < 0) res.omegam += 2 * PI
if (res.tau < 0) res.tau += 1
return res
}
fun clear(){
orbitCalculator?.clear()
flightLineCalculator?.clear()
flightLineCalculator?.clear()
zrvCalculator?.clear()
mplCalculator?.clear()
}
}
@@ -0,0 +1,105 @@
package ballistics.flightLine
import ballistics.types.FleghtLineSector
import ballistics.types.FlightLine
import kotlin.math.PI
import kotlin.math.truncate
internal class EarthCovering {
var coverings = mutableMapOf<Int, MutableList<FleghtLineSector>>()
fun getBInd(b: Double): Int {
var b2: Double = b * 180.0 / PI
b2 = 90.0 - b2
var k: Int = truncate(b2 / 2.0).toInt()
if (k > 89) k = 89
return k
}
fun getLInd(l2: Double): Int {
var l: Double = l2 * 180.0 / PI
if (l < 0) l += 360.0
var k: Int = truncate(l / 2.0).toInt()
if (k > 179) k = 179
return k
}
fun getInd(
b: Double,
l: Double,
): Int {
return getBInd(b) * 180 + getLInd(l)
}
fun addCoverings(
frst: FlightLine,
sec: FlightLine,
) {
val bmax = getBInd(minOf(frst.leftOuterSwath.lat, frst.rightOuterSwath.lat, sec.leftOuterSwath.lat, sec.rightOuterSwath.lat))
val bmin = getBInd(maxOf(frst.leftOuterSwath.lat, frst.rightOuterSwath.lat, sec.leftOuterSwath.lat, sec.rightOuterSwath.lat))
var lmin = getLInd(minOf(frst.leftOuterSwath.long, frst.rightOuterSwath.long, sec.leftOuterSwath.long, sec.rightOuterSwath.long))
var lmax = getLInd(maxOf(frst.leftOuterSwath.long, frst.rightOuterSwath.long, sec.leftOuterSwath.long, sec.rightOuterSwath.long))
if (lmax - lmin > 90) {
lmax = checkLmax(frst, sec)
lmin = checkLmin(frst, sec)
val l = lmin
lmin = lmax
lmax = l + 179
}
for (bi in bmin..bmax) {
for (li in lmin..lmax) {
val ll = if (li >= 180) li - 180 else li
val ind = bi * 180 + ll
if (coverings.containsKey(ind)) {
coverings[ind]!!.add(FleghtLineSector(frst.t, sec.t))
} else {
coverings.put(ind, mutableListOf(FleghtLineSector(frst.t, sec.t)))
}
}
}
}
fun checkLmax(frst: FlightLine, sec: FlightLine) : Int{
var buf = 2 * PI
if (frst.leftOuterSwath.long > PI / 2) {
buf = frst.leftOuterSwath.long
}
if (frst.rightOuterSwath.long > PI / 2 && frst.rightOuterSwath.long < buf) {
buf = frst.rightOuterSwath.long
}
if (sec.rightOuterSwath.long > PI / 2 && sec.rightOuterSwath.long < buf) {
buf = sec.rightOuterSwath.long
}
if (sec.leftOuterSwath.long > PI / 2 && sec.leftOuterSwath.long < buf) {
buf = sec.leftOuterSwath.long
}
return getLInd(buf)
}
fun checkLmin(frst: FlightLine, sec: FlightLine) : Int{
var buf = 0.0
if (frst.leftOuterSwath.long < PI / 2) {
buf = frst.leftOuterSwath.long
}
if (frst.rightOuterSwath.long < PI / 2 && frst.rightOuterSwath.long > buf) {
buf = frst.rightOuterSwath.long
}
if (sec.rightOuterSwath.long < PI / 2 && sec.rightOuterSwath.long > buf) {
buf = sec.rightOuterSwath.long
}
if (sec.leftOuterSwath.long < PI / 2 && sec.leftOuterSwath.long > buf) {
buf = sec.leftOuterSwath.long
}
return getLInd(buf)
}
fun getCoverings(
b: Double,
l: Double,
): Iterable<FleghtLineSector> {
return coverings.getOrDefault(getInd(b, l), listOf<FleghtLineSector>())
}
}
@@ -0,0 +1,82 @@
package ballistics.flightLine
import ballistics.orbitalPoints.AbstractOrbPointsCalculator
import ballistics.types.BallisticsError
import ballistics.types.FlightLine
import ballistics.types.OrbitalPoint
import ballistics.types.Orientation
import ballistics.types.WorkCSType
import ballistics.utils.earth.getEarth
internal class FlightLineCalculator(
var opc: AbstractOrbPointsCalculator,
var rollMin: Double,
var rollMax: Double,
var wcs: WorkCSType = WorkCSType.WCSOrbit,
) {
var step = 60.0
val eart = getEarth(opc.earthType)
var flightLine = mutableListOf<FlightLine>()
var pc = PointOnEarthCalculator(opc.earthType, wcs)
var earthCoveringCalculator: EarthCovering = EarthCovering()
var needToCalcEarthCovering: Boolean = true
fun calculate(
tn: Double,
tk: Double,
): BallisticsError {
flightLine.clear()
earthCoveringCalculator.coverings.clear()
var t = tn
val stepper = opc.getStepper()
var point: OrbitalPoint?
point = stepper.calculate(t)
if (point == null)
return BallisticsError.STEPPER_ERROR
flightLine.add(calcFl(point))
t += step
while (t <= tk) {
point = stepper.calculate(t)
if (point == null) {
println("Ошибка выхода на заданное время в середине расчета")
return BallisticsError.STEPPER_ERROR
}
val fl = calcFl(point)
if (needToCalcEarthCovering) {
earthCoveringCalculator.addCoverings(flightLine.last(), fl)
}
flightLine.add(fl)
t += step
}
return BallisticsError.OK
}
private fun calcFl(point: OrbitalPoint): FlightLine {
return FlightLine(
point.t,
point.vit,
if (point.v.z >= 0) 0 else 1,
pc.pointOnEarth(point, Orientation(0.0, -rollMax, 0.0))!!,
pc.pointOnEarth(point, Orientation(0.0, -rollMin, 0.0))!!,
pc.pointOnEarth(point, Orientation(0.0, 0.0, 0.0))!!,
pc.pointOnEarth(point, Orientation(0.0, rollMin, 0.0))!!,
pc.pointOnEarth(point, Orientation(0.0, rollMax, 0.0))!!,
)
}
fun clear(){
flightLine.clear()
earthCoveringCalculator.coverings.clear()
opc.clear()
}
}
@@ -0,0 +1,191 @@
package ballistics.flightLine
import ballistics.types.EarthType
import ballistics.types.OrbitalPoint
import ballistics.types.Orientation
import ballistics.types.THBLPoint
import ballistics.types.WorkCSType
import ballistics.utils.astro.AstronomerJ2000
import ballistics.utils.math.Matrix3D
import ballistics.utils.math.Vector3D
import kotlin.math.asin
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin
import kotlin.math.sqrt
class PointOnEarthCalculator(val earthType: EarthType, private val wcs: WorkCSType) {
private var astro = AstronomerJ2000(earthType)
fun pointOnEarth(
point: OrbitalPoint,
orientation: Orientation,
): THBLPoint? {
val krenMax = asin((astro.earth.polarRadius - 50000.0) / point.r.module())
if (orientation.kren > krenMax) {
orientation.kren = krenMax
}
if (orientation.kren < -krenMax) {
orientation.kren = -krenMax
}
val rabs: Vector3D
val vabs: Vector3D
if (wcs == WorkCSType.WCSPath) {
rabs = point.r
vabs = point.v
} else {
val ask = astro.grinvToASK(point)
rabs = ask.r
vabs = ask.v
}
val dd: Vector3D
val c = Matrix3D()
c.second = rabs.basis()
c.third = (vabs.rem(rabs)).basis()
c.first = c.second.rem(c.third)
var ct = c.transpose()
val aa =
Matrix3D(
Vector3D(
cos(orientation.tang) * cos(orientation.risk) - sin(orientation.tang) * sin(orientation.kren) * sin(orientation.risk),
-sin(orientation.tang) * cos(orientation.kren),
cos(orientation.tang) * sin(orientation.risk) + sin(orientation.tang) * sin(orientation.kren) * cos(orientation.risk),
),
Vector3D(
sin(orientation.tang) * cos(orientation.risk) + cos(orientation.tang) * sin(orientation.kren) * sin(orientation.risk),
cos(orientation.tang) * cos(orientation.kren),
sin(orientation.tang) * sin(orientation.risk) - cos(orientation.tang) * sin(orientation.kren) * cos(orientation.risk),
),
Vector3D(
-cos(orientation.kren) * sin(orientation.risk),
sin(orientation.kren),
cos(orientation.kren) * cos(orientation.risk),
),
)
if (wcs == WorkCSType.WCSOrbit) {
val g = Matrix3D()
g.makeOzMatrix(astro.si2000(point.t))
ct = g.transpose() * ct
}
val k = ct * aa
val d = Vector3D(0.0, 1.0, 0.0).basis()
dd = k * d
return earthIntersection(point.t, point.r, dd)
}
/**
* расчет точки пересечени отрезка, определенного вектором dd из точка rotn и земной поверхности
*/
private fun earthIntersection(
t: Double,
rotn: Vector3D,
dd: Vector3D,
): THBLPoint? {
var dz = dd.z
if (dz < 0) dz *= -1
val x1: Double
val y1: Double
val z1: Double
val x2: Double
val y2: Double
val z2: Double
val r: Vector3D
val h: Double
val ekvRadiusOverPolarRadiusSquared = (astro.earth.ekvRadius / astro.earth.polarRadius).pow(2)
if (dz < 0.1) {
val k1 = dd.y / dd.x
val k2 = rotn.y - k1 * rotn.x
val k3 = dd.z / dd.x
val k4 = rotn.z - k3 * rotn.x
val k5 = k1 * k1 + ekvRadiusOverPolarRadiusSquared * k3 * k3 + 1
val k6 = 2 * (k1 * k2 + ekvRadiusOverPolarRadiusSquared * k3 * k4)
val k7 = k2 * k2 + ekvRadiusOverPolarRadiusSquared * k4 * k4 - astro.earth.ekvRadius * astro.earth.ekvRadius
if ((k6 * k6 - k5 * k7 * 4) < 0) {
return null
}
x1 = (-k6 + sqrt(k6 * k6 - 4 * k5 * k7)) / (2 * k5)
y1 = k1 * x1 + k2
z1 = k3 * x1 + k4
x2 = (-k6 - sqrt(k6 * k6 - 4 * k5 * k7)) / (2 * k5)
y2 = k1 * x2 + k2
z2 = k3 * x2 + k4
} else {
val k1 = dd.x / dd.z
val k2 = rotn.x - k1 * rotn.z
val k3 = dd.y / dd.z
val k4 = rotn.y - k3 * rotn.z
val k5 = k1 * k1 + k3 * k3 + ekvRadiusOverPolarRadiusSquared
val k6 = 2 * (k1 * k2 + k3 * k4)
val k7 = k2 * k2 + k4 * k4 - astro.earth.ekvRadius * astro.earth.ekvRadius
if ((k6 * k6 - 4 * k5 * k7) < 0) {
return null
}
z1 = (-k6 + sqrt(k6 * k6 - 4 * k5 * k7)) / (2 * k5)
y1 = k3 * z1 + k4
x1 = k1 * z1 + k2
z2 = (-k6 - sqrt(k6 * k6 - 4 * k5 * k7)) / (2 * k5)
y2 = k3 * z2 + k4
x2 = k1 * z2 + k2
}
val d1 = Vector3D(rotn.x - x1, rotn.y - y1, rotn.z - z1).module()
val d2 = Vector3D(rotn.x - x2, rotn.y - y2, rotn.z - z2).module()
if (d1 <= d2) {
r = Vector3D(x1, y1, z1)
h = d1
} else {
r = Vector3D(x2, y2, z2)
h = d2
}
val blh = astro.earth.xyz2blh(r)
return THBLPoint(blh.lat, blh.long, h, astro.sunAngle(t, r))
}
/**
* скорость компенсации (чтобы получить СДИ надо домножить на фокусное расстояние в мм)
*/
fun calculateWD(
point: OrbitalPoint,
orientation: Orientation,
range: Double,
): Double {
try {
val r = point.r.module()
val c1: Double = point.r.y * point.v.z - point.r.z * point.v.y
val c2: Double = point.r.z * point.v.x - point.r.x * point.v.z
val c3: Double = point.r.x * point.v.y - point.r.y * point.v.x
val cc = sqrt(c1 * c1 + c2 * c2 + c3 * c3)
// Трансверсальная скорость V*sin(Q)
val worb = cc / r
val wd = worb * (r - range * cos(orientation.kren)) / r / range
return wd
} catch (ex: Exception) {
return 0.0
}
}
}
@@ -0,0 +1,143 @@
package ballistics.mpl
import ballistics.flightLine.FlightLineCalculator
import ballistics.types.BallisticsError
import ballistics.types.OPKatObj
import ballistics.types.PointViewParams
import ballistics.types.TangageType
import ballistics.utils.math.Vector3D
import ballistics.utils.math.equations.EquationCalculatorSpan
import kotlin.math.PI
import kotlin.math.abs
internal class MPLCalculator(val flightLineCalculator: FlightLineCalculator, val tangage: Double, tangageType: TangageType) {
private val debugEasyCalc = false
private val stepper = flightLineCalculator.opc.getStepper()
private val orientCalculator = OrientOnPointCalculator(flightLineCalculator.opc.earthType, flightLineCalculator.wcs, tangageType)
private var currentObj = Vector3D()
var sunAngleMin: Double = 0.0
var useObjConstraints: Boolean = false
var krenMin = 0.0
var krenMax = 0.0
var krenByModule : Boolean = true
var mpl = mutableListOf<PointViewParams>()
fun calculate(
tn: Double,
tk: Double,
objs: Iterable<OPKatObj>,
): BallisticsError {
mpl.clear()
val eqc = EquationCalculatorSpan()
eqc.delta = 0.001 * PI / 180.0
eqc.value = tangage
val samin = sunAngleMin
// krenMin = flightLineCalculator.rollMin
// krenMax = flightLineCalculator.rollMax
for (o in objs) {
if (useObjConstraints) {
krenMin = o.rollMin
krenMax = o.rollMax
sunAngleMin = o.sunAngleMin
}
currentObj = flightLineCalculator.eart.blh2xyz(o.lat, o.long, o.height)
val covs =
flightLineCalculator.earthCoveringCalculator.getCoverings(
o.lat,
o.long,
).filter { (it.tStop >= tn && it.tStart <= tk) }
var tLast = 0.0
for (cov in covs) {
if (cov.tStart - tLast < 600.0) {
continue
}
var isOk: Boolean
var traverz: Double?
if (!debugEasyCalc) {
traverz = eqc.calculate(cov.tStart, cov.tStop, this::equation)
isOk = traverz != null
} else {
traverz = (cov.tStart + cov.tStop) / 2
isOk = true
}
if (isOk && tryToAddViewParams(traverz!!, o)) {
tLast = traverz
}
}
}
sunAngleMin = samin
mpl.sortBy { it.traverz }
return BallisticsError.OK
}
fun tryToAddViewParams(
t: Double,
o: OPKatObj,
): Boolean {
val ka = stepper.calculate(t)
if (ka != null) {
val orient = orientCalculator.calculateOrientOnPoint(ka, currentObj)
val w = orientCalculator.pointInWCS(ka, currentObj)
val blh = flightLineCalculator.eart.xyz2blh(ka.r)
val vp =
PointViewParams(
o.objON,
o.objN,
o.objUUID,
o.pointNumb,
ka.vit,
t,
blh.lat,
blh.long,
orient,
w.module(),
orientCalculator.astro.sunAngle(t, currentObj),
orientCalculator.calculateVisirAngle(ka, o.lat, o.long, o.height),
if (ka.v.z > 0) 0 else 1
)
if ((vp.sunAngle >= sunAngleMin) && ( ( krenByModule &&
(abs(vp.orientation.kren) >= krenMin) &&
(abs(vp.orientation.kren) <= krenMax)
) ||
( !krenByModule &&
(vp.orientation.kren >= krenMin) &&
(vp.orientation.kren <= krenMax)
)
)
) {
mpl.add(vp)
}
return true
}
return false
}
private fun equation(x: Double): Double {
var tang = -1.0
val ka = stepper.calculate(x)
if (ka != null) {
val orient = orientCalculator.calculateOrientOnPoint(ka, currentObj)
tang = orient.tang
}
return tang
}
fun clear(){
flightLineCalculator.clear()
stepper.clear()
mpl.clear()
}
}
@@ -0,0 +1,113 @@
package ballistics.mpl
import ballistics.types.EarthType
import ballistics.types.OrbitalPoint
import ballistics.types.Orientation
import ballistics.types.TangageType
import ballistics.types.WorkCSType
import ballistics.utils.astro.AstronomerJ2000
import ballistics.utils.math.Matrix3D
import ballistics.utils.math.Vector3D
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.atan
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sign
import kotlin.math.sin
import kotlin.math.sqrt
internal class OrientOnPointCalculator(val earthType: EarthType, val wcs: WorkCSType, val tangType: TangageType) {
var astro = AstronomerJ2000(earthType)
fun pointInWCS(
ka: OrbitalPoint,
point: Vector3D,
): Vector3D {
var w: Vector3D
if (wcs == WorkCSType.WCSOrbit) {
val kaA = astro.grinvToASK(ka)
val pa = astro.grinvToASK(point, ka.t)
val c = Matrix3D()
c.second = kaA.r.basis()
c.third = (kaA.v.rem(kaA.r)).basis()
c.first = c.second.rem(c.third)
val d: Vector3D = pa - kaA.r
w = c * d
} else {
val c = Matrix3D()
c.second = ka.r.basis()
c.third = (ka.v.rem(ka.r)).basis()
c.first = c.second.rem(c.third)
val d: Vector3D = point - ka.r
w = c * d
}
return w
}
fun calculateOrientOnPoint(
ka: OrbitalPoint,
point: Vector3D,
): Orientation {
val orient = Orientation(0.0, 0.0, 0.0)
val w = pointInWCS(ka, point)
if (tangType == TangageType.TTProactive) {
orient.tang = PI - atan2(w.x, w.y)
orient.kren = atan2(w.z, w.y / cos(orient.tang)) - PI
} else {
orient.kren = atan2(w.z, w.y) - PI
orient.tang = PI - atan2(w.x, w.z / sin(orient.kren))
}
if (abs(orient.tang) > PI) {
orient.tang -= sign(orient.tang) * PI * 2
}
if (abs(orient.kren) > PI) {
orient.kren -= sign(orient.kren) * PI * 2
}
return orient
}
fun calculateVisirAngle(
ka: OrbitalPoint,
b: Double,
l: Double,
h: Double,
): Double {
val rp = astro.earth.blh2xyz(b, l, h)
val r1 = Vector3D()
val r = Vector3D()
r1.x = ka.r.z - rp.z
r1.y = ka.r.x - rp.x
r1.z = ka.r.y - rp.y
val r4 = r1.y * cos(l) + r1.z * sin(l)
r.x = cos(b) * r1.x - sin(b) * r4
r.y = sin(b) * r1.x + cos(b) * r4
r.z = cos(l) * r1.z - sin(l) * r1.y
var angV: Double = sqrt(r.x * r.x + r.z * r.z)
if (angV != 0.0) {
angV = atan(r.y / sqrt(r.x * r.x + r.z * r.z))
angV = PI / 2 - angV
}
return angV
}
}
@@ -0,0 +1,40 @@
package ballistics.orbitalPoints
import ballistics.orbitalPoints.timeStepper.AbstractStepper
import ballistics.types.BallisticsError
import ballistics.types.EarthType
import ballistics.types.OrbitalPoint
import ballistics.types.RevolutionParameter
import ballistics.utils.earth.getEarth
abstract class AbstractOrbPointsCalculator(var earthType: EarthType) {
val points = mutableListOf<OrbitalPoint>()
val revolutions = mutableListOf<RevolutionParameter>()
val earth = getEarth(earthType)
var step = 60.0
abstract fun calculate(
tbegin: Double,
tend: Double,
): BallisticsError
abstract fun getStepper(): AbstractStepper
protected abstract fun fastStep(t: Double): OrbitalPoint
protected fun addRevolution(t: Double) {
val p = fastStep(t)
val blh = earth.xyz2blh(p.r)
revolutions.add(RevolutionParameter(p, blh.long, blh.h))
}
protected fun equation(x: Double): Double {
val p = fastStep(x)
return p.r.z
}
fun clear(){
points.clear()
revolutions.clear()
}
}
@@ -0,0 +1,232 @@
package ballistics.orbitalPoints
import ballistics.orbitalPoints.integrator.AbstractIntegrator
import ballistics.orbitalPoints.integrator.IntegratorAdams
import ballistics.orbitalPoints.integrator.IntegratorRK4
import ballistics.orbitalPoints.timeStepper.AbstractStepper
import ballistics.orbitalPoints.timeStepper.RungeStepper
import ballistics.types.BallisticsError
import ballistics.types.EarthType
import ballistics.types.InitialConditions
import ballistics.types.IntegrationType
import ballistics.types.ModDVType
import ballistics.types.OrbitalPoint
import ballistics.utils.math.Vector3D
import ballistics.utils.math.equations.AbstractEquationCalculator
import ballistics.utils.math.equations.EquationCalculatorSpan
import kotlin.math.abs
internal class OrbitalPointsIntegrator(val moddv: ModDVType, integratorType: IntegrationType, earthType: EarthType) :
AbstractOrbPointsCalculator(earthType) {
private val integrator: AbstractIntegrator
private val fastStepper: IntegratorRK4
var mic = mutableListOf<InitialConditions>()
private var x = arrayOf<Double>()
private var vit = 0
val equationCalculator: AbstractEquationCalculator = EquationCalculatorSpan()
var mTn : Double = 0.0
init {
when (integratorType) {
IntegrationType.RUNG4 -> {
integrator = IntegratorRK4(moddv, earthType)
fastStepper = IntegratorRK4(moddv, earthType)
}
IntegrationType.ADAMS7 -> {
integrator = IntegratorAdams(moddv, earthType)
fastStepper = IntegratorRK4(moddv, earthType)
}
}
equationCalculator.delta = 0.001
}
constructor(
nu: InitialConditions,
modDVType: ModDVType,
integratorType: IntegrationType,
earthType: EarthType,
) : this(modDVType, integratorType, earthType) {
mic.clear()
mic.add(nu)
}
constructor(
nu: Array<InitialConditions>,
modDVType: ModDVType,
integratorType: IntegrationType,
earthType: EarthType,
) : this(modDVType, integratorType, earthType) {
mic.clear()
mic.addAll(nu)
}
/**
* Расчет точек орбиты
* @param nu начальные условия движения центра масс КА
* @param tn время начала расчета
* @param tk время конца расчета
*/
fun calculate(
nu: InitialConditions,
tn: Double,
tk: Double,
): BallisticsError {
mTn = tn
var t = nu.point.t
x = arrayOf(nu.point.v.x, nu.point.v.y, nu.point.v.z, nu.point.r.x, nu.point.r.y, nu.point.r.z, nu.point.t)
integrator.setSBall(nu.sBall)
fastStepper.setSBall(nu.sBall)
integrator.setF81(nu.f81)
fastStepper.setF81(nu.f81)
integrator.accelerate(x)
points.clear()
revolutions.clear()
val equationCalculator: AbstractEquationCalculator = EquationCalculatorSpan()
equationCalculator.delta = 0.001
vit = nu.point.vit
if (abs(nu.point.r.z) < 0.00001 && nu.point.t >= tn) {
addRevolution(nu.point.t)
}
while (t <= tk) {
integrator.nextStep(x)
nextPoint(t)
for (i in 0..6)
x[i] = integrator.y[i]
t += integrator.step
}
return BallisticsError.OK
}
fun nextPoint(t : Double){
if (x[5] < 0 && integrator.y[5] >= 0) {
++vit
val tvuz = equationCalculator.calculate(t, t + integrator.step, this::equation)
tvuz?.let {
if (tvuz >= mTn) {
addRevolution(it)
}
}
}
if (t >= mTn) {
val p = OrbitalPoint(t, vit, Vector3D(x[3], x[4], x[5]), Vector3D(x[0], x[1], x[2]))
points.add(p)
}
}
/**
* Расчет точек орбиты
* @param mIC массив начальных условий движения центра масс КА
* @param tn время начала расчета
* @param tk время конца расчета
*/
fun calculate(
mIC: Array<InitialConditions>,
tn: Double,
tk: Double,
): BallisticsError {
points.clear()
revolutions.clear()
mTn = tn
if (mIC.isEmpty())
return BallisticsError.EMPTY_NU
var t = mIC[0].point.t
x =
arrayOf(
mIC[0].point.v.x,
mIC[0].point.v.y,
mIC[0].point.v.z,
mIC[0].point.r.x,
mIC[0].point.r.y,
mIC[0].point.r.z,
mIC[0].point.t,
)
integrator.setSBall(mIC[0].sBall)
fastStepper.setSBall(mIC[0].sBall)
integrator.setF81(mIC[0].f81)
fastStepper.setF81(mIC[0].f81)
integrator.accelerate(x)
vit = mIC[0].point.vit
var tkk: Double
for (i in mIC.indices) {
tkk = if (i == mIC.size - 1) tk else mIC[i + 1].point.t
while (t < tkk) {
integrator.nextStep(x)
nextPoint(t)
for (j in 0..6)
x[j] = integrator.y[j]
t += integrator.step
}
if (i < mIC.size - 1) {
if (points.isNotEmpty() && points.last().t >= mIC[i + 1].point.t) {
points.removeLast()
}
arrayOf(
mIC[i + 1].point.v.x,
mIC[i + 1].point.v.y,
mIC[i + 1].point.v.z,
mIC[i + 1].point.r.x,
mIC[i + 1].point.r.y,
mIC[i + 1].point.r.z,
mIC[i + 1].point.t,
).also { x = it }
integrator.setSBall(mIC[i + 1].sBall)
fastStepper.setSBall(mIC[i + 1].sBall)
integrator.setF81(mIC[i + 1].f81)
fastStepper.setF81(mIC[i + 1].f81)
integrator.accelerate(x)
vit = mIC[i + 1].point.vit
t = mIC[i + 1].point.t
}
}
return BallisticsError.OK
}
override fun calculate(
tbegin: Double,
tend: Double,
): BallisticsError {
if (mic.isEmpty()) {
return BallisticsError.EMPTY_NU
} else if (mic.size == 1) {
calculate(mic.first(), tbegin, tend)
} else {
calculate(mic.toTypedArray(), tbegin, tend)
}
return BallisticsError.OK
}
override fun getStepper(): AbstractStepper {
return RungeStepper(this)
}
override fun fastStep(t: Double): OrbitalPoint {
val dt = t - x[6]
fastStepper.step = dt
fastStepper.nextStep(x)
val p : OrbitalPoint = OrbitalPoint(
t,
vit,
Vector3D(fastStepper.y[3], fastStepper.y[4], fastStepper.y[5]),
Vector3D(fastStepper.y[0], fastStepper.y[1], fastStepper.y[2]),
)
return p
}
}
@@ -0,0 +1,70 @@
package ballistics.orbitalPoints
import ballistics.orbitalPoints.timeStepper.AbstractStepper
import ballistics.orbitalPoints.timeStepper.TLEStepper
import ballistics.types.BallisticsError
import ballistics.types.EarthType
import ballistics.types.OrbitalPoint
import ballistics.utils.math.equations.AbstractEquationCalculator
import ballistics.utils.math.equations.EquationCalculatorSpan
import kotlin.math.abs
internal class OrbitalPointsTLE(str1: String, str2: String, earthType: EarthType) :
AbstractOrbPointsCalculator(earthType) {
val stepper = TLEStepper(str1, str2, earthType)
private var vit = 0
override fun calculate(
tbegin: Double,
tend: Double,
): BallisticsError {
points.clear()
revolutions.clear()
val equationCalculator: AbstractEquationCalculator = EquationCalculatorSpan()
equationCalculator.delta = 0.001
vit = stepper.satellite.orbit.orbitNum
var t = stepper.baseEpoch.toDouble()
var p = stepper.calculate(t)
var pn: OrbitalPoint
if (abs(p.r.z) < 0.00001 && p.t >= tbegin) {
addRevolution(p.t)
}
try {
while (t <= tend) {
t = t + step
pn = stepper.calculate(t)
if (p.r.z < 0 && pn.r.z >= 0) {
++vit
val tvuz = equationCalculator.calculate(t - step, t + step, this::equation)
tvuz?.let {
if (tvuz >= tbegin) {
addRevolution(it)
}
}
}
if (t >= tbegin) {
pn.vit = vit
points.add(pn)
}
p = pn
}
}catch (ex : Exception){
return BallisticsError.STEPPER_ERROR
}
return BallisticsError.OK
}
override fun getStepper(): AbstractStepper {
return stepper
}
override fun fastStep(t: Double): OrbitalPoint {
val op = stepper.calculate(t)
op.vit = vit
return op
}
}
@@ -0,0 +1,38 @@
package ballistics.orbitalPoints.integrator
import ballistics.orbitalPoints.integrator.modDv.AbstractMDV
import ballistics.orbitalPoints.integrator.modDv.MDVBars
import ballistics.orbitalPoints.integrator.modDv.MDVFoto
import ballistics.orbitalPoints.integrator.modDv.MDVKondor
import ballistics.types.AstroType
import ballistics.types.BallisticsError
import ballistics.types.EarthType
import ballistics.types.ModDVType
internal abstract class AbstractIntegrator(var mdType: ModDVType, earthType: EarthType) {
protected val a = arrayOf(1, 2, 2, 1)
protected val prav: AbstractMDV
var step = 60.0
val y = arrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
init {
when (mdType) {
ModDVType.FOTO -> prav = MDVFoto(earthType)
ModDVType.METEORM1 -> prav = MDVKondor(earthType, AstroType.ATJ2000, 8, true)
ModDVType.METEORM2 -> prav = MDVKondor(earthType, AstroType.ATJ2000, 8, true)
ModDVType.KONDOR -> prav = MDVKondor(earthType, AstroType.ATJ2000, 24, false)
ModDVType.KONDOR_PROGNOZ -> prav = MDVKondor(earthType, AstroType.ATJ2000, 24, true)
ModDVType.BARS -> prav = MDVBars(earthType)
}
}
abstract fun nextStep(x: Array<Double>): BallisticsError
abstract fun accelerate(x: Array<Double>): BallisticsError
abstract fun setSBall(s: Double)
fun setF81(f81: Double) {
prav.f81 = if (f81 < 25.0) 25.0 else f81
}
}
@@ -0,0 +1,157 @@
package ballistics.orbitalPoints.integrator
import ballistics.types.BallisticsError
import ballistics.types.EarthType
import ballistics.types.ModDVType
internal class IntegratorAdams(mdType: ModDVType, earthType: EarthType) : AbstractIntegrator(mdType, earthType) {
private val rng: IntegratorRK4 = IntegratorRK4(mdType, earthType)
val a2 =
arrayOf(
0.11367394179894179894E-1,
-0.93840939153439153439E-1,
0.343080357142857142857,
-0.732035383597883597884,
0.1017964616402116402116E+1,
-0.1006919642857142857143E+1,
0.1156159060846560846560E+1,
0.304224537037037037037,
)
val a1 =
arrayOf(
-0.304224537037037037037,
0.2445163690476190476190E+1,
-0.8612127976190476190476E+1,
0.17379654431216931216931E+2,
-0.22027752976190476190476E+2,
0.18054538690476190476190E+2,
-0.9525206679894179894180E+1,
0.3589955357142857142857E+1,
)
var intpoints =
arrayOf(
arrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
arrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
arrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
arrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
arrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
arrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
arrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
arrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
arrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
arrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
arrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
)
override fun accelerate(x: Array<Double>): BallisticsError {
for (m in 0..6)
intpoints[0][m] = x[m]
var er = prav.calculate(intpoints[0])
if (er != BallisticsError.OK) {
return er
}
for (n in 0..6)
intpoints[9][n] = prav.y[n]
// кратность шага по Рунге шагу по Адамсу
val dStep = 2
val ihrng = -step / dStep
rng.step = ihrng
// текущий элемент массива
var j = 8
val fld1 = arrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
val fld2 = arrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
val fld3 = arrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
for (m in 0..6)
fld1[m] = x[m]
for (i in 0..6) {
for (q in 0..1) {
er = rng.nextStep(fld1)
if (er != BallisticsError.OK) {
return er
}
for (m in 0..6)
fld2[m] = rng.y[m]
for (m in 0..6)
fld1[m] = fld2[m]
}
er = prav.calculate(fld1)
if (er != BallisticsError.OK) {
return er
}
for (m in 0..6)
fld3[m] = prav.y[m]
for (m in 0..6)
intpoints[j][m] = fld3[m]
j--
}
return BallisticsError.OK
}
override fun setSBall(s: Double) {
prav.sBall = s
rng.setSBall(s)
}
private fun copyPoints(from : Int, to : Int){
for (m in 0..6)
intpoints[from][m] = intpoints[to][m]
}
override fun nextStep(x: Array<Double>): BallisticsError {
for (m in 0..6)
intpoints[1][m] = x[m]
// Экстрaполяция
var r: Double
for (j in 1..6) {
r = 0.0
for (k in 1..8)
r = r + intpoints[1 + k][j - 1] * a1[k - 1]
intpoints[1][j - 1] = intpoints[1][j - 1] + r * step
}
intpoints[1][6] = intpoints[1][6] + step
var er = prav.calculate(intpoints[1])
if (er != BallisticsError.OK) {
return er
}
for (n in 0..6)
intpoints[10][n] = prav.y[n]
copyPoints(1,0)
// Интерполяция
for (j in 1..6) {
r = 0.0
for (k in 1..8)
r = r + intpoints[2 + k][j - 1] * a2[k - 1]
intpoints[1][j - 1] = intpoints[1][j - 1] + step * r
}
intpoints[1][6] = intpoints[1][6] + step
er = prav.calculate(intpoints[1])
if (er != BallisticsError.OK)
return er
for (n in 0..6)
intpoints[10][n] = prav.y[n]
for (m in 0..6)
y[m] = intpoints[1][m]
for (j in 0..9)
copyPoints(j, j+1)
return BallisticsError.OK
}
}
@@ -0,0 +1,42 @@
package ballistics.orbitalPoints.integrator
import ballistics.types.BallisticsError
import ballistics.types.EarthType
import ballistics.types.ModDVType
internal class IntegratorRK4(mdType: ModDVType, earthType: EarthType) : AbstractIntegrator(mdType, earthType) {
override fun accelerate(x: Array<Double>): BallisticsError {
return BallisticsError.OK
}
override fun setSBall(s: Double) {
prav.sBall = s
}
override fun nextStep(x: Array<Double>): BallisticsError {
val step6 = step / 6
var r = arrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
for (i in 0..6) {
y[i] = x[i]
r[i] = x[i]
}
for (j in 0..3) {
var er = prav.calculate(r)
if (er != BallisticsError.OK) {
return er
}
for (i in 0..5)
y[i] = y[i] + step6 * a[j] * prav.y[i]
if (j == 3) {
break
}
for (i in 0..5)
r[i] = x[i] + step / a[j + 1] * prav.y[i]
}
y[6] = x[6] + step
return BallisticsError.OK
}
}
@@ -0,0 +1,36 @@
package ballistics.orbitalPoints.integrator.modDv
import ballistics.types.BallisticsError
import ballistics.types.EarthType
import ballistics.utils.earth.AbstractEarth
import ballistics.utils.earth.getEarth
internal abstract class AbstractMDV(val earthType: EarthType) {
protected val hminimum = 160000.0
protected var r = 0.0
protected var v = 0.0
protected var fik = 0.0
protected var bca = 0.0
protected var oma = 0.0
protected var earth: AbstractEarth = getEarth(earthType)
var sBall = 0.0
var f81 = 100.0
var y = arrayOf(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
fun prepare(x: Array<Double>) {
var z2 = x[5] * x[5]
var r2 = Math.pow(x[3], 2.0) + Math.pow(x[4], 2.0) + z2
var v2 = x[2] * x[2] + x[1] * x[1] + x[0] * x[0]
r = Math.sqrt(r2)
v = Math.sqrt(v2)
fik = z2 / r2
var d = 5 * fik
var c = earth.ekvRadius * earth.ekvRadius / r2 * 1.5 * earth.c20
var b = earth.fM / r / r2
var a = b * (1 + c * (d - 1))
bca = 2 * b * c - a
oma = earth.wEarth * earth.wEarth - a
}
abstract fun calculate(x: Array<Double>): BallisticsError
}
@@ -0,0 +1,51 @@
package ballistics.orbitalPoints.integrator.modDv
import ballistics.types.BallisticsError
import ballistics.types.EarthType
import ballistics.utils.atmosphere.getAtm81
import ballistics.utils.geopotential.Geopotencial1990
import ballistics.utils.math.Vector3D
internal open class MDVBars(earthType: EarthType) : AbstractMDV(earthType) {
protected var geopotential = Geopotencial1990()
override fun calculate(x: Array<Double>): BallisticsError {
prepare(x)
y[0] = oma * x[3] + earth.wEarth * x[1] * 2
y[1] = oma * x[4] - earth.wEarth * x[0] * 2
y[2] = bca * x[5]
y[3] = x[0]
y[4] = x[1]
y[5] = x[2]
// УЧЕТ АТМОСФЕРЫ //
val h = r - earth.ekvRadius * (1.0 - earth.alphaEllips * fik)
if (h < hminimum) {
return BallisticsError.H_MINIMUM_ERROR
}
val ro = getAtm81(h)
val srv = v * ro * sBall
y[0] -= srv * x[0]
y[1] -= srv * x[1]
y[2] -= srv * x[2]
// УЧЕТ АНОМАЛИЙ ГЕОПОТЕНЦИАЛА //
var xyz = Vector3D(0.0, 0.0, 0.0)
var rez: BallisticsError = geopotential.anomkond(x, 16, 16, xyz)
if (rez != BallisticsError.OK) {
return rez
}
y[0] = y[0] + xyz.x / 1000000.0
y[1] = y[1] + xyz.y / 1000000.0
y[2] = y[2] + xyz.z / 1000000.0
return BallisticsError.OK
}
}
@@ -0,0 +1,33 @@
package ballistics.orbitalPoints.integrator.modDv
import ballistics.types.BallisticsError
import ballistics.types.EarthType
import ballistics.utils.atmosphere.getAtm62
internal class MDVFoto(earthType: EarthType) : AbstractMDV(earthType) {
override fun calculate(x: Array<Double>): BallisticsError {
prepare(x)
y[0] = oma * x[3] + earth.wEarth * x[1] * 2
y[1] = oma * x[4] - earth.wEarth * x[0] * 2
y[2] = bca * x[5]
y[3] = x[0]
y[4] = x[1]
y[5] = x[2]
val h = r - earth.ekvRadius * (1 - earth.alphaEllips * fik)
if (h < hminimum) {
return BallisticsError.H_MINIMUM_ERROR
}
val ro = getAtm62(h, 6)
val srv = v * ro * sBall
y[0] -= srv * x[0]
y[1] -= srv * x[1]
y[2] -= srv * x[2]
return BallisticsError.OK
}
}
@@ -0,0 +1,99 @@
package ballistics.orbitalPoints.integrator.modDv
import ballistics.types.AstroType
import ballistics.types.BallisticsError
import ballistics.types.EarthType
import ballistics.utils.astro.AstronomerJ2000
import ballistics.utils.atmosphere.Atmosphere2004
import ballistics.utils.math.Vector3D
import java.time.LocalDateTime
import java.time.ZoneOffset
import kotlin.math.cos
import kotlin.math.sin
internal open class MDVKondor(
earthType: EarthType,
var astroType: AstroType,
var garmonics: Int,
var isProgn: Boolean,
) : MDVBars(earthType) {
protected val amax = 0.6378136E+7
protected val alpha = 3.3528037E-3F
protected var astro = AstronomerJ2000(earthType)
protected var atm2004 = Atmosphere2004()
override fun calculate(x: Array<Double>): BallisticsError {
prepare(x)
y[0] = oma * x[3] + earth.wEarth * x[1] * 2
y[1] = oma * x[4] - earth.wEarth * x[0] * 2
y[2] = bca * x[5]
y[3] = x[0]
y[4] = x[1]
y[5] = x[2]
if (!isProgn) {
// УЧЕТ АТМОСФЕРЫ //
var rb1: Double = fik * alpha
rb1 = 1.0 - rb1
rb1 *= amax
val h: Double = r - rb1
if (h < hminimum) {
return BallisticsError.H_MINIMUM_ERROR
}
var sun = astro.sunCoordinates(x[6])
sun = astro.askToGrinvich(sun, x[6]).basis()
var day = (LocalDateTime.ofEpochSecond(x[6].toLong(), 0, ZoneOffset.UTC).dayOfYear - 1).toDouble()
var ro = atm2004.atm2004Kav(f81, day, h, x, sun.x, sun.y, sun.z)
var srv = v * ro * sBall
y[0] -= srv * x[0]
y[1] -= srv * x[1]
y[2] -= srv * x[2]
}
// УЧЕТ АНОМАЛИЙ ГЕОПОТЕНЦИАЛА //
var xyz = Vector3D(0.0, 0.0, 0.0)
var rez: BallisticsError = geopotential.anomkond(x, garmonics, garmonics, xyz)
if (rez != BallisticsError.OK) {
return rez
}
y[0] = y[0] + xyz.x / 1000000.0
y[1] = y[1] + xyz.y / 1000000.0
y[2] = y[2] + xyz.z / 1000000.0
if (!isProgn) {
// УЧЕТ Луны и Солнца
var tsol = astro.si2000(x[6])
val cg: Double = cos(tsol)
val sg: Double = sin(tsol)
// Перевод из ОГЭСК в АГЭСК
// Перевод из ОГЭСК в АГЭСК
val x2: Double = (x[3] * cg - x[4] * sg) / 1000.0
val y2: Double = (x[3] * sg + x[4] * cg) / 1000.0
val z2: Double = x[5] / 1000.0
var cor = astro.sunMoonCorrection(x[6], x2, y2, z2)
var ddx = (cor.x * cg + cor.y * sg) * 1000.0
var ddy = (cor.y * cg - cor.x * sg) * 1000.0
var ddz = cor.z * 1000.0
y[0] = y[0] + ddx // Учет влияния Солнца и Луны
y[1] = y[1] + ddy
y[2] = y[2] + ddz
}
return BallisticsError.OK
}
}
@@ -0,0 +1,14 @@
package ballistics.orbitalPoints.timeStepper
import ballistics.types.OrbitalPoint
interface AbstractStepper {
fun calculate(t: Double): OrbitalPoint?
fun calculate(
t: Double,
p: OrbitalPoint,
): OrbitalPoint?
fun clear()
}
@@ -0,0 +1,119 @@
package ballistics.orbitalPoints.timeStepper
import ballistics.orbitalPoints.AbstractOrbPointsCalculator
import ballistics.orbitalPoints.integrator.AbstractIntegrator
import ballistics.orbitalPoints.integrator.IntegratorRK4
import ballistics.types.BallisticsError
import ballistics.types.EarthType
import ballistics.types.ModDVType
import ballistics.types.OrbitalPoint
import ballistics.utils.math.Vector3D
import kotlin.math.abs
import kotlin.math.round
class RungeStepper(var mdType: ModDVType, earthType: EarthType) : AbstractStepper {
private val integrator: AbstractIntegrator = IntegratorRK4(mdType, earthType) // ModDVType.FOTO//BARS
var points = mutableListOf<OrbitalPoint>()
constructor(source: AbstractOrbPointsCalculator) : this(ModDVType.FOTO, source.earthType) {
points = source.points
}
constructor(points: MutableList<OrbitalPoint>, earth: EarthType) : this(ModDVType.FOTO, earth) {
this.points = points
}
private fun findPoint(t: Double): OrbitalPoint? {
val cnt = points.size
val dt = t - points.first().t
var ind: Int = round(dt / integrator.step).toInt()
if (ind >= -1 && ind < cnt + 1) {
if (ind < 0) {
ind = 0
}
if (ind >= cnt) {
ind = cnt - 1
}
while ((t - points[ind].t > integrator.step) && (ind < points.size)) {
++ind
}
if (t - points[ind].t < 0 && ind > 0) {
--ind
}
return points[ind]
}
return null
}
override fun calculate(t: Double): OrbitalPoint? {
val p = findPoint(t)
return p?.let {
val dt = t - p.t
val st = integrator.step
integrator.step = dt
val x = arrayOf(p.v.x, p.v.y, p.v.z, p.r.x, p.r.y, p.r.z, p.t)
val r = integrator.nextStep(x)
if (r != BallisticsError.OK) {
return null
}
var vit = p.vit
if (dt >= 0 && p.r.z > 0 && x[5] < 0 && p.v.z > 0) {
vit++
}
if (dt < 0 && p.r.z < 0 && x[5] > 0 && p.v.z > 0) {
vit--
}
integrator.step = st
OrbitalPoint(
integrator.y[6],
vit,
Vector3D(integrator.y[3], integrator.y[4], integrator.y[5]),
Vector3D(integrator.y[0], integrator.y[1], integrator.y[2]),
)
} ?: run {
null
}
}
override fun clear() {
points.clear()
}
override fun calculate(
t: Double,
p: OrbitalPoint,
): OrbitalPoint? {
val dt = t - p.t
if (abs(dt) > 80.0) {
return null
}
val st = integrator.step
integrator.step = dt
val x = arrayOf(p.v.x, p.v.y, p.v.z, p.r.x, p.r.y, p.r.z, p.t)
if (integrator.nextStep(x) != BallisticsError.OK) {
return null
}
var vit = p.vit
if (dt >= 0 && p.r.z > 0 && x[5] < 0 && p.v.z > 0) {
vit++
}
if (dt < 0 && p.r.z < 0 && x[5] > 0 && p.v.z > 0) {
vit--
}
integrator.step = st
return OrbitalPoint(
integrator.y[6],
vit,
Vector3D(integrator.y[3], integrator.y[4], integrator.y[5]),
Vector3D(integrator.y[0], integrator.y[1], integrator.y[2]),
)
}
}
@@ -0,0 +1,96 @@
package ballistics.orbitalPoints.timeStepper
import ballistics.types.EarthType
import ballistics.types.OrbitalPoint
import ballistics.utils.astro.AstronomerJ2000
import ballistics.utils.math.Vector3D
import org.nstart.dep265.tletools.zeptomoby.core.Julian
import org.nstart.dep265.tletools.zeptomoby.core.TLE
import org.nstart.dep265.tletools.zeptomoby.orbit.Satellite
import java.util.Calendar
import java.util.GregorianCalendar
import java.util.TimeZone
class TLEStepper(str1: String, str2: String, earthType: EarthType) : AbstractStepper {
val astro = AstronomerJ2000(earthType)
val tleParser: TLE = TLE("", str1, str2)
val satellite: Satellite = Satellite(tleParser)
val baseEpoch: Double
init {
baseEpoch = extractUTCMillis(satellite) / 1000.0 + 10800
}
override fun clear() {
}
private fun extractUTCMillis(
year: Int,
month: Int,
dayOfMonth: Double,
): Long {
val gc = GregorianCalendar()
gc[Calendar.YEAR] = year
gc[Calendar.MONTH] = month - 1
gc[Calendar.DAY_OF_MONTH] = dayOfMonth.toInt()
gc.timeZone = TimeZone.getTimeZone("UTC")
var dfrac = dayOfMonth - dayOfMonth.toLong()
dfrac *= 24.0
gc[Calendar.HOUR_OF_DAY] = dfrac.toInt()
dfrac = 60.0 * (dfrac - dfrac.toInt())
gc[Calendar.MINUTE] = dfrac.toInt()
dfrac = 60.0 * (dfrac - dfrac.toInt())
gc[Calendar.SECOND] = dfrac.toInt()
dfrac = 1000.0 * (dfrac - dfrac.toInt())
gc[Calendar.MILLISECOND] = dfrac.toInt()
return gc.timeInMillis
}
private fun extractUTCMillis(date: Julian.DateComponent): Long = extractUTCMillis(date.year, date.mon ?: 1, date.dom ?: 1.0)
private fun extractUTCMillis(sat: Satellite): Long =
extractUTCMillis(sat.orbit.epoch.getComponent())
override fun calculate(t: Double): OrbitalPoint {
try {
val utcMillis = baseEpoch
val dt = t - utcMillis
val pos = satellite.positionEci(dt / 60)
val ask =
OrbitalPoint(
t,
satellite.orbit.orbitNum + (dt / satellite.orbit.period).toInt(),
Vector3D(
pos.position.x * 1000.0,
pos.position.y * 1000.0,
pos.position.z * 1000.0,
),
Vector3D(
pos.velocity.x * 1000.0,
pos.velocity.y * 1000.0,
pos.velocity.z * 1000.0,
),
)
return astro.askToGrinvich(ask)
} catch (ex : Exception){
throw ex
}
}
override fun calculate(
t: Double,
p: OrbitalPoint,
): OrbitalPoint? {
return calculate(t)
}
}
@@ -0,0 +1,57 @@
package org.nstart.dep265.tletools.tools
import java.lang.NumberFormatException
import kotlin.math.min
fun <Double> MutableList<Double>.mapInPlace(transform: (Double) -> Double) {
for (i in this.indices)
this[i] = transform(this[i])
}
fun <Double> MutableList<Double>.mapInPlaceIndexed(transform: (Int, Double) -> Double) {
for (i in this.indices)
this[i] = transform(i, this[i])
}
fun String.leftJustified(size: Int, char: Char = ' ', truncate: Boolean = false): String {
val padLen = size - length
val builder = StringBuilder(this.padEnd(size, char))
if (padLen <= 0 && truncate) {
builder.replace(length + padLen, length, "")
}
return builder.toString()
}
fun String.rightJustified(size: Int, char: Char = ' ', truncate: Boolean = false): String {
val padLen = size - length
val builder = StringBuilder(this.padStart(size, char))
if (padLen <= 0 && truncate) {
builder.replace(length + padLen, length, "")
}
return builder.toString()
}
fun String.substringByLength(startIndex: Int, size: Int): String = this.substring(startIndex, min(startIndex + size, this.length))
operator fun <T, V: Enum<V>> Array<T>.get(index: Enum<V>): T = this[index.ordinal]
operator fun <T, V: Enum<V>> Array<T>.set(index: Enum<V>, value: T) {
this[index.ordinal] = value
}
fun String?.isParsableToNum(): Boolean {
if (this == null) return false
try {
this.toDouble()
}
catch (e: NumberFormatException) {
return false
}
return true
}
@@ -0,0 +1,74 @@
package org.nstart.dep265.tletools.zeptomoby.core
import org.nstart.dep265.tletools.zeptomoby.core.iau.IAU
import org.nstart.dep265.tletools.zeptomoby.core.iau.IAU76
import org.nstart.dep265.tletools.zeptomoby.core.wgs.WGS
import org.nstart.dep265.tletools.zeptomoby.core.wgs.WGS72
import kotlin.math.atan
import kotlin.math.floor
import kotlin.math.pow
import kotlin.math.sqrt
@Suppress("unused")
object Globals {
const val pi: Double = Math.PI
const val twoPi: Double = 2.0 * org.nstart.dep265.tletools.zeptomoby.core.Globals.pi
const val radsPerDeg = org.nstart.dep265.tletools.zeptomoby.core.Globals.pi / 180.0
const val gm = 398601.2 // Earth gravitational constant, km^3/sec^2
const val geoSyncAlt = 42241.892 // km
const val earthDia = 12800.0 // km
const val daySidereal = 86164.09 // sec
const val day24HR = 86400 // sec
const val ae = 1.0
var iau: IAU = IAU76
var wgs: WGS = WGS72
set(value) {
field = value
org.nstart.dep265.tletools.zeptomoby.core.Globals.ck2 = org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.j2 * 0.5
org.nstart.dep265.tletools.zeptomoby.core.Globals.ck4 = -3.0 * org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.j4 / 8.0
org.nstart.dep265.tletools.zeptomoby.core.Globals.xj3 = org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.j3
org.nstart.dep265.tletools.zeptomoby.core.Globals.qo = org.nstart.dep265.tletools.zeptomoby.core.Globals.ae + 120.0 / org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.xkmPer
org.nstart.dep265.tletools.zeptomoby.core.Globals.s = org.nstart.dep265.tletools.zeptomoby.core.Globals.ae + 78.0 / org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.xkmPer
org.nstart.dep265.tletools.zeptomoby.core.Globals.xke = sqrt(3600.0 * org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.ge / (org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.xkmPer * org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.xkmPer * org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.xkmPer))
org.nstart.dep265.tletools.zeptomoby.core.Globals.qoms2t = (org.nstart.dep265.tletools.zeptomoby.core.Globals.qo - org.nstart.dep265.tletools.zeptomoby.core.Globals.s).pow(4)
}
var ck2 = org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.j2 * 0.5
var ck4 = -3.0 * org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.j4 * 0.125
var xj3 = org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.j3
var qo = org.nstart.dep265.tletools.zeptomoby.core.Globals.ae + 120.0 / org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.xkmPer
var s = org.nstart.dep265.tletools.zeptomoby.core.Globals.ae + 78.0 / org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.xkmPer
const val hrPerDay = 24.0 // Hours per day (solar)
const val minPerDay = 1440.0 // Minutes per day (solar)
const val secPerDay = 86400.0 // Seconds per day (solar)
const val omegaE = 1.00273790934 // earth rotation per sidereal day
var xke = sqrt(3600.0 * org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.ge / //sqrt(ge) ER^3/min^2
(org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.xkmPer * org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.xkmPer * org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.xkmPer))
var qoms2t = (org.nstart.dep265.tletools.zeptomoby.core.Globals.qo - org.nstart.dep265.tletools.zeptomoby.core.Globals.s).pow(4) //(QO - S)^4 ER^4
fun sqr(x: Double) = x*x
fun fmod(numerator: Double, denominator: Double): Double {
val tquot = floor(numerator / denominator).toLong()
return numerator - tquot * denominator
}
fun fmod2p(arg: Double): Double {
val modU = org.nstart.dep265.tletools.zeptomoby.core.Globals.fmod(
arg,
org.nstart.dep265.tletools.zeptomoby.core.Globals.twoPi
)
return if (modU < 0.0) modU + org.nstart.dep265.tletools.zeptomoby.core.Globals.twoPi else modU
}
fun acTan(sinX: Double, cosX: Double): Double =
if (cosX == 0.0) (if (sinX > 0.0) org.nstart.dep265.tletools.zeptomoby.core.Globals.pi * 0.5 else 1.5 * org.nstart.dep265.tletools.zeptomoby.core.Globals.pi)
else (if (cosX > 0.0) atan(sinX/cosX) else org.nstart.dep265.tletools.zeptomoby.core.Globals.pi + atan(sinX/cosX))
fun rad2deg(r: Double): Double = Math.toDegrees(r)
fun deg2rad(d: Double): Double = Math.toRadians(d)
}
@@ -0,0 +1,120 @@
package org.nstart.dep265.tletools.zeptomoby.core
@Suppress("unused")
open class Julian {
object EpochConst {
const val EPOCH_JAN0_12H_1900 = 2415020.0 // Dec 31.5 1899 = Dec 31 1899 12h UTC
const val EPOCH_JAN1_00H_1900 = 2415020.5 // Jan 1.0 1900 = Jan 1 1900 00h UTC
const val EPOCH_JAN1_12H_2000 = 2451545.0 // Jan 1.5 2000 = Jan 1 2000 12h UTC
}
object Static {
fun isLeapYear(y: Int): Boolean =
(y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)
}
data class DateComponent(val year: Int, val mon: Int? = null, val dom: Double? = null)
protected var date: Double = 0.0
constructor() {
initialize(2000, 1.0)
}
constructor(year: Int, day: Double) {
initialize(year, day)
}
constructor(year: Int, mon: Int, day: Int, hour: Int, min: Int, sec: Double = 0.0) {
val f1 = (275.0 * mon / 9.0).toInt()
val f2 = ((mon + 9.0) / 12.0).toInt()
val n = if (org.nstart.dep265.tletools.zeptomoby.core.Julian.Static.isLeapYear(year)) f1 - f2 + day - 30
else f1 - 2 * f2 + day - 30
val dblDay = n + (hour + (min + sec / 60.0) / 60.0) / 24.0
initialize(year, dblDay)
}
fun toGMST(): Double {
val ut = org.nstart.dep265.tletools.zeptomoby.core.Globals.fmod(date + 0.5, 1.0)
val tu = (fromJan1_12h_2000() - ut) / 36525.0
var gmst = 24110.54841 + tu * (8640184.812866 + tu * (0.093104 - tu * 6.2e-06))
gmst = org.nstart.dep265.tletools.zeptomoby.core.Globals.fmod(
gmst + org.nstart.dep265.tletools.zeptomoby.core.Globals.secPerDay * org.nstart.dep265.tletools.zeptomoby.core.Globals.omegaE * ut,
org.nstart.dep265.tletools.zeptomoby.core.Globals.secPerDay
)
if (gmst < 0.0) gmst += org.nstart.dep265.tletools.zeptomoby.core.Globals.secPerDay
return org.nstart.dep265.tletools.zeptomoby.core.Globals.twoPi * (gmst / org.nstart.dep265.tletools.zeptomoby.core.Globals.secPerDay)
}
fun toLMST(lon: Double): Double = org.nstart.dep265.tletools.zeptomoby.core.Globals.fmod(
toGMST() + lon,
org.nstart.dep265.tletools.zeptomoby.core.Globals.twoPi
)
@Suppress("FunctionName")
fun fromJan0_12h_1900(): Double = date - org.nstart.dep265.tletools.zeptomoby.core.Julian.EpochConst.EPOCH_JAN0_12H_1900
@Suppress("FunctionName")
fun fromJan1_00h_1900(): Double = date - org.nstart.dep265.tletools.zeptomoby.core.Julian.EpochConst.EPOCH_JAN1_00H_1900
@Suppress("FunctionName")
fun fromJan1_12h_2000(): Double = date - org.nstart.dep265.tletools.zeptomoby.core.Julian.EpochConst.EPOCH_JAN1_12H_2000
fun getComponent(): org.nstart.dep265.tletools.zeptomoby.core.Julian.DateComponent {
val jdAdj = date + 0.5
val z = jdAdj.toInt()
val f = jdAdj - z
val alpha = ((z - 1867216.25) / 36524.25).toInt()
val a = z + 1 + alpha - (alpha * 0.25).toInt()
val b = a + 1524.0
val c = ((b - 122.1) / 365.25).toInt()
val d = (c * 365.25).toInt()
val e = ((b - d) / 30.6001).toInt()
val dom = b - d - (e * 30.6001).toInt() + f
val month = if (e < 13.5) e - 1 else e - 13
val year = if (month > 2.5) c - 4716 else c - 4715
return org.nstart.dep265.tletools.zeptomoby.core.Julian.DateComponent(year, month, dom)
}
fun addDay(day: Double) {
date += day
}
fun addHour(hr: Double) {
date += hr / org.nstart.dep265.tletools.zeptomoby.core.Globals.hrPerDay
}
fun addMin(min: Double) {
date += min / org.nstart.dep265.tletools.zeptomoby.core.Globals.minPerDay
}
fun addSec(sec: Double) {
date += sec / org.nstart.dep265.tletools.zeptomoby.core.Globals.secPerDay
}
fun spanDay (b: org.nstart.dep265.tletools.zeptomoby.core.Julian): Double = date - b.date
fun spanHour(b: org.nstart.dep265.tletools.zeptomoby.core.Julian): Double = spanDay(b) * org.nstart.dep265.tletools.zeptomoby.core.Globals.hrPerDay
fun spanMin (b: org.nstart.dep265.tletools.zeptomoby.core.Julian): Double = spanDay(b) * org.nstart.dep265.tletools.zeptomoby.core.Globals.minPerDay
fun spanSec (b: org.nstart.dep265.tletools.zeptomoby.core.Julian): Double = spanDay(b) * org.nstart.dep265.tletools.zeptomoby.core.Globals.secPerDay
protected fun initialize(year: Int, day: Double) {
assert((year > 1582) && (year < 3000))
assert((day >= 1.0) && (day < 367.0))
val iyear = year - 1
val a: Int = iyear / 100
val b: Int = 2 - a + a / 4
val newYears: Double = (365.25 * iyear).toInt() +
(30.6001 * 14).toInt() +
1720994.5 + b
date = newYears + day
}
}
@@ -0,0 +1,101 @@
package org.nstart.dep265.tletools.zeptomoby.core
import org.nstart.dep265.tletools.zeptomoby.core.Globals.deg2rad
import org.nstart.dep265.tletools.zeptomoby.core.Globals.rad2deg
import org.nstart.dep265.tletools.zeptomoby.core.Globals.sqr
import org.nstart.dep265.tletools.zeptomoby.core.coord.Geo
import org.nstart.dep265.tletools.zeptomoby.core.coord.Topo
import org.nstart.dep265.tletools.zeptomoby.core.eci.EciTime
import kotlin.math.*
@Suppress("unused")
open class Site {
constructor(degLat: Double, degLon: Double, kmAlt: Double, n: String) {
name = n
geo = Geo(deg2rad(degLat), deg2rad(degLon), kmAlt)
}
constructor(degLat: Double, degLon: Double, kmAlt: Double):
this(degLat, degLon, kmAlt, "")
constructor(g: Geo) {
name = ""
geo = g
}
override fun toString() = if (name.isEmpty()) "$geo" else "$name $geo"
fun positionEci(julian: org.nstart.dep265.tletools.zeptomoby.core.Julian): EciTime = EciTime(geo, julian)
@Deprecated("", ReplaceWith("positionEci(geo, julian)"))
fun position(julian: org.nstart.dep265.tletools.zeptomoby.core.Julian): EciTime = EciTime(geo, julian)
fun lookAngle(time: EciTime): Topo {
val date: org.nstart.dep265.tletools.zeptomoby.core.Julian = time.date
val eciSite = EciTime(geo, date)
val vecRgRate = org.nstart.dep265.tletools.zeptomoby.core.Vector(
time.velocity.x - eciSite.velocity.x,
time.velocity.y - eciSite.velocity.y,
time.velocity.z - eciSite.velocity.z
)
val x: Double = time.position.x - eciSite.position.x
val y: Double = time.position.y - eciSite.position.y
val z: Double = time.position.z - eciSite.position.z
val w: Double = sqrt(sqr(x) + sqr(y) + sqr(z))
val vecRange = org.nstart.dep265.tletools.zeptomoby.core.Vector(x, y, z, w)
val theta = date.toLMST(longitudeRad)
val sinLat = sin(latitudeRad)
val cosLat = cos(latitudeRad)
val sinTheta = sin(theta)
val cosTheta = cos(theta)
val topS = sinLat * (cosTheta * vecRange.x + sinTheta * vecRange.y) - cosLat * vecRange.z
val topE = -sinTheta * vecRange.x + cosTheta * vecRange.y
val topZ = cosLat * (cosTheta * vecRange.x + sinTheta * vecRange.y) + sinLat * vecRange.z
var az = atan(-topE / topS)
if (topS > 0.0) az += org.nstart.dep265.tletools.zeptomoby.core.Globals.pi
if (az < 0.0) az += org.nstart.dep265.tletools.zeptomoby.core.Globals.twoPi
var el = asin(topZ / vecRange.w)
val rate = (vecRange.x * vecRgRate.x +
vecRange.y * vecRgRate.y +
vecRange.z * vecRgRate.z) / vecRange.w
if (atmosphericCorrection) {
val saveEl = el
el += deg2rad(
(1.02 / tan(
deg2rad(rad2deg(el) + 10.3 / (rad2deg(el) + 5.11))
)) / 60.0
)
if (el < 0.0) el = saveEl
if (el > org.nstart.dep265.tletools.zeptomoby.core.Globals.pi * 0.5) el = org.nstart.dep265.tletools.zeptomoby.core.Globals.pi * 0.5
}
return Topo(az, // azimuth, radians
el, // elevation, radians
vecRange.w, // range, km
rate // rate, km / sec
)
}
val latitudeRad: Double
get() = geo.latitudeRad
val longitudeRad: Double
get() = geo.longitudeRad
val latitudeDeg: Double
get() = geo.latitudeDeg
val longitudeDeg: Double
get() = geo.longitudeDeg
val name: String
val geo: Geo
val atmosphericCorrection = false
}
@@ -0,0 +1,276 @@
package org.nstart.dep265.tletools.zeptomoby.core
import org.nstart.dep265.tletools.tools.get
import org.nstart.dep265.tletools.tools.isParsableToNum
import org.nstart.dep265.tletools.tools.set
import org.nstart.dep265.tletools.tools.substringByLength
import org.nstart.dep265.tletools.zeptomoby.core.TLE.ProtectedStatic.convertUnits
@Suppress("unused")
open class TLE {
object TLEPartLength {
const val TLE_LEN_LINE_DATA = 69; const val TLE_LEN_LINE_NAME = 24
const val TLE1_COL_SATNUM = 2; const val TLE1_LEN_SATNUM = 5
const val TLE1_COL_INTLDESC_A = 9; const val TLE1_LEN_INTLDESC_A = 2
const val TLE1_COL_INTLDESC_B = 11; const val TLE1_LEN_INTLDESC_B = 3
const val TLE1_COL_INTLDESC_C = 14; const val TLE1_LEN_INTLDESC_C = 3
const val TLE1_COL_EPOCH_A = 18; const val TLE1_LEN_EPOCH_A = 2
const val TLE1_COL_EPOCH_B = 20; const val TLE1_LEN_EPOCH_B = 12
const val TLE1_COL_MEANMOTIONDT = 33; const val TLE1_LEN_MEANMOTIONDT = 10
const val TLE1_COL_MEANMOTIONDT2 = 44; const val TLE1_LEN_MEANMOTIONDT2 = 8
const val TLE1_COL_BSTAR = 53; const val TLE1_LEN_BSTAR = 8
const val TLE1_COL_EPHEMTYPE = 62; const val TLE1_LEN_EPHEMTYPE = 1
const val TLE1_COL_ELNUM = 64; const val TLE1_LEN_ELNUM = 4
const val TLE2_COL_SATNUM = 2; const val TLE2_LEN_SATNUM = 5
const val TLE2_COL_INCLINATION = 8; const val TLE2_LEN_INCLINATION = 8
const val TLE2_COL_RAASCENDNODE = 17; const val TLE2_LEN_RAASCENDNODE = 8
const val TLE2_COL_ECCENTRICITY = 26; const val TLE2_LEN_ECCENTRICITY = 7
const val TLE2_COL_ARGPERIGEE = 34; const val TLE2_LEN_ARGPERIGEE = 8
const val TLE2_COL_MEANANOMALY = 43; const val TLE2_LEN_MEANANOMALY = 8
const val TLE2_COL_MEANMOTION = 52; const val TLE2_LEN_MEANMOTION = 11
const val TLE2_COL_REVATEPOCH = 63; const val TLE2_LEN_REVATEPOCH = 5
}
object Static {
fun isValidLine(str: String, num: org.nstart.dep265.tletools.zeptomoby.core.TLE.Lines): Boolean {
val testStr = str.trim()
return if (num == org.nstart.dep265.tletools.zeptomoby.core.TLE.Lines.LINE_ZERO) {
testStr.length <= org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE_LEN_LINE_NAME
} else {
testStr.length == org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE_LEN_LINE_DATA &&
str[0].digitToInt() == num.ordinal &&
str[1] == ' '
}
}
}
protected object ProtectedStatic {
fun expToAtof(exp: String): String {
val colSign = 0
val lenSign = 1
val colMantissa = 1
val lenMantissa = 5
val colExponent = 6
val lenExponent = 2
val strBuilder = StringBuilder()
strBuilder.append(exp.substringByLength(colSign, lenSign))
strBuilder.append("0.")
strBuilder.append(exp.substringByLength(colMantissa, lenMantissa))
strBuilder.append("e")
strBuilder.append(exp.substringByLength(colExponent, lenExponent).trimStart())
return strBuilder.toString()
}
fun convertUnits(value: Double, fld: org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields, units: org.nstart.dep265.tletools.zeptomoby.core.TLE.Units): Double =
when (fld) {
org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_I,
org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_RAAN,
org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_ARGPER,
org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_M -> if (units == org.nstart.dep265.tletools.zeptomoby.core.TLE.Units.U_RAD) org.nstart.dep265.tletools.zeptomoby.core.Globals.deg2rad(
value
) else value
else -> value
}
fun checkSum(cs: String): Int =
cs.dropLast(1).fold(0) {
acc: Int, ch: Char ->
when (true) {
ch.isDigit() -> acc + ch.digitToInt()
(ch == '-') -> acc + 1
else -> acc
}
}
}
private object PrivateStatic {
const val strDegrees: String = " degrees"
const val strRevsPerDay: String = " revs / day"
fun getUnits(fld: org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields): String =
when (fld) {
org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_I,
org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_RAAN,
org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_ARGPER,
org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_M -> org.nstart.dep265.tletools.zeptomoby.core.TLE.PrivateStatic.strDegrees
org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_MMOTION -> org.nstart.dep265.tletools.zeptomoby.core.TLE.PrivateStatic.strRevsPerDay
else -> ""
}
}
enum class Lines {
LINE_ZERO,
LINE_ONE,
LINE_TWO
}
enum class Fields {
FLD_NORADNUM,
FLD_INTLDESC,
FLD_SET, // TLE set number
FLD_EPOCHYEAR, // Epoch: Last two digits of year
FLD_EPOCHDAY, // Epoch: Fractional Julian Day of year
FLD_ORBITNUM, // Orbit at epoch
FLD_I, // Inclination
FLD_RAAN, // R.A. ascending node
FLD_E, // Eccentricity
FLD_ARGPER, // Argument of perigee
FLD_M, // Mean anomaly
FLD_MMOTION, // Mean motion
FLD_MMOTIONDT, // First time derivative of mean motion
FLD_MMOTIONDT2,// Second time derivative of mean motion
FLD_BSTAR, // BSTAR Drag
FLD_LAST // MUST be last
}
enum class Units {
U_RAD, // radians
U_DEG, // degrees
U_NATIVE, // TLE format native units (no conversion)
U_LAST // MUST be last
}
val header: String
val first: String
val second: String
private var field = arrayOfNulls<String>(org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_LAST.ordinal)
private var mapCache = mutableMapOf<Int, Double>()
fun getField(fld: org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields, units: org.nstart.dep265.tletools.zeptomoby.core.TLE.Units = org.nstart.dep265.tletools.zeptomoby.core.TLE.Units.U_NATIVE, bStrUnits: Boolean = false): Pair<Double, String> {
assert((0 <= fld.ordinal) && (fld < org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_LAST))
assert((0 <= units.ordinal) && (units < org.nstart.dep265.tletools.zeptomoby.core.TLE.Units.U_LAST))
val strBuilder = StringBuilder(field[fld])
if (bStrUnits) strBuilder.append(org.nstart.dep265.tletools.zeptomoby.core.TLE.PrivateStatic.getUnits(fld))
strBuilder.trim()
val key = key(units, fld)
val valueInDouble: Double
if (!mapCache.contains(key)) {
val valStr = field[fld.ordinal]
if (!valStr.isParsableToNum()) valueInDouble = 0.0
else {
valueInDouble = convertUnits(valStr!!.toDouble(), fld, units)
mapCache[key] = valueInDouble
}
}
else valueInDouble = mapCache[key] ?: 0.0
return Pair(valueInDouble, strBuilder.toString())
}
constructor(h: String, f: String, s: String) {
header = h.trimEnd()
first = f
second = s
initialize()
}
constructor(tle: org.nstart.dep265.tletools.zeptomoby.core.TLE) {
header = tle.header
first = tle.first
second = tle.second
field = tle.field
mapCache = tle.mapCache
}
protected fun initialize() {
if (field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_NORADNUM]?.isNotEmpty() == true) return
assert(first.isNotEmpty())
assert(second.isNotEmpty())
field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_NORADNUM] = first.substringByLength(
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_COL_SATNUM,
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_LEN_SATNUM
)
field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_INTLDESC] = first.substringByLength(
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_COL_INTLDESC_A,
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_LEN_INTLDESC_A + org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_LEN_INTLDESC_B + org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_LEN_INTLDESC_C
)
field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_EPOCHYEAR] = first.substringByLength(
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_COL_EPOCH_A,
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_LEN_EPOCH_A
)
field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_EPOCHDAY] = first.substringByLength(
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_COL_EPOCH_B,
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_LEN_EPOCH_B
)
if (first[org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_COL_MEANMOTIONDT] == '-') field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_MMOTIONDT] = "-0"
else field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_MMOTIONDT] = "0"
field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_MMOTIONDT] += first.substringByLength(
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_COL_MEANMOTIONDT + 1,
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_LEN_MEANMOTIONDT
)
field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_MMOTIONDT2] =
org.nstart.dep265.tletools.zeptomoby.core.TLE.ProtectedStatic.expToAtof(
first.substringByLength(
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_COL_MEANMOTIONDT2,
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_LEN_MEANMOTIONDT2
)
)
field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_BSTAR] =
org.nstart.dep265.tletools.zeptomoby.core.TLE.ProtectedStatic.expToAtof(
first.substringByLength(
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_COL_BSTAR,
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_LEN_BSTAR
)
)
field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_SET] = first.substringByLength(
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_COL_ELNUM,
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE1_LEN_ELNUM
).trimStart()
field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_I] = second.substringByLength(
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE2_COL_INCLINATION,
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE2_LEN_INCLINATION
).trimStart()
field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_RAAN] = second.substringByLength(
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE2_COL_RAASCENDNODE,
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE2_LEN_RAASCENDNODE
).trimStart()
field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_E] = "0."
field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_E] += second.substringByLength(
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE2_COL_ECCENTRICITY,
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE2_LEN_ECCENTRICITY
)
field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_E] = field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_E.ordinal]!!.trimStart()
field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_ARGPER] = second.substringByLength(
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE2_COL_ARGPERIGEE,
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE2_LEN_ARGPERIGEE
).trimStart()
field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_M] = second.substringByLength(
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE2_COL_MEANANOMALY,
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE2_LEN_MEANANOMALY
).trimStart()
field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_MMOTION] = second.substringByLength(
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE2_COL_MEANMOTION,
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE2_LEN_MEANMOTION
).trimStart()
field[org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields.FLD_ORBITNUM] = second.substringByLength(
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE2_COL_REVATEPOCH,
org.nstart.dep265.tletools.zeptomoby.core.TLE.TLEPartLength.TLE2_LEN_REVATEPOCH
).trimStart()
}
private fun key(u: org.nstart.dep265.tletools.zeptomoby.core.TLE.Units, f: org.nstart.dep265.tletools.zeptomoby.core.TLE.Fields): Int = (u.ordinal * 100) + f.ordinal
}
@@ -0,0 +1,31 @@
package org.nstart.dep265.tletools.zeptomoby.core
import kotlin.math.abs
import kotlin.math.acos
import kotlin.math.sqrt
@Suppress("unused")
class Vector(
var x: Double = 0.0,
var y: Double = 0.0,
var z: Double = 0.0,
var w: Double = 0.0) {
fun sub(v: Vector) {
x -= v.x
y -= v.y
z -= v.z
w -= v.w
}
fun mul(f: Double) {
x *= f
y *= f
z *= f
w *= abs(f)
}
fun angle(v: Vector): Double = acos(dot(v) / (magnitude() * v.magnitude()))
fun magnitude(): Double = sqrt(x*x + y*y + z*z)
fun dot(v: Vector): Double = x*v.x + y*v.y + z*v.z
}
@@ -0,0 +1,78 @@
package org.nstart.dep265.tletools.zeptomoby.core.coord
import org.nstart.dep265.tletools.zeptomoby.core.Globals
import org.nstart.dep265.tletools.zeptomoby.core.Globals.acTan
import org.nstart.dep265.tletools.zeptomoby.core.Globals.fmod
import org.nstart.dep265.tletools.zeptomoby.core.Globals.rad2deg
import org.nstart.dep265.tletools.zeptomoby.core.Globals.sqr
import org.nstart.dep265.tletools.zeptomoby.core.Julian
import org.nstart.dep265.tletools.zeptomoby.core.Vector
import org.nstart.dep265.tletools.zeptomoby.core.eci.Eci
import java.lang.StringBuilder
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
open class Geo {
constructor(eci: Eci, date: Julian) {
val coords = construct(eci.position, fmod((acTan(eci.position.y, eci.position.x) - date.toGMST()), org.nstart.dep265.tletools.zeptomoby.core.Globals.twoPi))
latitudeRad = coords[0]
longitudeRad = coords[1]
altitudeKm = coords[2]
}
constructor(latRad: Double, lonRad: Double, altKm: Double) {
latitudeRad = latRad
longitudeRad = lonRad
altitudeKm = altKm
}
var altitudeKm: Double
val latitudeRad: Double
val longitudeRad: Double
val latitudeDeg: Double
get() = rad2deg(latitudeRad)
val longitudeDeg: Double
get() = rad2deg(longitudeRad)
override fun toString(): String {
val strBuilder = StringBuilder()
val isNorth = latitudeRad >= 0.0
val isEast = longitudeRad >= 0.0
strBuilder.append(String.format("%04.3f%c", latitudeDeg, if (isNorth) 'N' else 'S'))
strBuilder.append(" ")
strBuilder.append(String.format("%05.3f%c", latitudeDeg, if (isEast) 'E' else 'W'))
strBuilder.append(" ")
strBuilder.append(String.format("%.1fm", altitudeKm * 1000.0))
return strBuilder.toString()
}
protected fun construct(posEcf: Vector, theta: Double): Array<Double> {
var fTheta = fmod(theta, org.nstart.dep265.tletools.zeptomoby.core.Globals.twoPi)
if (fTheta < 0.0) fTheta += org.nstart.dep265.tletools.zeptomoby.core.Globals.twoPi
val kmSemiMaj = org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.xkmPer
val r = sqrt(sqr(posEcf.x) + sqr(posEcf.y))
val e2 = org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.f * (2.0 - org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.f)
var lat = acTan(posEcf.z, r)
val delta = 1.0e-7
var phi: Double
var c: Double
do {
phi = lat
c = 1.0 / sqrt(1.0 - e2 * sqr(sin(phi)))
lat = acTan(posEcf.z + kmSemiMaj * c * e2 * sin(phi), r)
}
while (abs(lat - phi) > delta)
return arrayOf(lat, fTheta, r / cos(lat) - kmSemiMaj * c)
}
}
@@ -0,0 +1,26 @@
package org.nstart.dep265.tletools.zeptomoby.core.coord
import org.nstart.dep265.tletools.zeptomoby.core.Julian
import org.nstart.dep265.tletools.zeptomoby.core.eci.Eci
import org.nstart.dep265.tletools.zeptomoby.core.eci.EciTime
@Suppress("unused")
class GeoTime: Geo {
constructor(geo: Geo, d: Julian):
this(geo.latitudeRad, geo.longitudeRad, geo.altitudeKm, d)
constructor(latRad: Double, lonRad: Double, altKm: Double, d: Julian): super(latRad, lonRad, altKm) {
date = d
}
constructor(eci: Eci, d: Julian): super(eci, d) {
date = d
}
constructor(eci: EciTime): super(eci, eci.date) {
date = eci.date
}
val date: Julian
}
@@ -0,0 +1,13 @@
package org.nstart.dep265.tletools.zeptomoby.core.coord
import org.nstart.dep265.tletools.zeptomoby.core.Globals.rad2deg
@Suppress("unused")
open class Topo(val azimuthRad: Double, val elevationRad: Double,
val rangeKm: Double, val rangeRatekmSec: Double) {
val azimuthDeg: Double
get() = rad2deg(azimuthRad)
val elevationDeg: Double
get() = rad2deg(elevationRad)
}
@@ -0,0 +1,17 @@
package org.nstart.dep265.tletools.zeptomoby.core.coord
import org.nstart.dep265.tletools.zeptomoby.core.Julian
@Suppress("unused")
class TopoTime: Topo {
constructor(topo: Topo, d: Julian):
this(topo.azimuthRad, topo.elevationRad, topo.rangeKm, topo.rangeRatekmSec, d)
constructor(azRad: Double, elRad: Double, range: Double ,rangeRate: Double, d: Julian)
: super(azRad, elRad, range, rangeRate) {
date = d
}
val date: Julian
}
@@ -0,0 +1,57 @@
package org.nstart.dep265.tletools.zeptomoby.core.eci
import org.nstart.dep265.tletools.zeptomoby.core.Globals
import org.nstart.dep265.tletools.zeptomoby.core.Globals.sqr
import org.nstart.dep265.tletools.zeptomoby.core.Julian
import org.nstart.dep265.tletools.zeptomoby.core.Vector
import org.nstart.dep265.tletools.zeptomoby.core.coord.Geo
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
open class Eci {
constructor(pos: Vector, vel: Vector) {
position = pos
velocity = vel
}
constructor(geo: Geo, date: Julian) {
val lat: Double = geo.latitudeRad
val lon: Double = geo.longitudeRad
val alt: Double = geo.altitudeKm
val theta: Double = date.toLMST(lon)
val c: Double = 1.0 / sqrt(1.0 + org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.f * (org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.f - 2.0) * sqr(sin(lat)))
val s: Double = sqr(1.0 - org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.f) * c
val achcp: Double = (org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.xkmPer * c + alt) * cos(lat)
val x = achcp * cos(theta) // km
val y = achcp * sin(theta) // km
val z = (org.nstart.dep265.tletools.zeptomoby.core.Globals.wgs.xkmPer * s + alt) * sin(lat) // km
position = Vector(x, y, z,
sqrt(
sqr(x) + sqr(y) + sqr(z) // range, km
)
)
val mFactor: Double = org.nstart.dep265.tletools.zeptomoby.core.Globals.twoPi * (org.nstart.dep265.tletools.zeptomoby.core.Globals.omegaE / org.nstart.dep265.tletools.zeptomoby.core.Globals.secPerDay)
val vx = -mFactor * position.y // km / sec
val vy = mFactor * position.x // km / sec
val vz = 0.0 // km / sec
velocity = Vector(vx, vy, vz,
sqrt(
sqr(vx) + sqr(vy) // range rate km/sec^2
)
)
}
val position: Vector
val velocity: Vector
fun scalePosVector(f: Double) = position.mul(f)
fun scaleVelVector(f: Double) = velocity.mul(f)
}
@@ -0,0 +1,28 @@
package org.nstart.dep265.tletools.zeptomoby.core.eci
import org.nstart.dep265.tletools.zeptomoby.core.Julian
import org.nstart.dep265.tletools.zeptomoby.core.Vector
import org.nstart.dep265.tletools.zeptomoby.core.coord.Geo
import org.nstart.dep265.tletools.zeptomoby.core.coord.GeoTime
@Suppress("unused")
class EciTime: Eci {
constructor(eci: Eci, d: Julian): super(eci.position, eci.velocity) {
date = d
}
constructor(pos: Vector, vel: Vector, d: Julian): super(pos, vel) {
date = d
}
constructor(geo: Geo, d: Julian): super(geo, d) {
date = d
}
constructor(geo: GeoTime): super(geo, geo.date) {
date = geo.date
}
val date: Julian
}
@@ -0,0 +1,5 @@
package org.nstart.dep265.tletools.zeptomoby.core.exceptions
import org.nstart.dep265.tletools.zeptomoby.core.Julian
open class DecayException(val decayTime: Julian, val satName: String, msg: String): PropagationException(msg)
@@ -0,0 +1,5 @@
package org.nstart.dep265.tletools.zeptomoby.core.exceptions
import java.lang.Exception
open class PropagationException(msg: String = ""): Exception(msg)
@@ -0,0 +1,13 @@
package org.nstart.dep265.tletools.zeptomoby.core.iau
interface IAU {
val au: Double // Astronomical unit (km)
val sr: Double // Solar radius (km) (IAU 76)
}
object IAU76: IAU {
override val au: Double = 149597870.0
override val sr: Double = 696000.0
}
@@ -0,0 +1,31 @@
package org.nstart.dep265.tletools.zeptomoby.core.wgs
interface WGS {
val xkmPer: Double // Earth equatorial radius - km
val f: Double // Earth flattening
val ge: Double // Earth gravitational constant
val j2: Double // J2 harmonic
val j3: Double // J3 harmonic
val j4: Double // J4 harmonic
}
object WGS72: WGS {
override val xkmPer: Double = 6378.135
override val f: Double = 1.0 / 298.26
override val ge: Double = 398600.8
override val j2: Double = 1.0826158e-3
override val j3: Double = -2.53881e-6
override val j4: Double = -1.65597e-6
}
object WGS84: WGS {
override val xkmPer: Double = 6378.137
override val f: Double = 1.0 / 298.257223563
override val ge: Double = 398600.4418
override val j2: Double = 1.08262998905e-3
override val j3: Double = -2.53215306e-6
override val j4: Double = -1.61098761e-6
}
@@ -0,0 +1,115 @@
package org.nstart.dep265.tletools.zeptomoby.orbit
import org.nstart.dep265.tletools.zeptomoby.core.Globals
import org.nstart.dep265.tletools.zeptomoby.core.Globals.sqr
import org.nstart.dep265.tletools.zeptomoby.core.Julian
import org.nstart.dep265.tletools.zeptomoby.core.TLE
import org.nstart.dep265.tletools.zeptomoby.core.eci.EciTime
import org.nstart.dep265.tletools.zeptomoby.orbit.norad.NoradBase
import org.nstart.dep265.tletools.zeptomoby.orbit.norad.NoradSDP4
import org.nstart.dep265.tletools.zeptomoby.orbit.norad.NoradSGP4
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sqrt
@Suppress("unused")
open class Orbit(t: TLE) {
val inclination: Double
val eccentricity: Double
val raan: Double
val argPerigee: Double
val bStar: Double
val drag: Double
val meanMotionTle: Double
val meanAnomaly: Double
val epoch: Julian
val satId: String
val satName: String
get() = tle.header
val tleFirstLine: String
get() = tle.first
val tleSecondLine: String
get() = tle.second
val semiMajor: Double
val semiMinor: Double
val meanMotion: Double
val perigee: Double
val apogee: Double
val orbitNum: Int
val period: Double
get() {
if (secPeriod < 0.0) {
secPeriod = if (meanMotion == 0.0) 0.0
else Globals.twoPi / meanMotion * 60.0
}
return secPeriod
}
val major: Double
get() = semiMajor * 2.0
val minor: Double
get() = semiMinor * 2.0
private val tle: TLE = t
private val noradModel: NoradBase
private var secPeriod: Double
init {
satId = tle.getField(TLE.Fields.FLD_NORADNUM).second
inclination = radGet(TLE.Fields.FLD_I).first
eccentricity = tle.getField(TLE.Fields.FLD_E).first
raan = radGet(TLE.Fields.FLD_RAAN).first
argPerigee = radGet(TLE.Fields.FLD_ARGPER).first
bStar = tle.getField(TLE.Fields.FLD_BSTAR).first / Globals.ae
drag = tle.getField(TLE.Fields.FLD_MMOTIONDT).first
meanMotionTle = tle.getField(TLE.Fields.FLD_MMOTION).first
meanAnomaly = radGet(TLE.Fields.FLD_M).first
var epochYear = tle.getField(TLE.Fields.FLD_EPOCHYEAR).first.toInt()
val epochDay = tle.getField(TLE.Fields.FLD_EPOCHDAY).first
orbitNum = tle.getField(TLE.Fields.FLD_ORBITNUM).first.toInt()
epochYear += if (epochYear < 57) 2000 else 1900
epoch = Julian(epochYear, epochDay)
secPeriod = -1.0
val mm = meanMotionTle
val rpmin = mm * Globals.twoPi / Globals.minPerDay
val a1 = (Globals.xke / rpmin).pow(2.0 / 3.0)
val e = eccentricity
val i = inclination
val temp = 1.5 * Globals.ck2 * (3.0 * sqr(cos(i)) - 1.0) /
(1.0 - e * e).pow(1.5)
val delta1 = temp / (a1 * a1)
val a0 = a1 *
(1.0 - delta1 *
((1.0 / 3.0) + delta1 *
(1.0 + 134.0 / 81.0 * delta1)))
val delta0 = temp / (a0 * a0)
meanMotion = rpmin / (1.0 + delta0)
semiMajor = a0 / (1.0 - delta0)
semiMinor = semiMajor * sqrt(1.0 - (e * e))
perigee = Globals.wgs.xkmPer * (semiMajor * (1.0 - e) - Globals.ae)
apogee = Globals.wgs.xkmPer * (semiMajor * (1.0 + e) - Globals.ae)
noradModel = if (Globals.twoPi / meanMotion >= 225) NoradSDP4(this)
else NoradSGP4(this)
}
fun tPlusEpoch(t: Julian) = t.spanSec(epoch)
@Deprecated("", ReplaceWith("positionEci(mpe)"))
fun position(mpe: Double) = positionEci(mpe)
fun positionEci(mpe: Double): EciTime {
val eci = noradModel.position(mpe)
val radiusAe: Double = Globals.wgs.xkmPer / Globals.ae
eci.scalePosVector(radiusAe) // km
eci.scaleVelVector(radiusAe * (Globals.minPerDay / 86400)) // km/sec
return eci
}
protected fun radGet(fld: TLE.Fields) = tle.getField(fld, TLE.Units.U_RAD)
protected fun degGet(fld: TLE.Fields) = tle.getField(fld, TLE.Units.U_DEG)
}
@@ -0,0 +1,24 @@
package org.nstart.dep265.tletools.zeptomoby.orbit
import org.nstart.dep265.tletools.zeptomoby.core.Julian
import org.nstart.dep265.tletools.zeptomoby.core.TLE
@Suppress("unused")
class Satellite(tle: TLE, n: String? = null) {
val name: String
val orbit: Orbit
init {
orbit = Orbit(tle)
name = n ?: orbit.satName
}
fun positionEci(time: Julian) = positionEci(time.spanMin(orbit.epoch))
fun positionEci(mpe: Double) = orbit.positionEci(mpe)
fun copy(src: Satellite) = Satellite(TLE(src.orbit.satName, src.orbit.tleFirstLine, src.orbit.tleSecondLine), name.ifEmpty { null })
val tleName: String
get() = orbit.satName
}
@@ -0,0 +1,210 @@
package org.nstart.dep265.tletools.zeptomoby.orbit.norad
import org.nstart.dep265.tletools.zeptomoby.core.Globals
import org.nstart.dep265.tletools.zeptomoby.core.Globals.sqr
import org.nstart.dep265.tletools.zeptomoby.core.Vector
import org.nstart.dep265.tletools.zeptomoby.core.eci.EciTime
import org.nstart.dep265.tletools.zeptomoby.core.exceptions.DecayException
import org.nstart.dep265.tletools.zeptomoby.core.exceptions.PropagationException
import org.nstart.dep265.tletools.zeptomoby.orbit.Orbit
import kotlin.math.*
abstract class NoradBase(o: Orbit) {
abstract fun position(tsince: Double): EciTime
abstract fun clone(o: Orbit): NoradBase
protected fun finalPosition(incl: Double, omega: Double, e: Double, a: Double,
xl: Double, xnode: Double, xn: Double, tsince: Double): EciTime {
if ((e * e) > 1.0) throw PropagationException("Error in satellite data")
val beta = sqrt(1.0 - e * e)
val axn = e * cos(omega)
var temp = 1.0 / (a * beta * beta)
val sinip = sin(orbit.inclination)
val cosip = cos(orbit.inclination)
val aycof = 0.25 * a3ovk2 * sinip
val xlcof = (0.125 * a3ovk2 * sinip * (3.0 + 5.0 * cosip)) /
(1.0 + cosip)
val xll = temp * xlcof * axn
val aynl = temp * aycof
val xlt = xl + xll
val ayn = e * sin(omega) + aynl
val e6a = 1.0e-6
val capu = Globals.fmod2p(xlt - xnode)
var temp2 = capu
var temp3 = 0.0
var temp4 = 0.0
var temp5 = 0.0
var temp6 = 0.0
var sinepw = 0.0
var cosepw = 0.0
var fDone = false
for (i in 1..10) {
if (fDone) break
sinepw = sin(temp2)
cosepw = cos(temp2)
temp3 = axn * sinepw
temp4 = ayn * cosepw
temp5 = axn * cosepw
temp6 = ayn * sinepw
val epw = (capu - temp4 + temp3 - temp2) / (1.0 - temp5 - temp6) + temp2
if (abs(epw - temp2) <= e6a) fDone = true
else temp2 = epw
}
val ecose = temp5 + temp6
val esine = temp3 - temp4
val elsq = axn * axn + ayn * ayn
temp = 1.0 - elsq
val pl = a * temp
val r = a * (1.0 - ecose)
var temp1 = 1.0 / r
val rdot = Globals.xke * sqrt(a) * esine * temp1
val rfdot = Globals.xke * sqrt(pl) * temp1
temp2 = a * temp1
val betal = sqrt(temp)
temp3 = 1.0 / (1.0 + betal)
val cosu = temp2 * (cosepw - axn + ayn * esine * temp3)
val sinu = temp2 * (sinepw - ayn - axn * esine * temp3)
val u = Globals.acTan(sinu, cosu)
val sin2u = 2.0 * sinu * cosu
val cos2u = 2.0 * cosu * cosu - 1.0
temp = 1.0 / pl
temp1 = Globals.ck2 * temp
temp2 = temp1 * temp
val cosip2 = cosip * cosip
val x3thm1 = 3.0 * cosip2 - 1.0
val x1mth2 = 1.0 - cosip2
val x7thm1 = 7.0 * cosip2 - 1.0
val rk = r * (1.0 - 1.5 * temp2 * betal * x3thm1) +
0.5 * temp1 * x1mth2 * cos2u
val uk = u - 0.25 * temp2 * x7thm1 * sin2u
val xnodek = xnode + 1.5 * temp2 * cosio * sin2u
val xinck = incl + 1.5 * temp2 * cosio * sinio * cos2u
val rdotk = rdot - xn * temp1 * x1mth2 * sin2u
val rfdotk = rfdot + xn * temp1 * (x1mth2 * cos2u + 1.5 * x3thm1)
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
val x = rk * ux
val y = rk * uy
val z = rk * uz
val vecPos = Vector(x, y, z)
val altKm = (vecPos.magnitude() * (Globals.wgs.xkmPer / Globals.ae))
if (altKm < Globals.wgs.xkmPer) {
val decayTime = orbit.epoch
decayTime.addMin(tsince)
throw DecayException(decayTime, "${orbit.satName}#${orbit.satId}", "altitude < xkmper")
}
val xdot = rdotk * ux + rfdotk * vx
val ydot = rdotk * uy + rfdotk * vy
val zdot = rdotk * uz + rfdotk * vz
val vecVel = Vector(xdot, ydot, zdot)
val gmt = orbit.epoch
gmt.addMin(tsince)
return EciTime(vecPos, vecVel, gmt)
}
val orbit: Orbit = o
val cosio: Double = cos(orbit.inclination); val sinio: Double = sin(orbit.inclination)
val betao2: Double; val betao: Double
var s4: Double
protected set
var qoms24: Double
protected set
val tsi: Double; val eta: Double
val eeta: Double; val coef: Double; val coef1: Double
val c1: Double; val c3: Double; val c4: Double
val a3ovk2: Double; val xmdot: Double; val omgdot: Double
val xnodot: Double; val xnodcf: Double; val t2cof: Double
init {
val theta2 = cosio * cosio
val x3thm1 = 3.0 * theta2 - 1.0
val eosq = sqr(orbit.eccentricity)
betao2 = 1.0 - eosq
betao = sqrt(betao2)
val rp = orbit.semiMajor * (1.0 - orbit.eccentricity)
val perigee = (rp - 1.0) * Globals.wgs.xkmPer
s4 = Globals.s
qoms24 = Globals.qoms2t
if (perigee < 156.0) {
s4 = perigee - 78.0
if (perigee <= 98.0) s4 = 20.0
qoms24 = ((120.0 - s4) * Globals.ae / Globals.wgs.xkmPer).pow(4)
s4 = s4 / Globals.wgs.xkmPer + Globals.ae
}
val pinvsq = 1.0 / (sqr(orbit.semiMajor) * sqr(betao2))
tsi = 1.0 / (orbit.semiMajor - s4)
eta = orbit.semiMajor * orbit.eccentricity * tsi
eeta = orbit.eccentricity * eta
val etasq = eta * eta
val psisq = abs(1.0 - etasq)
coef = qoms24 * tsi.pow(4.0)
coef1 = coef / psisq.pow(3.5)
val c2 = coef1 * orbit.meanMotion *
(orbit.semiMajor * (1.0 + 1.5 * etasq + eeta * (4.0 + etasq)) +
0.75 * Globals.ck2 * tsi / psisq * x3thm1 *
(8.0 + 3.0 * etasq * (8.0 + etasq)))
c1 = orbit.bStar * c2
a3ovk2 = -Globals.xj3 / Globals.ck2 * Globals.ae.pow(3.0)
c3 = coef * tsi * a3ovk2 * orbit.meanMotion * Globals.ae * sinio / orbit.eccentricity
val x1mth2 = 1.0 - theta2
c4 = 2.0 * orbit.meanMotion * coef1 * orbit.semiMajor * betao2 *
(eta * (2.0 + 0.5 * etasq) +
orbit.eccentricity * (0.5 + 2.0 * etasq) -
2.0 * Globals.ck2 * tsi / (orbit.semiMajor * psisq) *
(-3.0 * x3thm1 * (1.0 - 2.0 * eeta + etasq * (1.5 - 0.5 * eeta)) +
0.75 * x1mth2 *
(2.0 * etasq - eeta * (1.0 + etasq)) *
cos(2.0 * orbit.argPerigee)))
val theta4 = theta2 * theta2
val temp1 = 3.0 * Globals.ck2 * pinvsq * orbit.meanMotion
val temp2 = temp1 * Globals.ck2 * pinvsq
val temp3 = 1.25 * Globals.ck4 * pinvsq * pinvsq * orbit.meanMotion
xmdot = orbit.meanMotion + 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
xnodcf = 3.5 * betao2 * xhdot1 * c1
t2cof = 1.5 * c1
}
}
@@ -0,0 +1,616 @@
package org.nstart.dep265.tletools.zeptomoby.orbit.norad
import org.nstart.dep265.tletools.zeptomoby.core.Globals
import org.nstart.dep265.tletools.zeptomoby.core.Globals.acTan
import org.nstart.dep265.tletools.zeptomoby.core.Globals.fmod2p
import org.nstart.dep265.tletools.zeptomoby.core.Globals.sqr
import org.nstart.dep265.tletools.zeptomoby.core.eci.EciTime
import org.nstart.dep265.tletools.zeptomoby.orbit.Orbit
import kotlin.math.*
open class NoradSDP4(o: Orbit): NoradBase(o) {
companion object Constants {
const val zes: Double = 0.01675
const val zel: Double = 0.05490
const val zns: Double = 1.19459e-05
const val znl: Double = 1.5835218e-04
const val thdt: Double = 4.3752691e-03
}
protected data class DeepSecularVars(val xmdf: Double, val omgadf: Double,
val xnode: Double, val em: Double,
val xinc: Double, val xn: Double)
protected data class DeepPeriodicsVars(val e: Double, val xinc: Double,
val omgadf: Double, val xnode: Double,
val xmam: Double)
protected data class DeepDotTermsVars(val xndot: Double, val xnddt: Double, val xldot: Double)
protected var dpE3 = 0.0; protected var dpEe2 = 0.0; protected var dpSe2 = 0.0
protected var dpSe3 = 0.0; protected var dpSgh2 = 0.0; protected var dpSgh3 = 0.0
protected var dpSgh4 = 0.0; protected var dpSh2 = 0.0; protected var dpSh3 = 0.0
protected var dpSi2 = 0.0; protected var dpSi3 = 0.0; protected var dpSl2 = 0.0
protected var dpSl3 = 0.0; protected var dpSl4 = 0.0; protected var dpXgh2 = 0.0
protected var dpXgh3 = 0.0; protected var dpXgh4 = 0.0; protected var dpXh2 = 0.0
protected var dpXh3 = 0.0; protected var dpXi2 = 0.0; protected var dpXi3 = 0.0
protected var dpXl2 = 0.0; protected var dpXl3 = 0.0; protected var dpXl4 = 0.0
protected val dpZmol: Double; protected val dpZmos: Double
protected var dpAtime: Double; protected var dpD2201 = 0.0; protected var dpD2211 = 0.0
protected var dpD3210 = 0.0; protected var dpD3222 = 0.0; protected var dpD4410 = 0.0
protected var dpD4422 = 0.0; protected var dpD5220 = 0.0; protected var dpD5232 = 0.0
protected var dpD5421 = 0.0; protected var dpD5433 = 0.0; protected var dpDel1 = 0.0
protected var dpDel2 = 0.0; protected var dpDel3 = 0.0; protected var dpSse: Double = 0.0
protected var dpSsg: Double = 0.0; protected var dpSsh: Double = 0.0; protected var dpSsi: Double = 0.0
protected var dpSsl: Double = 0.0; protected val dpStep2: Double; protected val dpStepn: Double
protected val dpStepp: Double; protected val dpThgr: Double; protected val dpXfact: Double
protected var dpXlamo: Double = 0.0; protected var dpXli: Double; protected var dpXni: Double
var gpReso: Boolean
var gpSync: Boolean
init {
val sinArg = sin(orbit.argPerigee)
val cosArg = cos(orbit.argPerigee)
val eqsq = sqr(orbit.eccentricity)
val jd = orbit.epoch
dpThgr = jd.toGMST()
val eq = orbit.eccentricity
val aqnv = 1.0 / orbit.semiMajor
val xmao = orbit.meanAnomaly
val xpidot = omgdot + xnodot
val sinQ = sin(orbit.raan)
val cosQ = cos(orbit.raan)
val day = jd.fromJan0_12h_1900()
val dpiXnodce = 4.5236020 - 9.2422029e-4 * day
val dpiStem = sin(dpiXnodce)
val dpiCtem = cos(dpiXnodce)
val dpiZcosil = 0.91375164 - 0.03568096 * dpiCtem
val dpiZsinil = sqrt(1.0 - dpiZcosil * dpiZcosil)
val dpiZsinhl = 0.089683511 *dpiStem / dpiZsinil
val dpiZcoshl = sqrt(1.0 - dpiZsinhl * dpiZsinhl)
val dpiC = 4.7199672 + 0.22997150 * day
val dpiGam = 5.8351514 + 0.0019443680 * day
dpZmol = fmod2p(dpiC - dpiGam)
var dpiZx = 0.39785416 * dpiStem / dpiZsinil
val dpiZy = dpiZcoshl * dpiCtem + 0.91744867 * dpiZsinhl * dpiStem
dpiZx = acTan(dpiZx,dpiZy) + dpiGam - dpiXnodce
val dpiZcosgl = cos(dpiZx)
val dpiZsingl = sin(dpiZx)
dpZmos = fmod2p(6.2565837 + 0.017201977 * day)
val zcosis = 0.91744867
val zsinis = 0.39785416
val c1ss = 2.9864797e-06
val zsings = -0.98088458
val zcosgs = 0.1945905
var zcosg = zcosgs
var zsing = zsings
var zcosi = zcosis
var zsini = zsinis
var zcosh = cosQ
var zsinh = sinQ
var cc = c1ss
var zn = zns
var ze = zes
val xnoi = 1.0 / orbit.meanMotion
var se = 0.0; var si = 0.0; var sl = 0.0
var sgh = 0.0; var sh = 0.0
for (pass in 1..2) {
val a1 = zcosg * zcosh + zsing * zcosi * zsinh
val a3 = -zsing * zcosh + zcosg * zcosi * zsinh
val a7 = -zcosg * zsinh + zsing * zcosi * zcosh
val a8 = zsing * zsini
val a9 = zsing * zsinh + zcosg * zcosi * zcosh
val a10 = zcosg * zsini
val a2 = cosio * a7 + sinio * a8
val a4 = cosio * a9 + sinio * a10
val a5 = -sinio * a7 + cosio * a8
val a6 = -sinio * a9 + cosio * a10
val x1 = a1 * cosArg + a2 * sinArg
val x2 = a3 * cosArg + a4 * sinArg
val x3 = -a1 * sinArg + a2 * cosArg
val x4 = -a3 * sinArg + a4 * cosArg
val x5 = a5 * sinArg
val x6 = a6 * sinArg
val x7 = a5 * cosArg
val x8 = a6 * cosArg
val z31 = 12.0 * x1 * x1 - 3.0 * x3 * x3
val z32 = 24.0 * x1 * x2 - 6.0 * x3 * x4
val z33 = 12.0 * x2 * x2 - 3.0 * x4 * x4
var z1 = 3.0 * (a1 * a1 + a2 * a2) + z31 * eqsq
var z2 = 6.0 * (a1 * a3 + a2 * a4) + z32 * eqsq
var z3 = 3.0 * (a3 * a3 + a4 * a4) + z33 * eqsq
val z11 = -6.0 * a1 * a5 + eqsq*(-24.0 * x1 * x7 - 6.0 * x3 * x5)
val z12 = -6.0 * (a1 * a6 + a3 * a5) +
eqsq * (-24.0 * (x2 * x7 + x1 * x8) - 6.0 * (x3 * x6 + x4 * x5))
val z13 = -6.0 * a3 * a6 + eqsq * (-24.0 * x2 * x8 - 6.0 * x4 * x6)
val z21 = 6.0 * a2 * a5 + eqsq * (24.0 * x1 * x5 - 6.0 * x3 * x7)
val z22 = 6.0*(a4 * a5 + a2 * a6) +
eqsq * (24.0 * (x2 * x5 + x1 * x6) - 6.0 * (x4 * x7 + x3 * x8))
val z23 = 6.0 * a4 * a6 + eqsq*(24.0 * x2 * x6 - 6.0 * x4 * x8)
z1 += z1 + betao2 * z31
z2 += z2 + betao2 * z32
z3 += z3 + betao2 * z33
val s3 = cc * xnoi
val s2 = -0.5 * s3 / betao
val s4 = s3 * betao
val s1 = -15.0 * eq * s4
val s5 = x1 * x3 + x2 * x4
val s6 = x2 * x3 + x1 * x4
val s7 = x2 * x4 - x1 * x3
se = s1 * zn * s5
si = s2 * zn * (z11 + z13)
sl = -zn * s3 * (z1 + z3 - 14.0 - 6.0 * eqsq)
sgh = s4 * zn * (z31 + z33 - 6.0)
sh = -zn * s2 * (z21 + z23)
if (orbit.inclination < 5.2359877e-2) sh = 0.0
dpEe2 = 2.0 * s1 * s6
dpE3 = 2.0 * s1 * s7
dpXi2 = 2.0 * s2 * z12
dpXi3 = 2.0 * s2 * (z13 - z11)
dpXl2 = -2.0 * s3 * z2
dpXl3 = -2.0 * s3 * (z3 - z1)
dpXl4 = -2.0 * s3 * (-21.0 - 9.0 * eqsq) * ze
dpXgh2 = 2.0 * s4 * z32
dpXgh3 = 2.0 * s4 * (z33 - z31)
dpXgh4 = -18.0 * s4 * ze
dpXh2 = -2.0 * s2 * z22
dpXh3 = -2.0 * s2 * (z23 - z21)
if (pass == 1) {
dpSse = se
dpSsi = si
dpSsl = sl
dpSsh = sh / sinio
dpSsg = sgh - cosio * dpSsh
dpSe2 = dpEe2
dpSi2 = dpXi2
dpSl2 = dpXl2
dpSgh2 = dpXgh2
dpSh2 = dpXh2
dpSe3 = dpE3
dpSi3 = dpXi3
dpSl3 = dpXl3
dpSgh3 = dpXgh3
dpSh3 = dpXh3
dpSl4 = dpXl4
dpSgh4 = dpXgh4
zcosg = dpiZcosgl
zsing = dpiZsingl
zcosi = dpiZcosil
zsini = dpiZsinil
zcosh = dpiZcoshl * cosQ + dpiZsinhl * sinQ
zsinh = sinQ * dpiZcoshl - cosQ * dpiZsinhl
zn = znl
val c1l = 4.7968065e-07
cc = c1l
ze = zel
}
}
dpSse += se
dpSsi += si
dpSsl += sl
dpSsg = dpSsg + sgh - cosio / sinio * sh
dpSsh += sh / sinio
gpReso = false
gpSync = false
val g310: Double
val f220: Double
var bfact = 0.0
if ((orbit.meanMotion > 0.0034906585) &&
(orbit.meanMotion < 0.0052359877)) {
gpReso = true
gpSync = true
val g200 = 1.0 + eqsq * (-2.5 + 0.8125 * eqsq)
g310 = 1.0 + 2.0 * eqsq
val g300 = 1.0 + eqsq * (-6.0 + 6.60937 * eqsq)
f220 = 0.75 * (1.0 + cosio) * (1.0 + cosio)
val f311 = 0.9375 * sinio * sinio * (1.0 + 3 * cosio) - 0.75 * (1.0 + cosio)
var f330 = 1.0 + cosio
val q22 = 1.7891679e-06
val q31 = 2.1460748e-06
val q33 = 2.2123015e-07
f330 *= 1.875 * f330 * f330
dpDel1 = 3.0 * orbit.meanMotion * orbit.meanMotion * aqnv * aqnv
dpDel2 = 2.0 * dpDel1 * f220 * g200 * q22
dpDel3 = 3.0 * dpDel1 * f330 * g300 * q33 * aqnv
dpDel1 *= f311 * g310 * q31 * aqnv
dpXlamo = xmao + orbit.raan + orbit.argPerigee - dpThgr
bfact = xmdot + xpidot - thdt
bfact += dpSsl + dpSsg + dpSsh
}
else if ((orbit.meanMotion >= 8.26e-03) &&
(orbit.meanMotion <= 9.24e-03) &&
(eq >= 0.5)) {
gpReso = true
val eoc = eq * eqsq
val g201 = -0.306 - (eq - 0.64) * 0.440
val g211: Double; val g322: Double
val g410: Double; val g422: Double
val g520: Double
if (eq <= 0.65) {
g211 = 3.616 - 13.247 * eq + 16.290 * eqsq
g310 = -19.302 + 117.390 * eq - 228.419 * eqsq + 156.591 * eoc
g322 = -18.9068 + 109.7927 * eq - 214.6334 * eqsq + 146.5816 * eoc
g410 = -41.122 + 242.694 * eq - 471.094 * eqsq + 313.953 * eoc
g422 = -146.407 + 841.880 * eq - 1629.014 * eqsq + 1083.435 * eoc
g520 = -532.114 + 3017.977 * eq - 5740.0 * eqsq + 3708.276 * eoc
}
else {
g211 = -72.099 + 331.819 * eq - 508.738 * eqsq + 266.724 * eoc
g310 = -346.844 + 1582.851 * eq - 2415.925 * eqsq + 1246.113 * eoc
g322 = -342.585 + 1554.908 * eq - 2366.899 * eqsq + 1215.972 * eoc
g410 = -1052.797 + 4758.686 * eq - 7193.992 * eqsq + 3651.957 * eoc
g422 = -3581.69 + 16178.11 * eq - 24462.77 * eqsq + 12422.52 * eoc
g520 = if (eq <= 0.715) 1464.74 - 4664.75 * eq + 3763.64 * eqsq
else -5149.66 + 29936.92 * eq - 54087.36 * eqsq + 31324.56 * eoc
}
val g533: Double
val g521: Double
val g532: Double
if (eq < 0.7) {
g533 = -919.2277 + 4988.61 * eq - 9064.77 * eqsq + 5542.21 * eoc
g521 = -822.71072 + 4568.6173 * eq - 8491.4146 * eqsq + 5337.524 * eoc
g532 = -853.666 + 4690.25 * eq - 8624.77 * eqsq + 5341.4 * eoc
}
else {
g533 = -37995.78 + 161616.52 * eq - 229838.2 * eqsq + 109377.94 * eoc
g521 = -51752.104 + 218913.95 * eq - 309468.16 * eqsq + 146349.42 * eoc
g532 = -40023.88 + 170470.89 * eq - 242699.48 * eqsq + 115605.82 * eoc
}
val sini2 = sqr(sinio)
val theta2 = sqr(cosio)
f220 = 0.75 * (1.0 + 2.0 * cosio + theta2)
val root22 = 1.7891679e-06
val root32 = 3.7393792e-07
val root44 = 7.3636953e-09
val root52 = 1.1428639e-07
val root54 = 2.1765803e-09
val f221 = 1.5 * sini2
val f321 = 1.875 * sinio * (1.0 - 2.0 * cosio - 3.0 * theta2)
val f322 = -1.875 * sinio * (1.0 + 2.0 * cosio - 3.0 * theta2)
val f441 = 35.0 * sini2 * f220
val f442 = 39.3750 * sini2 * sini2
val f522 = 9.84375 * sinio * (sini2 * (1.0 - 2.0 * cosio - 5.0 * theta2) +
0.33333333*(-2.0 + 4.0 * cosio + 6.0 * theta2))
val f523 = sinio * (4.92187512 * sini2 * (-2.0 - 4.0 * cosio + 10.0 * theta2) +
6.56250012 * (1.0 + 2.0 * cosio - 3.0 * theta2))
val f542 = 29.53125 * sinio * ( 2.0 - 8.0 * cosio + theta2 * (-12.0 + 8.0 * cosio + 10.0 * theta2))
val f543 = 29.53125 * sinio * (-2.0 - 8.0 * cosio + theta2 * ( 12.0 + 8.0 * cosio - 10.0 * theta2))
val xno2 = orbit.meanMotion * orbit.meanMotion
val ainv2 = aqnv * aqnv
var temp1 = 3.0 * xno2 * ainv2
var temp = temp1 * root22
dpD2201 = temp * f220 * g201
dpD2211 = temp * f221 * g211
temp1 *= aqnv
temp = temp1 * root32
dpD3210 = temp * f321 * g310
dpD3222 = temp * f322 * g322
temp1 *= aqnv
temp = 2.0 * temp1 * root44
dpD4410 = temp * f441 * g410
dpD4422 = temp * f442 * g422
temp1 *= aqnv
temp = temp1 * root52
dpD5220 = temp * f522 * g520
dpD5232 = temp * f523 * g532
temp = 2.0 * temp1 * root54
dpD5421 = temp * f542 * g521
dpD5433 = temp * f543 * g533
dpXlamo = xmao + orbit.raan + orbit.raan - dpThgr - dpThgr
bfact = xmdot + xnodot + xnodot - thdt - thdt
bfact += dpSsl + dpSsh + dpSsh
}
if (gpReso || gpSync) {
dpXfact = bfact - orbit.meanMotion
dpXli = dpXlamo
dpXni = orbit.meanMotion
dpAtime = 0.0
dpStepp = 720.0
dpStepn = -720.0
dpStep2 = 259200.0
}
else {
dpXfact = 0.0
dpXli = 0.0
dpXni = 0.0
dpAtime = 0.0
dpStepp = 0.0
dpStepn = 0.0
dpStep2 = 0.0
}
}
protected fun deepSecular(xmdf: Double, omgadf: Double, xnode: Double,
xn: Double, tsince: Double): DeepSecularVars {
var xmdfLocal = xmdf + dpSsl * tsince
var omgadfLocal = omgadf + dpSsg * tsince
var xnodeLocal = xnode + dpSsh * tsince
val emmLocal = orbit.eccentricity + dpSse * tsince
var xinccLocal = orbit.inclination + dpSsi * tsince
if (xinccLocal < 0.0) {
xinccLocal *= -1
xnodeLocal += Globals.pi
omgadfLocal -= Globals.pi
}
if (!gpReso) return DeepSecularVars(xmdfLocal, omgadfLocal, xnodeLocal,
emmLocal, xinccLocal, xn)
val xnddt: Double
val xndot: Double
val xldot: Double
var delt = 0.0
var fDone = false
while (!fDone) {
if ((dpAtime == 0.0) ||
((tsince >= 0.0) && (dpAtime < 0.0)) ||
((tsince < 0.0) && (dpAtime >= 0.0))) {
delt = if (tsince < 0) dpStepn else dpStepp
dpAtime = 0.0
dpXni = orbit.meanMotion
dpXli = dpXlamo
fDone = true
}
else {
if (abs(tsince) < abs(dpAtime)) {
delt = if (tsince >= 0.0) dpStepn else dpStepp
deepCalcIntegrator(delt)
}
else {
delt = if (tsince > 0.0) dpStepp else dpStepn
fDone = true
}
}
}
while (abs(tsince - dpAtime) >= dpStepp) deepCalcIntegrator(delt)
val ft = tsince - dpAtime
val vars = deepCalcDotTerms()
xndot = vars.xndot
xnddt = vars.xnddt
xldot = vars.xldot
val xnnLocal = dpXni + xndot * ft + xnddt * ft * ft * 0.5
val xl = dpXli + xldot * ft + xndot * ft * ft * 0.5
val temp = -xnodeLocal + dpThgr + tsince * thdt
xmdfLocal = xl - omgadfLocal + temp
if (!gpSync) xmdfLocal = xl + temp + temp
return DeepSecularVars(xmdfLocal, omgadfLocal, xnodeLocal, emmLocal, xinccLocal, xnnLocal)
}
protected fun deepCalcDotTerms(): DeepDotTermsVars {
val fasx2 = 0.13130908
val fasx4 = 2.8843198
val fasx6 = 0.37448087
val xndotLocal: Double
var xnddtLocal: Double
if (gpSync) {
xndotLocal = dpDel1 * sin(dpXli - fasx2) +
dpDel2 * sin(2.0 * (dpXli - fasx4)) +
dpDel3 * sin(3.0 * (dpXli - fasx6))
xnddtLocal = dpDel1 * cos(dpXli - fasx2) +
2.0 * dpDel2 * cos(2.0 * (dpXli - fasx4)) +
3.0 * dpDel3 * cos(3.0 * (dpXli - fasx6))
}
else {
val g22 = 5.7686396
val g32 = 0.95240898
val g44 = 1.8014998
val g52 = 1.0508330
val g54 = 4.4108898
val xomi = orbit.argPerigee + omgdot * dpAtime
val x2omi = xomi + xomi
val x2li = dpXli + dpXli
xndotLocal = dpD2201 * sin(x2omi + dpXli - g22) +
dpD2211 * sin(dpXli - g22) +
dpD3210 * sin( xomi + dpXli - g32) +
dpD3222 * sin(-xomi + dpXli - g32) +
dpD4410 * sin(x2omi + x2li - g44) +
dpD4422 * sin(x2li - g44) +
dpD5220 * sin( xomi + dpXli - g52) +
dpD5232 * sin(-xomi + dpXli - g52) +
dpD5421 * sin( xomi + x2li - g54) +
dpD5433 * sin(-xomi + x2li - g54)
xnddtLocal = dpD2201 * cos(x2omi + dpXli - g22) +
dpD2211 * cos(dpXli - g22) +
dpD3210 * cos( xomi + dpXli - g32) +
dpD3222 * cos(-xomi + dpXli - g32) +
dpD5220 * cos( xomi + dpXli - g52) +
dpD5232 * cos(-xomi + dpXli - g52) +
2.0 * (dpD4410 * cos(x2omi + x2li - g44) +
dpD4422 * cos(x2li - g44) +
dpD5421 * cos( xomi + x2li - g54) +
dpD5433 * cos(-xomi + x2li - g54))
}
val xldotLocal = dpXni + dpXfact
xnddtLocal *= xldotLocal
return DeepDotTermsVars(xndotLocal, xnddtLocal, xldotLocal)
}
protected fun deepCalcIntegrator(delt: Double): DeepDotTermsVars {
val vars = deepCalcDotTerms()
dpXli += vars.xldot * delt + vars.xndot * dpStep2
dpXni += vars.xndot * delt + vars.xnddt * dpStep2
dpAtime += delt
return vars
}
protected fun deepPeriodics(e: Double, xinc: Double,
omgadf: Double, xnode: Double,
xmam: Double, tsince: Double): DeepPeriodicsVars{
val sinis = sin(xinc)
val cosis = cos(xinc)
val sghs: Double
val shs: Double
val sh1: Double
val pe: Double
val pinc: Double
val pl: Double
val sghl: Double
var zm = dpZmos + zns * tsince
var zf = zm + 2.0 * zes * sin(zm)
var sinzf = sin(zf)
var f2 = 0.5 * sinzf * sinzf - 0.25
var f3 = -0.5 * sinzf * cos(zf)
val ses = dpSe2 * f2 + dpSe3 * f3
val sis = dpSi2 * f2 + dpSi3 * f3
val sls = dpSl2 * f2 + dpSl3 * f3 + dpSl4 * sinzf
sghs = dpSgh2 * f2 + dpSgh3 * f3 + dpSgh4 * sinzf
shs = dpSh2 * f2 + dpSh3 * f3
zm = dpZmol + znl * tsince
zf = zm + 2.0 * zel * sin(zm)
sinzf = sin(zf)
f2 = 0.5 * sinzf * sinzf - 0.25
f3 = -0.5 * sinzf * cos(zf)
val sel = dpEe2 * f2 + dpE3 * f3
val sil = dpXi2 * f2 + dpXi3 * f3
val sll = dpXl2 * f2 + dpXl3 * f3 + dpXl4 * sinzf
sghl = dpXgh2 * f2 + dpXgh3 * f3 + dpXgh4 * sinzf
sh1 = dpXh2 * f2 + dpXh3 * f3
pe = ses + sel
pinc = sis + sil
pl = sls + sll
var pgh = sghs + sghl
var ph = shs + sh1
val xinccLocal = xinc + pinc
val eLocal = e + pe
val omgadfLocal: Double
val xnodeLocal: Double
val xmamLocal: Double
if (orbit.inclination >= 0.2) {
ph /= sinio
pgh -= cosio * ph
omgadfLocal = omgadf + pgh
xnodeLocal = xnode + ph
xmamLocal = xmam + pl
}
else {
val sinok = sin(xnode)
val cosok = cos(xnode)
var alfdp = sinis * sinok
var betdp = sinis * cosok
val dalf = ph * cosok + pinc * cosis * sinok
val dbet = -ph * sinok + pinc * cosis * cosok
alfdp += dalf
betdp += dbet
var xls = xmam + omgadf + cosis * xnode
val dls = pl + pgh - pinc * xnode * sinis
xls += dls
xnodeLocal = acTan(alfdp, betdp)
xmamLocal = xmam + pl
omgadfLocal = xls - xmamLocal - cos(xinccLocal) * xnodeLocal
}
return DeepPeriodicsVars(eLocal, xinccLocal, omgadfLocal, xnodeLocal, xmamLocal)
}
override fun position(tsince: Double): EciTime {
var xmdf = orbit.meanAnomaly + xmdot * tsince
var omgadf = orbit.argPerigee + omgdot * tsince
val xnoddf = orbit.raan + xnodot * tsince
val tsq = tsince * tsince
var xnode = xnoddf + xnodcf * tsq
val tempa = 1.0 - c1 * tsince
val tempe = orbit.bStar * c4 * tsince
val templ = t2cof * tsq
var xn = orbit.meanMotion
val em: Double
var xinc: Double
val vars1 = deepSecular(xmdf, omgadf, xnode, xn, tsince)
xmdf = vars1.xmdf
omgadf = vars1.omgadf
xnode = vars1.xnode
xn = vars1.xn
em = vars1.em
xinc = vars1.xinc
val a = (Globals.xke / xn).pow(2.0 / 3.0) * sqr(tempa)
var e = em - tempe
var xmam = xmdf + orbit.meanMotion * templ
val vars2 = deepPeriodics(e, xinc, omgadf, xnode, xmam, tsince)
e = vars2.e
xinc = vars2.xinc
omgadf = vars2.omgadf
xnode = vars2.xnode
xmam = vars2.xmam
val xl = xmam + omgadf + xnode
xn = Globals.xke / a.pow(1.5)
return finalPosition(xinc, omgadf, e, a, xl, xnode, xn, tsince)
}
override fun clone(o: Orbit): NoradBase = NoradSDP4(o)
}
@@ -0,0 +1,89 @@
package org.nstart.dep265.tletools.zeptomoby.orbit.norad
import org.nstart.dep265.tletools.zeptomoby.core.Globals
import org.nstart.dep265.tletools.zeptomoby.core.Globals.sqr
import org.nstart.dep265.tletools.zeptomoby.core.eci.EciTime
import org.nstart.dep265.tletools.zeptomoby.orbit.Orbit
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin
open class NoradSGP4(o: Orbit): NoradBase(o) {
protected val c5: Double
protected val omgcof: Double
protected val xmcof: Double
protected val delmo: Double
protected val sinmo: Double
init {
val etasq: Double = eta * eta
c5 = 2.0 * coef1 * orbit.semiMajor * betao2 * (1.0 + 2.75 * (etasq + eeta) + eeta * etasq)
omgcof = orbit.bStar * c3 * cos(orbit.argPerigee)
xmcof = -(2.0 / 3.0) * coef * orbit.bStar * Globals.ae / eeta
delmo = (1.0 + eta * cos(orbit.meanAnomaly)).pow(3.0)
sinmo = sin(orbit.meanAnomaly)
}
override fun position(tsince: Double): EciTime {
var isImp = false
if ((orbit.semiMajor * (1.0 - orbit.eccentricity) / Globals.ae) <
(220.0 / Globals.wgs.xkmPer + Globals.ae)) isImp = true
var d2 = 0.0
var d3 = 0.0
var d4 = 0.0
var t3cof = 0.0
var t4cof = 0.0
var t5cof = 0.0
if (!isImp) {
val c1sq: Double = c1 * c1
d2 = 4.0 * orbit.semiMajor * tsi * c1sq
val temp: Double = d2 * tsi * c1 / 3.0
d3 = (17.0 * orbit.semiMajor + s4) * temp
d4 = 0.5 * temp * orbit.semiMajor * tsi *
(221.0 * orbit.semiMajor + 31.0 * s4) * c1
t3cof = d2 + 2.0 * c1sq
t4cof = 0.25 * (3.0 * d3 + c1 * (12.0 * d2 + 10.0 * c1sq))
t5cof = 0.2 * (3.0 * d4 + 12.0 * c1 * d3 + 6.0 *
d2 * d2 + 15.0 * c1sq * (2.0 * d2 + c1sq))
}
val xmdf: Double = orbit.meanAnomaly + xmdot * tsince
val omgadf: Double = orbit.argPerigee + omgdot * tsince
val xnoddf: Double = orbit.raan + xnodot * tsince
var omega = omgadf
var xmp = xmdf
val tsq = tsince * tsince
val xnode: Double = xnoddf + xnodcf * tsq
var tempa: Double = 1.0 - c1 * tsince
var tempe: Double = orbit.bStar * c4 * tsince
var templ: Double = t2cof * tsq
if (!isImp) {
val delomg: Double = omgcof * tsince
val delm: Double = xmcof * ((1.0 + eta * cos(xmdf)).pow(3.0) - delmo)
val temp = delomg + delm
xmp = xmdf + temp
omega = omgadf - temp
val tcube = tsq * tsince
val tfour = tsince * tcube
tempa = tempa - d2 * tsq - d3 * tcube - d4 * tfour
tempe += orbit.bStar * c5 * (sin(xmp) - sinmo)
templ += t3cof * tcube + tfour * (t4cof + tsince * t5cof)
}
val a: Double = orbit.semiMajor * sqr(tempa)
val e: Double = orbit.eccentricity - tempe
val xl: Double = xmp + omega + xnode + orbit.meanMotion * templ
val xn: Double = Globals.xke / a.pow(1.5)
return finalPosition(orbit.inclination, omgadf, e, a, xl, xnode, xn, tsince)
}
override fun clone(o: Orbit): NoradBase = NoradSGP4(o)
}
@@ -0,0 +1,44 @@
package ballistics.types
/**
* Код завершения расчета
*/
enum class BallisticsError(val value: Int) {
OK(0),
UNKNOWN_ERR(1),
EMPTY_NU(3),
H_MINIMUM_ERROR(4),
EMPTY_ORBITAL_POINTS(5),
EMPTY_FLIGHTLINE_POINTS(6),
ORB_POINTS_INTERVAL_ERROR(7),
STEPPER_ERROR(8),
EMPTY_PPI_LIST(9),
;
companion object {
val map = BallisticsError.values().associate { it.value to it }
fun fromInt(
value: Int,
defValue: BallisticsError = BallisticsError.OK,
): BallisticsError {
return map[value] ?: defValue
}
}
}
fun printError(err: BallisticsError): String {
return when (err) {
BallisticsError.OK -> "Успешно"
BallisticsError.UNKNOWN_ERR -> "нераспознанная ошибка"
BallisticsError.H_MINIMUM_ERROR -> "высота достигла минимального значения"
BallisticsError.EMPTY_NU -> "пустой массив начальных условий"
BallisticsError.EMPTY_ORBITAL_POINTS -> "не рассчитаны точки орбиты"
BallisticsError.ORB_POINTS_INTERVAL_ERROR -> "интервал расчета не пренадлежит интервалу расчета точек орбиты"
BallisticsError.EMPTY_PPI_LIST -> "пустой массив ППИ"
BallisticsError.STEPPER_ERROR -> "ошибка при выходе на заданное время"
else -> "неизвестный код ошибки"
}
}
@@ -0,0 +1,341 @@
package ballistics.types
import ballistics.utils.math.Vector3D
import java.time.LocalDateTime
import kotlin.math.PI
/**
* Тип метода интегрирования
*/
enum class IntegrationType {
RUNG4,
ADAMS7,
}
/**
* Тип модели движения
*/
enum class ModDVType(val value: Int) {
FOTO(1),
KONDOR(6),
METEORM1(14),
METEORM2(15),
BARS(16),
KONDOR_PROGNOZ(19),
;
companion object {
val map = values().associate { it.value to it }
fun fromInt(
value: Int,
defValue: ModDVType = FOTO,
): ModDVType {
return map[value] ?: defValue
}
}
}
/**
* Тип модели Земли
*/
enum class EarthType {
PZ90d0,
PZ90d02,
}
enum class AstroType {
ATJ2000,
}
/**
* Тип базовой СК
*/
enum class WorkCSType {
/**
* орбитальная
*/
WCSOrbit,
/**
* путевая / трассовая
*/
WCSPath,
}
/**
* Тип учета угла тангажа в расчете матрицы планирования
*/
enum class TangageType {
/**
* Конструктивный
*/
TTConstructive,
/**
* Упреждающий
*/
TTProactive,
}
/**
* Параметры точки орбиты
*/
data class OrbitalPoint(val t: Double, var vit: Int, val r: Vector3D, val v: Vector3D) {
constructor() : this(0.0, 0, Vector3D(), Vector3D())
}
/**
* Параметры витка (ВУЗ)
*/
data class RevolutionParameter(var vuz: OrbitalPoint, var lVuz: Double, var hVuz: Double) {
constructor() : this(OrbitalPoint(), 0.0, 0.0)
}
/**
* Начальные условия движения
*/
class InitialConditions(var point: OrbitalPoint, var sBall: Double, var f81: Double) {
constructor() : this(OrbitalPoint(), 0.0, 100.0)
}
class TLE(val tle1: String, val tle2: String) {
constructor() : this("", "")
}
/**
* затенение по азимуму
* азимут в диапазоне 0..2*PI
* при переходе через 0 долготы нужно заводить 2 записи! до 0 и после 0!
*/
data class ShadowAU(
val azimuthStart: Double,
val azimuthEnd: Double,
val elevation: Double,
)
/**
* Параметры ППИ
*/
data class PPI(
val ppiNum: Int = 0,
val auNum: Int = 0,
val lat: Double = 0.0,
val long: Double = 0.0,
val height: Double = 0.0,
val elevMin: Double = 0.0,
val elevMax: Double = 0.0,
var shadowMin: List<ShadowAU>? = null,
var shadowMax: List<ShadowAU>? = null,
) {
constructor(
ppiNum: Int,
auNum: Int,
lat: Double,
long: Double,
height: Double,
elevMin: Double,
elevMax: Double,
) : this (ppiNum, auNum, lat, long, height, elevMin, elevMax, null, null)
}
/**
* Параметры ППИ(расширенные)
*/
internal class PPIExtParams() {
var nip = 0
var au = 0
var bn = 0.0
var xn = 0.0
var yn = 0.0
var zn = 0.0
var sl = 0.0
var cl = 0.0
var sb = 0.0
var cb = 0.0
}
/**
* Параметры ППИ(расширенные)
*/
internal class PPIExt() {
var params = PPIExtParams()
var elevMin = 0.0
var elevMax = 0.0
var zoneIn = VisibilityParametersZRV()
var zoneMax = VisibilityParametersZRV()
var zoneOut = VisibilityParametersZRV()
var vit = 0
var kaY = 0.0
var t1 = 0.0
var gam1 = 0.0
var a1 = 0.0
var vy1 = 0.0
var t2 = 0.0
var gam2 = 0.0
var a2 = 0.0
var vy2 = 0.0
var shadowMin: Iterable<ShadowAU>? = null
var shadowMax: Iterable<ShadowAU>? = null
fun isVisible(
azimuth: Double,
elevation: Double,
): Boolean {
if (elevation > elevMax) {
return false
}
if (shadowMin != null) {
val a = if (azimuth >= 0) azimuth else azimuth + 2 * PI
val shad = shadowMin!!.find { it.azimuthStart <= a && a <= it.azimuthEnd }
if (shad != null && shad.elevation > elevation) {
return false
}
}
if (shadowMax != null) {
val a = if (azimuth >= 0) azimuth else azimuth + 2 * PI
val shad = shadowMax!!.find { it.azimuthStart <= a && a <= it.azimuthEnd }
if (shad != null && shad.elevation < elevation) {
return false
}
}
return true
}
}
/**
* Параметры видимости КА-ППИ
*/
data class VisibilityParametersZRV(var t: Double, var range: Double, var azimuth: Double, var elevation: Double) {
constructor() : this(0.0, 0.0, 0.0, 0.0)
}
/**
* Параметры зоны радиовидимости КА-ППИ
*/
data class ZRV(
val ppi: Int,
val au: Int,
val vit: Int,
val zoneIn: VisibilityParametersZRV,
val zoneMax: VisibilityParametersZRV,
val zoneOut: VisibilityParametersZRV,
)
class Orientation(
var tang: Double,
var kren: Double,
var risk: Double,
)
/**
* Параметры точки на Земной поверхности
*/
class BLHPoint(
var lat: Double,
var long: Double,
var h: Double,
)
/**
* Параметры точки на границе полосы обзора / трассе полета
*/
class THBLPoint(val lat: Double, val long: Double, val range: Double, val sunAngle: Double)
/**
* Параметры трассы полета и полосы обзора
*/
class FlightLine(
val t: Double,
val vit: Int,
val pv: Int,
val rightOuterSwath: THBLPoint,
val rightInternalSwath: THBLPoint,
val flightLine: THBLPoint,
val leftInternalSwath: THBLPoint,
val leftOuterSwath: THBLPoint,
)
class FleghtLineSector(
val tStart: Double,
val tStop: Double,
)
/**
* Объект оперативного каталога
*/
class OPKatObj(
val objON: Int,
val objN: Int,
val objUUID : String,
val pointNumb: Int,
val lat: Double,
val long: Double,
val height: Double,
val rollMin: Double,
val rollMax: Double,
val sunAngleMin: Double,
)
/**
* Параметры видимости точки наблюдения
*/
class PointViewParams(
val objON: Int,
val objN: Int,
val objUUID : String,
val pointNumb: Int,
val vit: Int,
val traverz: Double,
val latTraverz: Double,
val longTraverz: Double,
val orientation: Orientation,
val range: Double,
val sunAngle: Double,
val sightAngle: Double,
val pv : Int,
)
data class KeplerParams(
var dmv: Double = 0.0,
var ael: Double = 0.0,
var e: Double = 0.0,
var nakl: Double = 0.0,
var omegab: Double = 0.0,
var omegam: Double = 0.0,
var u: Double = 0.0,
var v: Double = 0.0,
var o: Double = 0.0,
var eA: Double = 0.0,
var tau: Double = 0.0,
var t: Double = 0.0,
var rP: Double = 0.0,
var rA: Double = 0.0,
)
class TLEParams(
var satName : String = "",
var noradId : Long = 0,
var revolution : Long = 0,
var time : LocalDateTime = LocalDateTime.now(),
var inclination : Double = 0.0,
var perigee : Double = 0.0,
var apogee: Double = 0.0,
var argPerigee: Double = 0.0,
var eccentricity: Double = 0.0,
var major: Double = 0.0,
var minor: Double = 0.0,
var meanAnomaly: Double = 0.0,
var period: Double = 0.0,
var semiMajor: Double = 0.0,
var semiMinor: Double = 0.0,
var ascendingNode: Double = 0.0,
var meanMotion: Double = 0.0,
var meanMotionTLE: Double = 0.0
)
@@ -0,0 +1,8 @@
package ballistics.utils
import java.time.LocalDateTime
import java.time.ZoneOffset
fun toDateTime(t: Double) = LocalDateTime.ofEpochSecond(t.toLong(), (t % 1 * 1e9).toInt(), ZoneOffset.UTC)
fun fromDateTime(t: LocalDateTime) = t.toEpochSecond(ZoneOffset.UTC) + t.nano / 1e9
@@ -0,0 +1,406 @@
package ballistics.utils.astro
import ballistics.types.EarthType
import ballistics.types.OrbitalPoint
import ballistics.utils.earth.getEarth
import ballistics.utils.math.Matrix3D
import ballistics.utils.math.Vector3D
import java.time.LocalDateTime
import java.time.ZoneOffset
import kotlin.math.PI
import kotlin.math.atan
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
import kotlin.math.truncate
class AstronomerJ2000(var earthType: EarthType) {
var ase = 149600000.0
var earth = getEarth(earthType)
var baseDate =
LocalDateTime.of(2000, 1, 1, 12, 0, 0, 0).toEpochSecond(ZoneOffset.UTC)
.toDouble() / 86400
fun si2000(tDtTm: Double): Double {
var d = tDtTm / 86400.0
var dat = truncate(d)
var tt = d - dat
if (tt < 0.125) {
dat--
}
var t = (dat - baseDate) / (36525.0)
var t2 = t * t
var t3 = t2 * t
var lt =
2.355555743494e0 + 8328.691425719e0 * t +
1.545547e-4 * t2 + 2.50e-7 * t3
var llt =
6.240060126913e0 + 628.301955171e0 * t - 2.681989e-6 * t2 -
6.593e-10 * t3
var ft =
1.6279050815375e0 + 8433.466156916e0 * t - 6.181956e-5 * t2 +
5.0275e-9 * t3
var dt =
5.1984665886602e0 + 7771.377145594e0 * t - 5.559397e-4 * t2 +
3.196e-8 * t3
var omt =
2.1824391966156e0 - 33.757044613e0 * t + 3.622625e-5 * t2 +
3.734e-8 * t3
var psi =
(-17.1996e0 - 0.01742 * t) * sin(omt) +
(0.2062e0 + 0.00002 * t) * sin(2 * omt) +
(-1.3187e0 - 0.00016 * t) * sin(2 * (ft - dt + omt)) +
(0.1426e0 - 0.00034 * t) * sin(llt) +
(-0.0517 + 0.00012 * t) * sin(llt + 2 * ft - 2 * dt + 2 * omt) +
(0.0217 - 0.00005 * t) * sin(-llt + 2 * ft - 2 * dt + 2 * omt) +
(0.0129 + 0.00001 * t) * sin(2 * ft - 2 * dt + omt)
var r =
(-0.2274e0 - 0.00002 * t) * sin(2 * ft + 2 * omt) +
(0.0712e0 + 0.00001 * t) * sin(lt) +
(-0.0386e0 - 0.00004 * t) * sin(2 * ft + omt) -
0.0301e0 * sin(lt + 2 * ft + 2 * omt) -
0.0158 * sin(lt - 2 * dt) +
0.0123 * sin(-lt + 2 * ft + 2 * omt)
psi += r
if (tt < 0.125) {
tt += 0.875
} else {
tt -= 0.125
}
r = 84381.448e0 - 46.84024e0 * t - 0.00059e0 * t2 + 0.001813e0 * t3
r = r * PI / 180.0 / 3600.0
psi = psi * PI / 180.0 / 3600.0
r = cos(r)
r = psi * r
var s =
(
24110.54841 + 8640184.812866 * t + 0.093104 * t2 -
6.2e-6 * t3
) * 2 * PI / 86400.0e0
s = s + r + 2 * PI * tt * 1.002737811906E0
var ds = s / (PI + PI)
var ts = truncate(ds)
s = (ds - ts) * (PI + PI)
return s
}
fun grinvToASK(gsk: OrbitalPoint): OrbitalPoint {
var m = Matrix3D()
m.makeOzMatrix(si2000(gsk.t))
var ra = m * gsk.r
var va = m * gsk.v
va.x -= earth.wEarth * ra.y
va.y += earth.wEarth * ra.x
return OrbitalPoint(gsk.t, gsk.vit, ra, va)
}
fun grinvToASK(
gsk: OrbitalPoint,
si: Double,
): OrbitalPoint {
var m = Matrix3D()
m.makeOzMatrix(si)
var ra = m * gsk.r
var va = m * gsk.v
va.x -= earth.wEarth * ra.y
va.y += earth.wEarth * ra.x
return OrbitalPoint(gsk.t, gsk.vit, ra, va)
}
fun grinvToASK(
gsk: Vector3D,
t: Double,
): Vector3D {
var m = Matrix3D()
m.makeOzMatrix(si2000(t))
return m * gsk
}
fun askToGrinvich(
ask: Vector3D,
t: Double,
): Vector3D {
var m = Matrix3D()
m.makeOzMatrix(-si2000(t))
return m * ask
}
fun askToGrinvich(ask: OrbitalPoint): OrbitalPoint {
var m = Matrix3D()
m.makeOzMatrix(-si2000(ask.t))
var ra = m * ask.r
var va = m * ask.v
va.x += earth.wEarth * ra.y
va.y -= earth.wEarth * ra.x
return OrbitalPoint(ask.t, ask.vit, ra, va)
}
fun askToGrinvich(
ask: OrbitalPoint,
si: Double,
): OrbitalPoint {
var m = Matrix3D()
m.makeOzMatrix(-si)
var ra = m * ask.r
var va = m * ask.v
va.x += earth.wEarth * ra.y
va.y -= earth.wEarth * ra.x
return OrbitalPoint(ask.t, ask.vit, ra, va)
}
fun moonCoordinates(tm: Double): Vector3D {
var d: Double = tm / 86400.0
d = d - 0.125
val t: Double = (d - baseDate) / 36525.0 // Юлианский период ot 12h 1.1.2000
val t2 = t * t
val t3 = t2 * t
// (* Средняя аномалия ЛУНЫ*)
val lL = 2.355555743494e0 + 8328.691425719e0 * t + 1.545547e-4 * t2 + 2.50e-7 * t3
// (* Средняя аномалия СОЛНЦА*)
val lS = 6.240060126913e0 + 628.301955171e0 * t - 2.681989e-6 * t2 - 6.593e-10 * t3
// (* Средний аргумент широты ЛУНЫ *)
val f = 1.6279050815375e0 + 8433.466156916e0 * t - 6.181956e-5 * t2 + 5.0275e-9 * t3
// (* Разность средних долгот ЛУНЫ и СОЛНЦА *)
d = 5.1984665886602e0 + 7771.377145594e0 * t - 5.559397e-4 * t2 + 3.196e-8 * t3
val d2 = d + d
// (* Вычисление горизонтального паралакса и радиуса ЛУНЫ *)
val pl1 =
4.8481368110953599e-6 * (
3422.7 + 28.233 * cos(d2) + 3.086 * cos(lL + d2) + 186.539 * cos(lL) + 34.311 *
cos(
lL - d2,
) + 0.6 * cos(lL - 4 * d) - 0.399 * cos(lS) +
1.917 * cos(lS - d2) - 0.978 * cos(d) + 10.165 * cos(2 * lL) -
0.949 * cos(lL + lS) + 1.443 * cos(lL + lS - d2) + 1.152 * cos(lL - lS) + 0.621 * cos(3 * lL) - 0.713 *
cos(
lL - 2 * f,
)
)
var rl = 6378.14 / pl1
rl = rl * 1.0e3
// (* Средняя долгота ЛУНЫ *)
val vL =
3.8103405583329555 + 8399.7091123847003500 * t -
0.0000281288897780 * t2 + 0.0000000921145994 * t3
// (* Вычисление эклиптической долготы ЛУНЫ*)
val vSin =
4.8481368110953599e-6 * (
2369.9 * sin(d2) + 191.95 * sin(lL + d2) + 22639.5 * sin(lL) - 4586.42 * sin(lL - d2) - 668.11 *
sin(
lS,
) - 165.14 * sin(lS - d2) - 125.15 * sin(d) + 769.01 * sin(2 * lL) - 211.65 * sin(2 * lL - d2) - 109.66 *
sin(
lL + lS,
) - 205.96 * sin(lL + lS - d2) + 147.69 * sin(lL - lS) - 411.6 * sin(2 * f)
)
val arg = vL + vSin
val cl = cos(arg)
val sl = sin(arg)
// (* Вычисление эклиптической широты Луны *)
val betta =
4.8481368110953599e-6 * (
117.26 * sin(f + d2) + 18461.48 * sin(f) -
623.65 * sin(f - d2) + 1010.18 * sin(lL + f) -
166.57 * sin(lL + f - d2) + 199.48 * sin(-lL + f + d2) -
999.69 * sin(-lL + f)
)
val cb = cos(betta)
val sb = sin(betta)
// (* Средний наклон эклиптики *)
val ep = 0.4090928042223290 - 0.0002270878917845 * t - 0.0000000028604007 * t2 + 0.0000000087896720 * t3
val ce = cos(ep)
val se = sin(ep)
var moon = Vector3D()
moon.x = rl * cl * cb
moon.y = rl * (sl * cb * ce - sb * se)
moon.z = rl * (sb * ce + sl * cb * se)
return moon
}
fun sunCoordinates(tm: Double): Vector3D {
var d = tm / 86400.0
var dd = d - 0.125
var t = (dd - baseDate) / 36525.0
var t2 = t * t
var t3 = t2 * t
// Средняя долгота
var l =
4.8481368110953599e-6 * (
1009677.850 +
(100 * 1296000.0 + 2771.270) * t + 1.089 * t2
)
// Долгота перигелия
var p =
4.8481368110953599e-6 * (
1018578.046 + 6190.046 * t +
1.666 * t2 + 0.012 * t3
)
// Эксцентриситет земной орбиты
var ek = 0.0167086342 - 0.00004203654 * t - 0.00000012673 * t2
// Средний наклон эклиптики
var e1 =
4.8481368110953599e-6 * (
84381.448 - 46.84024 * t -
0.00059 * t2 + 0.001813 * t3
)
// Истинный наклон эклиптики
var oMT =
2.1824391966156e0 - 33.757044613e0 * t + 3.622625e-5 * t2 +
3.734e-8 * t3
var psi = 4.8481368110953599e-6 * 9.2025e0 * cos(oMT)
e1 += psi
// Средняя аномалия Земли
var g = l - p
var e = g + ek
for (i in 1..5)
e = g + ek * sin(e)
// Радиус Земли *)
var r = ase * (1.0 - ek * cos(e)) * 1.0e3
// Истинная аномалия
var sinv = ase / r * sqrt(1.0 - ek * ek) * sin(e)
var cosv = ase / r * (cos(e) - ek)
var v = atan2(sinv, cosv)
// Эклиптическая истинная долгота *)
var la = v + p
var sun = Vector3D()
// Собственно вычисления координат *)
sun.x = r * cos(la)
var y = r * sin(la)
sun.y = y * cos(e1)
sun.z = y * sin(e1)
return sun
}
fun sunCoordinatesGSK(t: Double): Vector3D {
var m = Matrix3D()
m.makeOzMatrix(-si2000(t))
var sun = sunCoordinates(t)
return m * sun
}
fun sunAngle(
t: Double,
currentObj: Vector3D,
): Double {
var sun = sunCoordinatesGSK(t)
var blh = earth.xyz2blh(currentObj)
var m =
Matrix3D(
Vector3D(cos(blh.lat) * cos(blh.long), cos(blh.lat) * sin(blh.long), sin(blh.lat)),
Vector3D(-sin(blh.long), cos(blh.long), 0.0),
Vector3D(-sin(blh.lat) * cos(blh.long), -sin(blh.lat) * sin(blh.long), cos(blh.lat)),
)
var pp = m * currentObj
var sp = m * sun
var r = sp - pp
return atan(r.x / sqrt(r.z * r.z + r.y * r.y))
}
fun sunMoonCorrection(
t3: Double,
x: Double,
y: Double,
z: Double,
): Vector3D {
var corrections = Vector3D()
val lm = 4902.7854
val mss = 132712518017.51
val kapa = 0.0
var sun = sunCoordinates(t3)
sun = sun * (1 / 1000.0)
var moon = moonCoordinates(t3)
moon = moon * (1 / 1000.0)
// Учет влияния Луны
var dpx: Double
var dpy: Double
var dpz: Double
dpx = moon.x - x
dpy = moon.y - y
dpz = moon.z - z
var r = dpx * dpx + dpy * dpy + dpz * dpz
var dr = sqrt(r)
var pp1 = r * dr
r = moon.x * moon.x + moon.y * moon.y + moon.z * moon.z
dr = sqrt(r)
var pp2 = r * dr
corrections.x = lm * (dpx / pp1 - moon.x / pp2)
corrections.y = lm * (dpy / pp1 - moon.y / pp2)
corrections.z = lm * (dpz / pp1 - moon.z / pp2)
dpx = sun.x - x
dpy = sun.y - y
dpz = sun.z - z
r = dpx * dpx + dpy * dpy + dpz * dpz
dr = sqrt(r)
pp1 = r * dr
dr = sun.module()
r = dr * dr
pp2 = r * dr
var kap = kapa
val rap = sqrt(x * x + y * y + z * z)
val cf = (sun.x * x + sun.y * y + sun.z * z) / (dr * rap)
if (cf <= 0) {
val rab1 = rap * sqrt(1 - cf * cf)
if (rab1 < 6378.388) kap = 0.0
}
val p1 = mss / pp1 * (1 - kap)
val p2 = mss / pp2
// Закоментарить для неучета Солнца
corrections.x += (dpx * p1 - sun.x * p2)
corrections.y += (dpy * p1 - sun.y * p2)
corrections.z += (dpz * p1 - sun.z * p2)
return corrections
}
}
@@ -0,0 +1,237 @@
package ballistics.utils.atmosphere
import kotlin.math.cos
import kotlin.math.exp
import kotlin.math.floor
import kotlin.math.sin
import kotlin.math.sqrt
import kotlin.math.truncate
class Atmosphere2004() {
val aAtm =
listOf(
-2.53418e-02,
-2.44075e-03,
3.08389e-06,
2.90115e-06,
-4.99606e-08,
3.36327e-10,
-1.0966e-12,
1.73227e-15,
-1.06271e-18,
)
val fi1 =
listOf(
0.5411,
0.5515,
0.5585,
0.5585,
0.5585,
0.5585,
0.5585,
)
val nAtm =
listOf(
-1.10059e+00,
-1.37626e-02,
-4.05631e-05,
4.64077e-08,
1.31904e+01,
-1.00640e-01,
2.66200e-04,
-2.10328e-07,
-6.83628e-01,
8.17955e-03,
-5.37817e-06,
4.18766e+00,
-3.63633e-02,
1.28280e-04,
-1.18060e-07,
-3.77859e+00,
4.59058e-05,
-5.42317e-05,
4.67279e-08,
8.62648e+00,
-6.45632e-02,
1.67737e-04,
-1.23719e-07,
-5.32838e-01,
7.17357e-03,
-4.13071e-06,
3.27388e+00,
-2.61311e-02,
9.26510e-05,
-8.03080e-08,
-2.06474e+00,
-1.16758e-02,
-2.26591e-05,
2.19816e-08,
5.84701e+00,
-4.29259e-02,
1.09960e-04,
-7.53447e-08,
-2.04545e-01,
5.44465e-03,
-2.53356e-06,
8.09259e-01,
-7.46853e-03,
4.52020e-05,
-4.13791e-08,
2.27394e+00,
-3.94166e-02,
3.95835e-05,
-2.19992e-08,
4.16132e+00,
-2.99269e-02,
7.56965e-05,
-4.77865e-08,
2.42053e-02,
4.38092e-03,
-1.76272e-06,
1.24462e+00,
-7.76442e-03,
3.88966e-05,
-3.23585e-08,
1.67658e+00,
-3.55099e-02,
3.46586e-05,
-1.95730e-08,
3.14269e+00,
-2.20308e-02,
5.48269e-05,
-3.14805e-08,
1.71702e-01,
3.77273e-03,
-1.45257e-06,
1.34930e-01,
4.11962e-04,
1.88965e-05,
-1.74602e-08,
-1.37615e+00,
-1.68609e-02,
3.40709e-07,
1.72566e-09,
2.43907e+00,
-1.65847e-02,
4.06547e-05,
-2.08670e-08,
2.59209e-01,
3.45977e-03,
-1.38895e-06,
1.47530e-01,
1.95702e-03,
1.17537e-05,
-1.08184e-08,
2.38462e+00,
-3.87222e-02,
4.60938e-05,
-2.80992e-08,
1.76084e+00,
-1.10685e-02,
2.67151e-05,
-1.08944e-08,
2.60394e-01,
3.53130e-03,
-1.54729e-06,
-7.53476e-03,
3.97475e-03,
5.08266e-06,
-5.66222e-09,
)
private fun floorm(a: Double): Double {
return (if (a >= 0) floor(a) else -1 * (floor(a * -1)))
}
fun atm2004Kav(
f81: Double,
deny: Double,
h: Double,
fld3: Array<Double>,
lxg: Double,
lyg: Double,
lzg: Double,
): Double {
var roh : Double
val ro0 = 1.58868e-08
val n0A = 3.03853
val n1A = 1.875e-03
val rg: Double = sqrt(fld3[3] * fld3[3] + fld3[4] * fld3[4] + fld3[5] * fld3[5])
val xort: Double = fld3[3] / rg
val yort: Double = fld3[4] / rg
val zort: Double = fld3[5] / rg
var j : Int
val f1: Double = floorm(f81 / 25.0 + 0.5)
var f0 = 25.0 * f1
if (f0 >= 225.0) {
f0 = 250.0
j = 7
} else {
j = truncate(f1 - 2.0).toInt()
}
if (j < 1) j = 1
val fUnderscore = (f81 - f0) / f0
val d = deny - 1.0
val aD: Double =
aAtm[0] + d * (
aAtm[1] + d * (
aAtm[2] + d * (
aAtm[3] + d * (
aAtm[4] + d * (
aAtm[5] + d * (aAtm[6] + d * (aAtm[7] + d * aAtm[8]))
)
)
)
)
)
val h1 = h * 0.001
val n = n0A + h1 * n1A
val x0ro: Double = lxg * cos(fi1[j - 1]) - lyg * sin(fi1[j - 1])
val y0ro: Double = lxg * sin(fi1[j - 1]) + lyg * cos(fi1[j - 1])
val cosfi = xort * x0ro + yort * y0ro + zort * lzg
val k10 = 0.5 * (1 + cosfi)
val mk: Double = Math.pow(k10, 0.5 * n)
val nI = (15 * (j - 1)).toDouble()
val aN: Double =
nAtm[((nI + 0).toInt())] + h1 * (
nAtm[((nI + 1).toInt())] + h1 * (
nAtm[((nI + 2).toInt()) ]+ h1 *
nAtm[(
(nI + 3).toInt()
)]
)
)
val cN: Double =
nAtm[((nI + 4).toInt())] + h1 * (
nAtm[(nI + 5).toInt()] + h1 * (
nAtm[(nI + 6).toInt()] + h1 *
nAtm[(nI + 7).toInt()]
)
)
val dN: Double =
nAtm[(nI + 8).toInt()] + h1 * (nAtm[(nI + 9).toInt()] + h1 * nAtm[(nI + 10).toInt()])
val lN: Double =
nAtm[(nI + 11).toInt()] + h1 * (
nAtm[(nI + 12).toInt()] + h1 * (
nAtm[(nI + 13).toInt()] + h1 *
nAtm[(nI + 14).toInt()]
)
)
val k0 = 1.0 + lN * fUnderscore
val k1 = cN * mk
val k2 = dN * aD
roh = ro0 * exp(aN)
roh *= k0 * (1.0 + k1 + k2)
return roh
}
}
@@ -0,0 +1,16 @@
package ballistics.utils.atmosphere
internal fun getAtm62(
h: Double,
indAtm: Int,
): Double {
val ht = arrayOf<Double>(0.0, 0.2e+5, 0.6e+5, 1.0e+5, 1.5e+5, 3.0e+5, 6e+5, 9e+5, 10e6)
val a = arrayOf<Double>(0.125, 0.9091e-2, 0.263e-4, 0.4141e-7, 0.21727e-9, 0.48607e-11, 0.8904e-13, 0.6497e-14, 1e-17)
val k1 = arrayOf<Double>(-0.2639e-8, 0.4407e-9, -0.256e-8, 0.14688e-8, 0.8004e-10, 0.7111e-11, 0.1831e-11, 0.0, 0.0)
val k2 = arrayOf<Double>(0.7825e-4, 0.16375e-3, 0.5905e-4, 0.1787e-3, 0.37336e-4, 0.15467e-4, 0.9275e-5, 0.954e-5, 1e-6)
val rab = k1[indAtm - 1] * ((h - ht[indAtm - 1]) * (h - ht[indAtm - 1])) - k2[indAtm - 1] * (h - ht[indAtm - 1])
val ro = a[indAtm - 1] * Math.exp(rab)
return ro
}
@@ -0,0 +1,35 @@
package ballistics.utils.atmosphere
internal fun getAtm81(h: Double): Double {
val ht = arrayOf<Double>(0.0, 0.2e+5, 0.6e+5, 1.0e+5, 1.5e+5, 3.0e+5, 6.0e+5, 9.0e+5)
val a = arrayOf<Double>(0.12522, 0.91907e-2, 0.31655e-4, 0.54733e-7, 0.20474e-9, 0.19019e-11, 0.11495e-13, 0.58038e-15)
val k1 = arrayOf<Double>(-0.20452e-8, 0.62669e-9, -0.86999e-9, 0.12870e-8, 0.10167e-9, 0.97266e-11, 0.15127e-10, 0.0)
val k2 = arrayOf<Double>(0.90764e-4, 0.16739e-3, 0.12378e-3, 0.17527e-3, 0.45825e-4, 0.19885e-4, 0.14474e-4, 0.39247e-5)
var i: Int
var rab : Double
var ro : Double
if (h < 9.0e+5) {
i = 0
i += 1
rab = ht[0] - h
while (rab < 0.0) {
i += 1
rab = ht[i - 1] - h
}
if (rab == 0.0) {
ro = a[i - 1]
} else {
i -= 1
rab = k1[i - 1] * (h - ht[i - 1]) * (h - ht[i - 1]) - k2[i - 1] * (h - ht[i - 1])
ro = a[i - 1] * Math.exp(rab)
}
} else {
i = 9
i -= 1
rab = k1[i - 1] * (h - ht[i - 1]) * (h - ht[i - 1]) - k2[i - 1] * (h - ht[i - 1])
ro = a[i - 1] * Math.exp(rab)
}
return ro
}
@@ -0,0 +1,27 @@
package ballistics.utils.earth
import ballistics.types.BLHPoint
import ballistics.utils.math.Vector3D
abstract class AbstractEarth {
var fM = 398600.44e9
var ekvRadius = 6378136.0
var wEarth = 0.7292115e-4
var alphaEllips = 1 / 298.257839303
var middleRadius = 6378100.0
var polarRadius = (1 - alphaEllips) * ekvRadius
var c20 = -1.08262741e-3
var eElips2 = 2 * alphaEllips - alphaEllips * alphaEllips
fun blh2xyz(blh: BLHPoint): Vector3D {
return blh2xyz(blh.lat, blh.long, blh.h)
}
abstract fun blh2xyz(
b: Double,
l: Double,
h: Double,
): Vector3D
abstract fun xyz2blh(r: Vector3D): BLHPoint
}
@@ -0,0 +1,10 @@
package ballistics.utils.earth
import ballistics.types.EarthType
fun getEarth(earth: EarthType): AbstractEarth {
return when (earth) {
EarthType.PZ90d0 -> EarthPZ90d0()
EarthType.PZ90d02 -> EarthPZ90d02()
}
}
@@ -0,0 +1,57 @@
package ballistics.utils.earth
import ballistics.types.BLHPoint
import ballistics.utils.math.Vector3D
import kotlin.math.PI
import kotlin.math.atan2
import kotlin.math.hypot
import kotlin.math.sqrt
open class EarthPZ90d0 : AbstractEarth() {
init {
fM = 398600.44e9
ekvRadius = 6378136.0
wEarth = 0.7292115e-4
alphaEllips = 1 / 298.257839303
middleRadius = 6378100.0
polarRadius = (1 - alphaEllips) * ekvRadius
c20 = -1.08262741e-3
eElips2 = 2 * alphaEllips - alphaEllips * alphaEllips
}
override fun blh2xyz(
b: Double,
l: Double,
h: Double,
): Vector3D {
var v = Vector3D()
val sb = Math.sin(b)
val cb = Math.cos(b)
val sl = Math.sin(l)
val cl = Math.cos(l)
val n = ekvRadius / Math.sqrt(1 - eElips2 * sb * sb)
v.x = (n + h) * cb * cl
v.y = (n + h) * cb * sl
v.z = ((1 - eElips2) * n + h) * sb
return v
}
override fun xyz2blh(r: Vector3D): BLHPoint {
var l = atan2(r.y, r.x)
if (l < 0.0) l = PI * 2.0 + l
var r1: Double = hypot(r.x, r.y)
val rr: Double = hypot(r1, r.z)
val sinFiC: Double = r.z / rr
val cosFiC = r1 / rr
val fic: Double = atan2(r.z, r1)
val ekvadrat: Double = (2.0 - alphaEllips) * alphaEllips
r1 = 1.0 - ekvadrat * cosFiC * cosFiC
val delta = ekvadrat * sinFiC * cosFiC / r1
val rE: Double = ekvRadius * sqrt(1.0 - ekvadrat) / sqrt(r1)
val eMal = rE * delta / rr
var b = fic + eMal
var h = ((rr - rE) * (1.0 - eMal * delta / 2.0))
return BLHPoint(b, l, h)
}
}
@@ -0,0 +1,14 @@
package ballistics.utils.earth
class EarthPZ90d02 : EarthPZ90d0() {
init {
fM = 398600.4418e9
ekvRadius = 6378136.0
wEarth = 0.7292115e-4
alphaEllips = 1 / 298.25784
middleRadius = 6378100.0
polarRadius = (1 - alphaEllips) * ekvRadius
c20 = -1.08262575e-3
eElips2 = 2 * alphaEllips - alphaEllips * alphaEllips
}
}
@@ -0,0 +1,58 @@
package ballistics.utils.math
class Matrix3D(var first: Vector3D, var second: Vector3D, var third: Vector3D) {
constructor() : this(Vector3D(), Vector3D(), Vector3D())
fun firstColumn(): Vector3D {
return Vector3D(first.x, second.x, third.x)
}
fun secondColumn(): Vector3D {
return Vector3D(first.y, second.y, third.y)
}
fun thirdColumn(): Vector3D {
return Vector3D(first.z, second.z, third.z)
}
fun empty() {
first.setData(0.0, 0.0, 0.0)
second.setData(0.0, 0.0, 0.0)
third.setData(0.0, 0.0, 0.0)
}
fun makeOxMatrix(angle: Double) {
first.setData(1.0, 0.0, 0.0)
second.setData(0.0, Math.cos(angle), -Math.sin(angle))
third.setData(0.0, Math.sin(angle), Math.cos(angle))
}
fun makeOyMatrix(angle: Double) {
first.setData(Math.cos(angle), 0.0, Math.sin(angle))
second.setData(0.0, 1.0, 0.0)
third.setData(-Math.sin(angle), 0.0, Math.cos(angle))
}
fun makeOzMatrix(angle: Double) {
first.setData(Math.cos(angle), -Math.sin(angle), 0.0)
second.setData(Math.sin(angle), Math.cos(angle), 0.0)
third.setData(0.0, 0.0, 1.0)
}
fun transpose(): Matrix3D {
return Matrix3D(
Vector3D(first.x, second.x, third.x),
Vector3D(first.y, second.y, third.y),
Vector3D(first.z, second.z, third.z),
)
}
operator fun times(other: Vector3D) = Vector3D(first * other, second * other, third * other)
operator fun times(other: Matrix3D) =
Matrix3D(
Vector3D(first * other.firstColumn(), first * other.secondColumn(), first * other.thirdColumn()),
Vector3D(second * other.firstColumn(), second * other.secondColumn(), second * other.thirdColumn()),
Vector3D(third * other.firstColumn(), third * other.secondColumn(), third * other.thirdColumn()),
)
}
@@ -0,0 +1,195 @@
package ballistics.utils.math
import kotlin.math.acos
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
class Quaternion3D(
var q0: Double,
var q1: Double,
var q2: Double,
var q3: Double,
) {
constructor() : this(0.0, 0.0, 0.0, 0.0)
fun makeQuat(
angle: Double,
v: Vector3D,
) {
var vv = v.basis()
q0 = cos(angle / 2.0)
q1 = vv.x * sin(angle / 2.0)
q2 = vv.y * sin(angle / 2.0)
q3 = vv.z * sin(angle / 2.0)
makeBasis()
}
/*!
норма кватерниона
*/
fun norm() = sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3)
fun makeBasis() {
var n = norm()
q0 /= n
q1 /= n
q2 /= n
q3 /= n
}
fun basis(): Quaternion3D {
var n = norm()
return Quaternion3D(q0 / n, q1 / n, q2 / n, q3 / n)
}
fun makeOpposite() {
var n = norm()
q0 /= n
q1 /= -n
q2 /= -n
q3 /= -n
}
fun opposite(): Quaternion3D {
var n = norm()
return Quaternion3D(q0 / n, -q1 / n, -q2 / n, -q3 / n)
}
fun module() = acos(basis().q0) * 2.0
fun matrix(): Matrix3D {
var x2 = q1 + q1
var y2 = q2 + q2
var z2 = q3 + q3
var xx = q1 * x2
var yy = q2 * y2
var wx = q0 * x2
var xy = q1 * y2
var yz = q2 * z2
var wy = q0 * y2
var xz = q1 * z2
var zz = q3 * z2
var wz = q0 * z2
return Matrix3D(
Vector3D(1.0 - (yy + zz), xy + wz, xz - wy),
Vector3D(xy - wz, 1.0 - (xx + zz), yz + wx),
Vector3D(xz + wy, yz - wx, 1.0 - (xx + yy)),
).transpose()
}
fun fromMatrixStanley(m: Matrix3D) {
val t = m.first.x + m.second.y + m.third.z
val valueq0 = (1.0 + t) / 4.0
val valueq1 = (1.0 + 2.0 * m.first.x - t) / 4.0
val valueq2 = (1.0 + 2.0 * m.second.y - t) / 4.0
val valueq3 = (1.0 + 2.0 * m.third.z - t) / 4.0
if ((valueq0 >= valueq1) && (valueq0 >= valueq2) && (valueq0 >= valueq3)) {
q0 = sqrt(valueq0)
q1 = (m.second.z - m.third.y) / (4.0 * q0)
q2 = (m.third.x - m.first.z) / (4.0 * q0)
q3 = (m.first.y - m.second.x) / (4.0 * q0)
}
if ((valueq1 >= valueq0) && (valueq1 >= valueq2) && (valueq1 >= valueq3)) {
q1 = sqrt(valueq1)
q0 = (m.second.z - m.third.y) / (4.0 * q1)
q2 = (m.first.y + m.second.x) / (4.0 * q1)
q3 = (m.third.x + m.first.z) / (4.0 * q1)
}
if ((valueq2 >= valueq0) && (valueq2 >= valueq1) && (valueq2 >= valueq3)) {
q2 = sqrt(valueq2)
q0 = (m.third.x - m.first.z) / (4.0 * q2)
q1 = (m.first.y + m.second.x) / (4.0 * q2)
q3 = (m.second.z + m.third.y) / (4.0 * q2)
}
if ((valueq3 >= valueq1) && (valueq3 >= valueq2) && (valueq3 >= valueq0)) {
q3 = sqrt(valueq3)
q0 = (m.first.y - m.second.x) / (4.0 * q3)
q1 = (m.third.x + m.first.z) / (4.0 * q3)
q2 = (m.second.z + m.third.y) / (4.0 * q3)
}
makeBasis()
makeOpposite()
}
operator fun times(v: Vector3D): Vector3D {
var p = opposite()
var r = Vector3D()
var a = (q0 + q1) * (v.x)
var b = (q3 - q2) * (v.y - v.z)
var c = (q1 - q0) * (v.y + v.z)
var d = (q2 + q3) * (v.x)
var e = (q1 + q3) * (v.x + v.y)
var f = (q1 - q3) * (v.x - v.y)
var g = (q0 + q2) * (-v.z)
var h = (q0 - q2) * (v.z)
var qq0 = b + (-e - f + g + h) * 0.5
var qq1 = a - (e + f + g + h) * 0.5
var qq2 = -c + (e - f + g - h) * 0.5
var qq3 = -d + (e - f - g + h) * 0.5
a = (qq0 + qq1) * (p.q0 + p.q1)
c = (qq1 - qq0) * (p.q2 + p.q3)
d = (qq2 + qq3) * (p.q1 - p.q0)
e = (qq1 + qq3) * (p.q1 + p.q2)
f = (qq1 - qq3) * (p.q1 - p.q2)
g = (qq0 + qq2) * (p.q0 - p.q3)
h = (qq0 - qq2) * (p.q0 + p.q3)
r.x = a - (e + f + g + h) * 0.5
r.y = -c + (e - f + g - h) * 0.5
r.z = -d + (e - f - g + h) * 0.5
return r
}
operator fun times(other: Quaternion3D): Quaternion3D {
var a = (q0 + q1) * (other.q0 + other.q1)
var b = (q3 - q2) * (other.q2 - other.q3)
var c = (q1 - q0) * (other.q2 + other.q3)
var d = (q2 + q3) * (other.q1 - other.q0)
var e = (q1 + q3) * (other.q1 + other.q2)
var f = (q1 - q3) * (other.q1 - other.q2)
var g = (q0 + q2) * (other.q0 - other.q3)
var h = (q0 - q2) * (other.q0 + other.q3)
return Quaternion3D(
b + (-e - f + g + h) * 0.5,
a - (e + f + g + h) * 0.5,
-c + (e - f + g - h) * 0.5,
-d + (e - f - g + h) * 0.5,
)
}
operator fun plus(other: Quaternion3D): Quaternion3D {
return Quaternion3D(
q0 + other.q0,
q1 + other.q1,
q2 + other.q2,
q3 + other.q3,
)
}
operator fun minus(other: Quaternion3D): Quaternion3D {
return Quaternion3D(
q0 - other.q0,
q1 - other.q1,
q2 - other.q2,
q3 - other.q3,
)
}
}
@@ -0,0 +1,70 @@
package ballistics.utils.math
import kotlin.math.sqrt
class Vector3D(ax: Double, ay: Double, az: Double) {
var x = ax
var y = ay
var z = az
constructor() : this(0.0, 0.0, 0.0)
fun setData(
xVal: Double,
yVal: Double,
zVal: Double,
) {
x = xVal
y = yVal
z = zVal
}
fun module() = sqrt(x * x + y * y + z * z)
fun basis(): Vector3D {
var v = Vector3D()
v.x = x / module()
v.y = y / module()
v.z = z / module()
return v
}
override fun toString(): String {
return "x = $x, y = $y, z = $z"
}
operator fun plus(other: Vector3D) = Vector3D(x + other.x, y + other.y, z + other.z)
operator fun minus(other: Vector3D) = Vector3D(x - other.x, y - other.y, z - other.z)
operator fun times(value: Double) = Vector3D(x * value, y * value, z * value)
operator fun times(other: Vector3D) = x * other.x + y * other.y + z * other.z
operator fun rem(other: Vector3D) =
Vector3D(
y * other.z - z * other.y,
z * other.x - x * other.z,
x * other.y - y * other.x,
)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Vector3D
if (x != other.x) return false
if (y != other.y) return false
if (z != other.z) return false
return true
}
override fun hashCode(): Int {
var result = x.hashCode()
result = 31 * result + y.hashCode()
result = 31 * result + z.hashCode()
return result
}
}
@@ -0,0 +1,25 @@
package ballistics.utils.math.equations
abstract class AbstractEquationCalculator {
var iteration = 0
var maxIterations = 20
var delta = 0.001
var value = 0.0
abstract fun calculate(
x1: Double,
x2: Double,
f: (Double) -> Double,
): Double?
inline fun check(
x1: Double,
x2: Double,
f: (Double) -> Double,
): Boolean {
val y1 = f(x1)
val y2 = f(x2)
return ((y1 - value) * (y2 - value) <= 0)
}
}
@@ -0,0 +1,48 @@
package ballistics.utils.math.equations
import kotlin.math.abs
class EquationCalculator2Div : AbstractEquationCalculator() {
override fun calculate(
x1: Double,
x2: Double,
f: (Double) -> Double,
): Double? {
if (!check(x1, x2, f)) {
return null
}
iteration = 0
var xl = x1
var xr = x2
var xmid = (xl + xr) / 2.0
var ymid = f(xmid)
var y1 = f(xl)
if ((y1 - value) * (ymid - value) < 0) {
xr = xmid
} else {
xl = xmid
}
var d = abs(ymid - value)
while ((d > delta) && (iteration < maxIterations)) {
xmid = (xl + xr) / 2.0
ymid = f(xmid)
y1 = f(xl)
if ((y1 - value) * (ymid - value) < 0) {
xr = xmid
} else {
xl = xmid
}
d = abs(ymid - value)
iteration++
}
return if (iteration < maxIterations) {
xmid
} else {
null
}
}
}
@@ -0,0 +1,44 @@
package ballistics.utils.math.equations
import kotlin.math.abs
class EquationCalculatorSpan : AbstractEquationCalculator() {
override fun calculate(
x1: Double,
x2: Double,
f: (Double) -> Double,
): Double? {
if (!check(x1, x2, f))
return null
iteration = 0
var xl = x1
var xr = x2
var yl = f(xl)
var yr = f(xr)
var xmid = xl - ((xr - xl) / ((yr - value) - (yl - value))) * (yl - value)
var ymid = f(xmid)
var d = abs(ymid - value) * 10000.0
while ((d > delta * 10000.0) && (iteration < maxIterations)) {
if ((yl - value) * (ymid - value) < 0) {
xr = xmid
yr = ymid
} else {
xl = xmid
yl = ymid
}
xmid = xl - ((xr - xl) / ((yr - value) - (yl - value))) * (yl - value)
ymid = f(xmid)
d = abs(ymid - value) * 10000.0
iteration++
}
return if (iteration < maxIterations) {
xmid
} else {
null
}
}
}
@@ -0,0 +1,13 @@
package ballistics.utils.math.minmax
abstract class AbstractMinMaxCalculator {
var delta = 0.00001
var maxIterations = 150
var sign = 1 // min = 1, max = -1
abstract fun calculate(
x1: Double,
x2: Double,
f: (Double) -> Double,
): Double?
}
@@ -0,0 +1,33 @@
package ballistics.utils.math.minmax
class MinMaxCrushingCalculator : AbstractMinMaxCalculator() {
override fun calculate(
x1: Double,
x2: Double,
f: (Double) -> Double,
): Double? {
var step = 1.0
var iteration = 0
var xk: Double = (x1 + x2) / 2
var yk: Double
var yk2: Double
yk = f(xk)
var xk2: Double = xk + step
yk2 = f(xk2)
var d = xk2 - xk
if (d < 0) d *= -1.0
while ((d >= delta) && (iteration < maxIterations)) {
if (yk2 * sign > yk * sign) step = -step / 3
xk = xk2
yk = yk2
xk2 = xk + step
yk2 = f(xk2)
d = xk2 - xk
if (d < 0) d *= -1.0
iteration++
}
return if (iteration < maxIterations) xk2 else null
}
}
@@ -0,0 +1,50 @@
package ballistics.utils.math.minmax
import kotlin.math.sqrt
class MinMaxGoldenSectCalculator : AbstractMinMaxCalculator() {
val tau = (sqrt(5.0) - 1) / 2
override fun calculate(
x1: Double,
x2: Double,
f: (Double) -> Double,
): Double? {
var iteration = 0
var x: Double?
var xl = x1
var xr = x2
var x11: Double = xl + (1 - tau) * (xr - xl)
var x22: Double = xl + tau * (xr - xl)
var d = x22 - x11
if (d < 0) d *= -1.0
//x = x1
var y1 = f(x11)
var y2 = f(x22)
while (d > delta) {
if (sign * y1 > sign * y2) {
xl = x11
x11 = x22
y1 = y2
x22 = xl + tau * (xr - xl)
y2 = f(x22)
} else {
xr = x22
x22 = x11
y2 = y1
x11 = xl + (1 - tau) * (xr - xl)
y1 = f(x11)
}
d = x22 - x11
if (d < 0) d *= -1.0
iteration++
}
x = (x11 + x22) / 2
return if (iteration < maxIterations) x else null
}
}
@@ -0,0 +1,340 @@
package ballistics.zrv
import ballistics.orbitalPoints.AbstractOrbPointsCalculator
import ballistics.types.*
import ballistics.utils.earth.getEarth
import ballistics.utils.math.Vector3D
import ballistics.utils.math.equations.EquationCalculator2Div
import ballistics.utils.math.minmax.MinMaxGoldenSectCalculator
import kotlin.math.PI
import kotlin.math.atan
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
/**
* Класс расчета ЗРВ КА-ППИ итерационным методом
* Осуществляется проход по точкам орбиты с заданным шагом и формирование участков
* видимости КА-ППИ с уточнением моментов входа и выхода в/из зоны видимости
*
*/
internal class ZRVStepperCalculator(opc: AbstractOrbPointsCalculator, ppi: List<PPI>) {
var opc: AbstractOrbPointsCalculator = opc
private var ppiExt = mutableListOf<PPIExt>()
private var indPPI = 0
private var step = 60.0
private val eart = getEarth(opc.earthType)
var ppi = ppi
var zrv = mutableListOf<ZRV>()
/**
* Расчет радиус-вектора КА в пунктовой СК текущего ППИ
* ППИ определяется по индексу _indPPI
*/
private fun gskToPointSK(point: OrbitalPoint): Vector3D {
val r1 =
Vector3D(
point.r.z - ppiExt[indPPI].params.zn,
point.r.x - ppiExt[indPPI].params.xn,
point.r.y - ppiExt[indPPI].params.yn,
)
val r4 = r1.y * ppiExt[indPPI].params.cl + r1.z * ppiExt[indPPI].params.sl
return Vector3D(
ppiExt[indPPI].params.cb * r1.x - ppiExt[indPPI].params.sb * r4,
ppiExt[indPPI].params.sb * r1.x + ppiExt[indPPI].params.cb * r4,
ppiExt[indPPI].params.cl * r1.z - ppiExt[indPPI].params.sl * r1.y,
)
}
/**
* Расчет параметров видимости КА-ППИ для заданного положения КА
* ППИ определяется по индексу _indPPI
*/
private fun calculateViewParams(point: OrbitalPoint) {
ppiExt[indPPI].t2 = point.t
val r1 = point.v.x * ppiExt[indPPI].params.cl + point.v.y * ppiExt[indPPI].params.sl
ppiExt[indPPI].vy2 = point.v.z * ppiExt[indPPI].params.sb + r1 * ppiExt[indPPI].params.cb
val r = gskToPointSK(point)
ppiExt[indPPI].gam2 = atan2(r.y, sqrt(r.x * r.x + r.z * r.z))
ppiExt[indPPI].kaY = r.y
ppiExt[indPPI].a2 = atan2(r.z, r.x)
if (ppiExt[indPPI].a2 < 0) {
ppiExt[indPPI].a2 += 2 * PI
}
}
/**
* Расчет зон радио-видимости на зааднном интервале времени
*/
fun calculate(
tn: Double,
tk: Double,
): BallisticsError {
zrv.clear()
if (ppi.isEmpty()) return BallisticsError.EMPTY_PPI_LIST
var gam1 : Double
var gam2 : Double
step = 60.0
val eqc = EquationCalculator2Div()
val minmax = MinMaxGoldenSectCalculator()
minmax.sign = -1
minmax.delta = 0.0001
eqc.delta = 0.001 * PI / 180.0
eqc.maxIterations = 50
ppiExt.clear()
for (p in ppi) {
ppiExt.add(
PPIExt().apply {
params.nip = p.ppiNum
params.au = p.auNum
params.sb = sin(p.lat)
params.cb = cos(p.lat)
params.sl = sin(p.long)
params.cl = cos(p.long)
elevMax = p.elevMax
elevMin = p.elevMin
val r = eart.blh2xyz(p.lat, p.long, p.height)
params.xn = r.x
params.yn = r.y
params.zn = r.z
params.bn = p.lat
shadowMin = p.shadowMin
shadowMax = p.shadowMax
// !!!
},
)
}
val stepper = opc.getStepper()
val cnt = ppiExt.count()
var ct = tn
// первая точка интервала расчета
var point = stepper.calculate(tn)
if (point == null) {
return BallisticsError.STEPPER_ERROR
}
for (i in 0..cnt - 1) {
indPPI = i
calculateViewParams(point)
if (ppiExt[i].gam2 > ppiExt[i].elevMin) {
ppiExt[i].zoneIn.t = point.t
ppiExt[i].vit = point.vit
}
}
ct += step
while (ct < tk) {
point = stepper.calculate(ct)
if (point == null) {
return BallisticsError.STEPPER_ERROR
}
for (i in 0..cnt - 1) {
indPPI = i
ppiExt[i].gam1 = ppiExt[i].gam2
ppiExt[i].t1 = ppiExt[i].t2
ppiExt[i].vy1 = ppiExt[i].vy2
ppiExt[i].a1 = ppiExt[i].a2
calculateViewParams(point)
gam1 = ppiExt[i].gam1
gam2 = ppiExt[i].gam2
// начало зоны
if (((gam1 < ppiExt[i].elevMin) && (gam2 > ppiExt[i].elevMin)) ||
(gam1 > ppiExt[i].elevMin && gam2 > ppiExt[i].elevMin && ppiExt[i].vit == 0)
) {
eqc.value = ppiExt[i].elevMin
var t = eqc.calculate(ppiExt[i].t1, ppiExt[i].t2, this::elevationAngle)
if (t != null) {
var buf = calcZoneParams(t)
var it = 0
while (buf.elevation <= ppiExt[i].elevMin && it < 15) {
t += 0.005
buf = calcZoneParams(t)
++it
}
ppiExt[i].zoneIn.t = t
ppiExt[i].vit = point.vit
} else {
println(
"""
Ошибка поиска момента входа в ЗРВ.
${ppiExt[i].gam1 * 180 / PI} - ${ppiExt[i].elevMin * 180 / PI} - ${ppiExt[i].gam2 * 180 / PI}
""".trimIndent(),
)
ppiExt[i].zoneIn.t = ppiExt[i].t2
}
}
// / максимум!
if ((ppiExt[i].vy1 * ppiExt[i].vy2 < 0) &&
(ppiExt[i].gam1 > ppiExt[i].elevMin) &&
(ppiExt[i].kaY > 0)
) {
val tmax = minmax.calculate(ppiExt[i].t1, ppiExt[i].t2, this::elevationAngle)
if (tmax == null) {
println("Ошибка при поиске максимума ЗРВ")
ppiExt[i].zoneMax = calcZoneParams((ppiExt[i].t1 + ppiExt[i].t2) / 2)
} else {
ppiExt[i].zoneMax = calcZoneParams(tmax)
}
}
// конец зоны
if ((
(ppiExt[i].gam1 > ppiExt[i].elevMin) &&
(gam2 < ppiExt[i].elevMin && ppiExt[i].vit != 0)
) ||
(gam1 < ppiExt[i].elevMin && gam2 < ppiExt[i].elevMin && ppiExt[i].vit != 0)
) {
eqc.value = ppiExt[i].elevMin
var t = eqc.calculate(ppiExt[i].t1, ppiExt[i].t2, this::elevationAngle)
if (t != null) {
var buf = calcZoneParams(t)
var it = 0
while (buf.elevation <= ppiExt[i].elevMin && it < 15) {
t -= 0.005
buf = calcZoneParams(t)
++it
}
// ppiExt[i].zoneOut.t = if (buf.elevation<=ppiExt[i].elevMin) t!! else t!! - 0.1
ppiExt[i].zoneOut.t = t
} else {
println("Ошибка поиска момента выхода в ЗРВ.")
ppiExt[i].zoneOut.t = ppiExt[i].t1
}
if (ppiExt[i].zoneOut.t * ppiExt[i].zoneIn.t > 1) {
addZone(i)
} else {
println(
"Ошибка расчета граничной точки зоны ${ppiExt[i].params.nip} ${ppiExt[i].zoneIn.t} ${
ppiExt[i].zoneOut.t
} ",
)
}
ppiExt[i].vit = 0
}
}
ct += step
}
return BallisticsError.OK
}
private fun addZone(ind: Int) {
// разбиение зоны при наличии затенений
if (ppiExt[ind].elevMax < ppiExt[ind].zoneMax.elevation ||
(ppiExt[ind].shadowMin != null && ppiExt[ind].shadowMin!!.toList().isNotEmpty()) ||
(ppiExt[ind].shadowMax != null && ppiExt[ind].shadowMax!!.toList().isNotEmpty())
) {
var tn: Double? = null
var inZone = false
var inZoneNext = false
var vzp = calcZoneParams(ppiExt[ind].zoneIn.t)
if (ppiExt[ind].isVisible(vzp.azimuth, vzp.elevation)) {
tn = ppiExt[ind].zoneIn.t
inZone = true
}
val step = 0.1
var t = ppiExt[ind].zoneIn.t + step
while (t <= ppiExt[ind].zoneOut.t) {
vzp = calcZoneParams(t)
inZoneNext = ppiExt[ind].isVisible(vzp.azimuth, vzp.elevation)
// конец видимости
if (inZone && !inZoneNext) {
val z =
ZRV(
ppiExt[ind].params.nip,
ppiExt[ind].params.au,
ppiExt[ind].vit,
calcZoneParams(tn!!),
VisibilityParametersZRV(
ppiExt[ind].zoneMax.t,
ppiExt[ind].zoneMax.range,
ppiExt[ind].zoneMax.azimuth,
ppiExt[ind].zoneMax.elevation,
),
calcZoneParams(t - step),
)
zrv.add(z)
}
// начало видимости
if (!inZone && inZoneNext) {
tn = t
}
inZone = inZoneNext
t += step
}
if (inZone && inZoneNext) {
val z =
ZRV(
ppiExt[ind].params.nip,
ppiExt[ind].params.au,
ppiExt[ind].vit,
calcZoneParams(tn!!),
VisibilityParametersZRV(
ppiExt[ind].zoneMax.t,
ppiExt[ind].zoneMax.range,
ppiExt[ind].zoneMax.azimuth,
ppiExt[ind].zoneMax.elevation,
),
calcZoneParams(ppiExt[ind].zoneOut.t),
)
zrv.add(z)
}
} else {
val z =
ZRV(
ppiExt[ind].params.nip,
ppiExt[ind].params.au,
ppiExt[ind].vit,
calcZoneParams(ppiExt[ind].zoneIn.t),
calcZoneParams(ppiExt[ind].zoneMax.t),
calcZoneParams(ppiExt[ind].zoneOut.t),
)
// z.zoneIn.elevation = ppiExt[ind].elevMin
// z.zoneOut.elevation = ppiExt[ind].elevMin
zrv.add(z)
}
}
fun clear(){
opc.clear()
zrv.clear()
}
private fun calcZoneParams(t: Double): VisibilityParametersZRV {
val z = VisibilityParametersZRV()
val stepper = opc.getStepper()
val point: OrbitalPoint? = stepper.calculate(t) ?: return z
val r = gskToPointSK(point!!)
z.t = t
z.elevation = atan(r.y / sqrt(r.x * r.x + r.z * r.z))
z.range = r.module()
z.azimuth = atan2(r.z, r.x)
if (z.azimuth < 0) {
z.azimuth += 2 * PI
}
return z
}
private fun elevationAngle(t: Double): Double {
val stepper = opc.getStepper()
val point = stepper.calculate(t)
val r = gskToPointSK(point!!)
return atan(r.y / sqrt(r.z * r.z + r.x * r.x))
}
}
@@ -0,0 +1,363 @@
package ballistics.zrv
import ballistics.orbitalPoints.AbstractOrbPointsCalculator
import ballistics.types.*
import ballistics.utils.earth.getEarth
import ballistics.utils.math.Vector3D
import ballistics.utils.math.equations.EquationCalculator2Div
import ballistics.utils.math.minmax.MinMaxGoldenSectCalculator
import reactor.core.publisher.Flux
import reactor.core.publisher.FluxSink
import java.io.IOException
import kotlin.math.PI
import kotlin.math.atan
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
internal class ZRVStepperCalculatorAsync(
private val opc: AbstractOrbPointsCalculator,
ppi: List<PPI>,
) {
private val ppiExt: List<PPIExt>
private var step = 60.0
private val earth = getEarth(opc.earthType)
init {
ppiExt =
ppi.map { p ->
PPIExt().apply {
params.nip = p.ppiNum
params.au = p.auNum
params.sb = sin(p.lat)
params.cb = cos(p.lat)
params.sl = sin(p.long)
params.cl = cos(p.long)
elevMax = p.elevMax
elevMin = p.elevMin
val r = earth.blh2xyz(p.lat, p.long, p.height)
params.xn = r.x
params.yn = r.y
params.zn = r.z
params.bn = p.lat
shadowMin = p.shadowMin
shadowMax = p.shadowMax
}
}
}
private fun gskToPointSK(
point: OrbitalPoint,
ppi: PPIExt,
): Vector3D {
val r1 =
Vector3D(
point.r.z - ppi.params.zn,
point.r.x - ppi.params.xn,
point.r.y - ppi.params.yn,
)
val r4 = r1.y * ppi.params.cl + r1.z * ppi.params.sl
return Vector3D(
ppi.params.cb * r1.x - ppi.params.sb * r4,
ppi.params.sb * r1.x + ppi.params.cb * r4,
ppi.params.cl * r1.z - ppi.params.sl * r1.y,
)
}
private fun elevationAngle(
t: Double,
ppi: PPIExt,
): Double {
val stepper = opc.getStepper()
val point = stepper.calculate(t)
val r = gskToPointSK(point!!, ppi)
return atan(r.y / sqrt(r.z * r.z + r.x * r.x))
}
private fun calcZoneParams(
t: Double,
ppi: PPIExt,
): VisibilityParametersZRV {
val z = VisibilityParametersZRV()
val stepper = opc.getStepper()
val point: OrbitalPoint = stepper.calculate(t) ?: return z
val r = gskToPointSK(point, ppi)
z.t = t
z.elevation = atan(r.y / sqrt(r.x * r.x + r.z * r.z))
z.range = r.module()
z.azimuth = atan2(r.z, r.x)
if (z.azimuth < 0) {
z.azimuth += 2 * PI
}
return z
}
private fun checkZone(ppi : PPIExt) =
(ppi.elevMax < ppi.zoneMax.elevation) ||
((ppi.shadowMin?.count() ?: 0) != 0) ||
((ppi.shadowMax?.count() ?: 0) != 0)
private fun parceZone(
sink: FluxSink<ZRV>,
ppi: PPIExt
){
var tn: Double? = null
var inZone = false
var inZoneNext = false
var vzp = calcZoneParams(ppi.zoneIn.t, ppi)
if (ppi.isVisible(vzp.azimuth, vzp.elevation)) {
tn = ppi.zoneIn.t
inZone = true
}
val step = 0.1
var t = ppi.zoneIn.t + step
while (t <= ppi.zoneOut.t) {
vzp = calcZoneParams(t, ppi)
inZoneNext = ppi.isVisible(vzp.azimuth, vzp.elevation)
// конец видимости
if (inZone && !inZoneNext) {
sink.next(
ZRV(
ppi.params.nip,
ppi.params.au,
ppi.vit,
calcZoneParams(tn!!, ppi),
VisibilityParametersZRV(ppi.zoneMax.t, ppi.zoneMax.range, ppi.zoneMax.azimuth, ppi.zoneMax.elevation),
calcZoneParams(t - step, ppi),
),
)
}
// начало видимости
if (!inZone && inZoneNext) {
tn = t
}
inZone = inZoneNext
t += step
}
if (inZone && inZoneNext) {
sink.next(
ZRV(
ppi.params.nip,
ppi.params.au,
ppi.vit,
calcZoneParams(tn!!, ppi),
VisibilityParametersZRV(ppi.zoneMax.t, ppi.zoneMax.range, ppi.zoneMax.azimuth, ppi.zoneMax.elevation),
calcZoneParams(ppi.zoneOut.t, ppi),
),
)
}
}
private fun addZone(
sink: FluxSink<ZRV>,
ppi: PPIExt,
) {
// разбиение зоны при наличии затенений
if ( checkZone(ppi) ) {
parceZone(sink, ppi)
} else {
sink.next(
ZRV(
ppi.params.nip,
ppi.params.au,
ppi.vit,
calcZoneParams(ppi.zoneIn.t, ppi),
calcZoneParams(ppi.zoneMax.t, ppi),
calcZoneParams(ppi.zoneOut.t, ppi),
),
)
}
}
private fun calculateViewParams(
point: OrbitalPoint,
ppi: PPIExt,
) {
ppi.t2 = point.t
val r1 = point.v.x * ppi.params.cl + point.v.y * ppi.params.sl
ppi.vy2 = point.v.z * ppi.params.sb + r1 * ppi.params.cb
val r = gskToPointSK(point, ppi)
ppi.gam2 = atan2(r.y, sqrt(r.x * r.x + r.z * r.z))
ppi.kaY = r.y
ppi.a2 = atan2(r.z, r.x)
if (ppi.a2 < 0) {
ppi.a2 += 2 * PI
}
}
fun calculate(
tn: Double,
tk: Double,
): Flux<ZRV> {
if (ppiExt.isEmpty()) {
return Flux.error(IOException()) // replace with custom exception
}
var gam1 : Double
var gam2 : Double
step = 60.0
val eqc = EquationCalculator2Div()
val minmax = MinMaxGoldenSectCalculator()
minmax.sign = -1
minmax.delta = 0.0001
eqc.delta = 0.001 * PI / 180.0
eqc.maxIterations = 50
val stepper = opc.getStepper()
var ct = tn
return Flux.create { sink ->
// первая точка интервала расчета
var point = stepper.calculate(tn) ?: return@create sink.error(IOException())
ppiExt.forEach {
calculateViewParams(point, it)
if (it.gam2 > it.elevMin) {
it.zoneIn.t = point.t
it.vit = point.vit
}
}
ct += step
while (ct < tk) {
point = stepper.calculate(ct) ?: return@create sink.error(IOException())
ppiExt.forEach { innerPpiLoop ->
innerPpiLoop.gam1 = innerPpiLoop.gam2
innerPpiLoop.t1 = innerPpiLoop.t2
innerPpiLoop.vy1 = innerPpiLoop.vy2
innerPpiLoop.a1 = innerPpiLoop.a2
calculateViewParams(point, innerPpiLoop)
gam1 = innerPpiLoop.gam1
gam2 = innerPpiLoop.gam2
// начало зоны
if (((gam1 < innerPpiLoop.elevMin) && (gam2 > innerPpiLoop.elevMin)) ||
(gam1 > innerPpiLoop.elevMin && gam2 > innerPpiLoop.elevMin && innerPpiLoop.vit == 0)
) {
eqc.value = innerPpiLoop.elevMin
var t =
eqc.calculate(innerPpiLoop.t1, innerPpiLoop.t2) {
elevationAngle(it, innerPpiLoop)
}
if (t != null) {
var buf = calcZoneParams(t, innerPpiLoop)
var it = 0
while (buf.elevation <= innerPpiLoop.elevMin && it < 15) {
t += 0.005
buf = calcZoneParams(t, innerPpiLoop)
++it
}
innerPpiLoop.zoneIn.t = t
innerPpiLoop.vit = point.vit
} else {
println(
"""
Ошибка поиска момента входа в ЗРВ.
${innerPpiLoop.gam1 * 180 / PI} - ${innerPpiLoop.elevMin * 180 / PI} - ${innerPpiLoop.gam2 * 180 / PI}
""".trimIndent(),
)
innerPpiLoop.zoneIn.t = innerPpiLoop.t2
}
}
// / максимум!
if ((innerPpiLoop.vy1 * innerPpiLoop.vy2 < 0) &&
(innerPpiLoop.gam1 > innerPpiLoop.elevMin) &&
(innerPpiLoop.kaY > 0)
) {
val tmax =
minmax.calculate(innerPpiLoop.t1, innerPpiLoop.t2) {
elevationAngle(it, innerPpiLoop)
}
if (tmax == null) {
println("Ошибка при поиске максимума ЗРВ")
innerPpiLoop.zoneMax = calcZoneParams((innerPpiLoop.t1 + innerPpiLoop.t2) / 2, innerPpiLoop)
} else {
innerPpiLoop.zoneMax = calcZoneParams(tmax, innerPpiLoop)
}
}
// конец зоны
if ((
(innerPpiLoop.gam1 > innerPpiLoop.elevMin) &&
(gam2 < innerPpiLoop.elevMin && innerPpiLoop.vit != 0)
) ||
(
gam1 < innerPpiLoop.elevMin && gam2 < innerPpiLoop.elevMin &&
innerPpiLoop.vit != 0
)
) {
eqc.value = innerPpiLoop.elevMin
var t =
eqc.calculate(innerPpiLoop.t1, innerPpiLoop.t2) {
elevationAngle(it, innerPpiLoop)
}
if (t != null) {
var buf = calcZoneParams(t, innerPpiLoop)
var it = 0
while (buf.elevation <= innerPpiLoop.elevMin && it < 15) {
t -= 0.005
buf = calcZoneParams(t, innerPpiLoop)
++it
}
innerPpiLoop.zoneOut.t = t
} else {
println("Ошибка поиска момента выхода в ЗРВ.")
innerPpiLoop.zoneOut.t = innerPpiLoop.t1
}
if (innerPpiLoop.zoneOut.t * innerPpiLoop.zoneIn.t > 1) {
addZone(sink, innerPpiLoop)
} else {
println(
"Ошибка расчета граничной точки зоны ${innerPpiLoop.params.nip} ${innerPpiLoop.zoneIn.t} ${
innerPpiLoop.zoneOut.t
} ",
)
}
innerPpiLoop.vit = 0
}
}
ct += step
}
sink.complete()
}
}
}
+165
View File
@@ -0,0 +1,165 @@
import ballistics.Ballistics
import ballistics.types.InitialConditions
import ballistics.types.IntegrationType
import ballistics.types.ModDVType
import ballistics.types.OrbitalPoint
import ballistics.utils.fromDateTime
import ballistics.utils.math.Vector3D
import ballistics.utils.toDateTime
import java.time.LocalDateTime
import java.time.ZoneOffset
fun main() {
var ts = System.currentTimeMillis()
val r = Ballistics()
r.modDVType = ModDVType.BARS // BARS
r.integrationType = IntegrationType.ADAMS7
val mnu = mutableListOf<InitialConditions>()
mnu.add(
InitialConditions(
OrbitalPoint(
LocalDateTime.of(2021, 12, 3, 0, 5, 45, 0).toEpochSecond(ZoneOffset.UTC).toDouble(),
1,
Vector3D(
-5357933.7872,
4328646.1787,
0.0,
),
Vector3D(
941.8995373,
1150.6919822,
7544.9920840,
),
),
0.0061,
125.0,
),
)
/*
mnu.add(InitialConditions(OrbitalPoint(1681135417.0,
1499,
6782429.138044466,
-1205586.994919729,
0.0,
-272.89561674108205,
-1460.1016031205972,
7539.209413654664),
0.0,
100.0)
)
mnu.add(InitialConditions(OrbitalPoint(1681203547.0,
1511,
2796871.797932365,
6295220.245166648,
0.0,
1351.432690993002,
-615.1950685524682,
7539.420044409451),
0.0,
100.0)
)
mnu.add(InitialConditions(OrbitalPoint(1681288710.0,
1526,
2219522.8066600505,
6520960.574145492,
0.0,
1401.0525909402313,
-491.90052480161194,
7539.654233369637),
0.0,
100.0)
)
mnu.add(InitialConditions(OrbitalPoint(1681373873.0,
1541,
1624190.3979707498,
6693858.330353258,
0.0,
1439.4621272728175,
-364.5959436771924,
7539.927000039702),
0.0,
100.0)
)
mnu.add(InitialConditions(OrbitalPoint(1681464714.0,
1557,
3663882.4989639986,
5832354.67692114,
0.0,
1249.158953470858,
-802.9876366520331,
7540.375561571017),
0.0,
100.0)
)
mnu.add(InitialConditions(OrbitalPoint(1681538522.0,
1570,
-2393483.7360840607,
6458206.838182452,
0.0,
1398.0568057512764,
501.055477122656,
7540.680958625912),
0.0,
100.0)
)
*/
mnu.sortBy { it.point.t }
// r.calculateOrbPoints(mnu.toTypedArray(), mnu.first().point.t, mnu.last().point.t+86400.0 * 5 )
r.calculateOrbPoints(mnu.toTypedArray(), mnu.first().point.t, mnu.first().point.t + 86400.0 * 5)
println("время расчета = ${System.currentTimeMillis() - ts}мс")
// for (p in r.points)
// println("${p.vit} ${LocalDateTime.ofEpochSecond(p.t.toLong(),0, ZoneOffset.UTC)} ${p.r.x} ${p.r.y} ${p.r.z} ${p.v.x} ${p.v.y} ${p.v.z}")
//
ts = System.currentTimeMillis()
r.calculateFlightLine(mnu.first().point.t, mnu.first().point.t + 86400.0 * 5)
println("время расчета = ${System.currentTimeMillis() - ts}мс")
// var p = r.points.last()
// println("${p.vit} ${LocalDateTime.ofEpochSecond(p.t.toLong(),0, ZoneOffset.UTC)} ${p.x} ${p.y} ${p.z} ${p.vx} ${p.vy} ${p.vz}")
//
// var st = r.getStepper()
// st?.let{
// var p = it.calculate(nu.point.t + 120.0)//452.4)
// p?.let {
// println(
// "${p.vit} ${
// LocalDateTime.ofEpochSecond(
// p.t.toLong(),
// 0,
// ZoneOffset.UTC
// )
// } ${p.x} ${p.y} ${p.z} ${p.vx} ${p.vy} ${p.vz}"
// )
// }
// } ?: println("is null")
//
// //
// // val ts2 = System.currentTimeMillis()
// var ppi = mutableListOf<PPI>(PPI(1000, 0, 0.36617007706841037, 1.8417344524829609,
// 0.0, 0.08726646259971647, 1.5707963267948966))
// try{
// r.calculateZRV(ppi, mnu.first().point.t, mnu.last().point.t+86400.0 * 5)
// println(r.zrv.size)
// }
// catch(e : Exception){
// println("err")
// }
// println("время расчета = ${System.currentTimeMillis()-ts}мс ЗРВ : ${r.zrv.size}")
}
@@ -0,0 +1,86 @@
package ballistics
import ballistics.orbitalPoints.timeStepper.TLEStepper
import ballistics.types.BallisticsError
import ballistics.types.EarthType
import ballistics.types.InitialConditions
import ballistics.types.OPKatObj
import ballistics.types.TLE
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.time.LocalDateTime
import java.time.ZoneOffset
import kotlin.math.PI
internal class BallisticsTest {
@Test
fun parseTLE() {
var s1 = "1 51824U 22019A 22058.83400146 .00001503 00000-0 10000-3 0 9990"
var s2 = "2 51824 97.5213 135.6262 0019439 268.1769 91.7238 15.08515864 114"
var s = TLEStepper(s1, s2, EarthType.PZ90d02)
var r = s.baseEpoch
println("${ LocalDateTime.ofEpochSecond(r.toLong(),(r % 1 * 1e9).toInt(), ZoneOffset.UTC)} ")
var p = s.calculate(r)
println(
"${p.vit} ${ p.let { LocalDateTime.ofEpochSecond(p.t.toLong(),(p.t % 1 * 1e9).toInt(), ZoneOffset.UTC)} } " +
"${p.r.x} " +
" ${p.r.y} " +
"${p.r.z} " +
" ${p.v.x} " +
"${p.v.y} " +
" ${p.v.z}",
)
}
@Test
fun testTLE() {
val tle =
// TLE(
// "1 51824U 22019A 22058.83400146 .00001503 00000-0 10000-3 0 9990",
// "2 51824 97.5213 135.6262 0019439 268.1769 91.7238 15.08515864 114",
// )
TLE(
"1 45621U 11037Q 22018.21004500 .07428171 00000-0 19069+0 0 9999",
"2 45621 51.4607 277.9348 0211635 34.9395 326.5259 15.14655454 76661",
)
val bal1 = Ballistics()
val bal2 = Ballistics()
bal1.sunAngleMin = -90 * PI / 180
bal2.sunAngleMin = -90 * PI / 180
val op = bal1.parseTLE(tle)
val tn = op.t
val tk = tn + 86400 * 2
val r = bal1.calculateOrbPoints(tle, tn, tk)
assertTrue(r == BallisticsError.OK)
bal2.calculateOrbPoints(InitialConditions(op, 0.0, 0.0), tn, tk)
bal1.calculateFlightLine(tn, tk)
bal2.calculateFlightLine(tn, tk)
val objs = listOf<OPKatObj>(OPKatObj(1, 1, "t",1, 60 * PI / 180, 20 * PI / 180, 100.0, 0.0, 90.0, -90.0))
bal1.calculateMPL(tn, tk, objs)
bal2.calculateMPL(tn, tk, objs)
println("${bal1.mpl.count()} - ${bal2.mpl.count()}")
assertEquals(bal1.mpl.count(), bal2.mpl.count())
if (bal1.mpl.toList().isNotEmpty()) {
val v1 = bal1.mpl.first()
val v2 = bal2.mpl.first()
assertTrue(v1.traverz - v2.traverz < 60)
}
}
}
@@ -0,0 +1,134 @@
package ballistics.flightLine
import ballistics.Ballistics
import ballistics.types.BallisticsError
import ballistics.types.EarthType
import ballistics.types.InitialConditions
import ballistics.types.IntegrationType
import ballistics.types.ModDVType
import ballistics.types.OrbitalPoint
import ballistics.types.WorkCSType
import ballistics.utils.math.Vector3D
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.time.LocalDateTime
import java.time.ZoneOffset
import kotlin.math.PI
internal class FlightLineCalculatorTest {
@Test
fun testFL() {
var r = Ballistics()
r.earthType = EarthType.PZ90d02
r.modDVType = ModDVType.FOTO
r.integrationType = IntegrationType.ADAMS7
r.workCoordinateSystem = WorkCSType.WCSOrbit
r.rollMax = 30 * PI / 180
var mnu = mutableListOf<InitialConditions>()
var tt1 = LocalDateTime.of(2023, 6, 20, 4, 18, 40, 180000000)
var tt2 = LocalDateTime.of(2023, 6, 19, 3, 17, 49, 251000000)
mnu.add(
InitialConditions(
OrbitalPoint(
tt1.toEpochSecond(ZoneOffset.UTC).toDouble(), // 1687238300.0,
18721,
Vector3D(
-6603039.949152646,
-1870023.1481177735,
0.0,
),
Vector3D(
-401.5289935173786,
1413.4309568106378,
7547.69556621523,
),
),
0.014542,
125.0,
),
)
mnu.add(
InitialConditions(
OrbitalPoint(
tt2.toEpochSecond(ZoneOffset.UTC).toDouble(), // 1687238300.0,
18705,
Vector3D(
-5880984.961878717,
-3535801.795084351,
0.0,
),
Vector3D(
-759.5481166352387,
1257.702405545223,
7548.463786464257,
),
),
0.014542,
125.0,
),
)
mnu.sortBy { it.point.t }
var t = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)
var rez = r.calculateOrbPoints(mnu.toTypedArray(), mnu.first().point.t, mnu.first().point.t + 86400.0 * 3)
var t2 = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)
println("расчет точек орибты за ${t2 - t} с")
// for (fl in r.points)
// println("${fl.vit} ${LocalDateTime.ofEpochSecond(fl.t.toLong(),0, ZoneOffset.UTC)} ${fl.r}")
if (rez == BallisticsError.OK) {
t = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)
var rezfl = r.calculateFlightLine(mnu.first().point.t, mnu.first().point.t + 86400.0 * 3)
t2 = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)
println("расчет ТП и ПО за ${t2 - t} с")
for (fl in r.flightLine) {
println(
"${fl.vit} ${LocalDateTime.ofEpochSecond(fl.t.toLong(),0, ZoneOffset.UTC)} " +
" ${fl.leftOuterSwath.lat * 180.0 / PI} ${fl.leftOuterSwath.long * 180 / PI} " +
" ${fl.flightLine.lat * 180.0 / PI} ${fl.flightLine.long * 180 / PI} " +
" ${fl.rightOuterSwath.lat * 180.0 / PI} ${fl.rightOuterSwath.long * 180 / PI}",
)
}
// assertEquals(/* expected = */BallisticsError.OK, /* actual = */rezfl )
}
/*var opKatObj = mutableListOf<OPKatObj>()
var b = -PI/ 2
var l = 0.0
while (b <= PI /2){
while (l <= PI * 2){
opKatObj.add(OPKatObj(1,1,1,b,l,0.0,0.0,PI/2,0.0))
l += 0.2 * PI / 180
}
b += 0.2 * PI / 180
l = 0.0
}
println(opKatObj.count())
t = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)
rez= r.calculateMPL(mnu.first().point.t, mnu.first().point.t+86400.0 * 3, opKatObj)
t2 = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)
println(r.mpl.count())
println("расчет МПЛ за ${t2-t}")*/
// println("$rez")
// for (t in r.mpl)
// println("${t.vit} ${LocalDateTime.ofEpochSecond(t.traverz.toLong(),(t.traverz %1 * 1e9).toInt(), ZoneOffset.UTC)} ${t.orientation.kren * 180.0 / PI}" +
// " ${t.range} ${t.sunAngle * 180 / PI}")
assertEquals(
// expected =
BallisticsError.OK,
// actual =
rez,
)
}
}
@@ -0,0 +1,115 @@
package ballistics.orbitalPoints
import ballistics.Ballistics
import ballistics.types.*
import ballistics.utils.math.Vector3D
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.time.LocalDateTime
import java.time.ZoneOffset
import kotlin.math.PI
internal class OrbitalPointsIntegratorTest {
@Test
fun testMDBARS() {
val r = Ballistics()
r.earthType = EarthType.PZ90d02
r.modDVType = ModDVType.BARS
r.integrationType = IntegrationType.ADAMS7
val mnu = mutableListOf<InitialConditions>()
mnu.add(
InitialConditions(
OrbitalPoint(
LocalDateTime.of(2021, 12, 3, 0, 5, 45, 0).toEpochSecond(ZoneOffset.UTC).toDouble(),
1,
Vector3D(
-5357933.7872,
4328646.1787,
0.0,
),
Vector3D(
941.8995373,
1150.6919822,
7544.9920840,
),
),
0.0061,
125.0,
),
)
val rez = r.calculateOrbPoints(mnu.toTypedArray(), mnu.first().point.t, mnu.first().point.t + 86400.0 * 3)
for (p in r.revolutions) {
println(
"${p.vuz.vit} ${LocalDateTime.ofEpochSecond(p.vuz.t.toLong(),0, ZoneOffset.UTC)} " +
"${p.lVuz * 180.0 / PI} " +
" ${p.hVuz} " +
"${p.vuz.r.x} " +
" ${p.vuz.r.y} " +
"${p.vuz.r.z} " +
" ${p.vuz.v.x} " +
"${p.vuz.v.y} " +
" ${p.vuz.v.z}",
)
}
val p = r.points.last()
println(
"${p.vit} ${LocalDateTime.ofEpochSecond(
p.t.toLong(),
0,
ZoneOffset.UTC,
)} ${p.r.x} ${p.r.y} ${p.r.z} ${p.v.x} ${p.v.y} ${p.v.z}",
)
val rc = Vector3D(r.points.last().r.x, r.points.last().r.y, r.points.last().r.z)
val vc = Vector3D(r.points.last().v.x, r.points.last().v.y, r.points.last().v.z)
val rex = Vector3D(4723743.916, -4261060.842, -2647685.695)
val vex = Vector3D(-3168.702, 797.721, -6953.781)
val dr = (rc - rex).module()
val dv = (vex - vc).module()
println(dr)
println(dv)
assertEquals(
BallisticsError.OK,
rez,
)
assertTrue(
dr < 0.01,
"Ошибка прогноза по радиус-вектору",
)
assertTrue(dv < 0.01, "ошибка прогноза по вектору скорости")
}
// fun initOrekitData() {
// val dataDir = File("src/main/resources/orekit-data-main") // or absolute path
// if (!dataDir.exists()) {
// throw IllegalStateException("Orekit data directory not found at ${dataDir.absolutePath}")
// }
// val crawler = DirectoryCrawler(dataDir)
// DataProvidersManager.getInstance().addProvider(crawler)
// }
// @Test
// fun orekitTest(){
//
// initOrekitData()
//
// val l1 = "1 00666U 18111A 25349.99139990 .00002772 00000+0 11883-3 0 9999"
// val l2 = "2 43876 97.3687 252.5788 0001999 83.2822 276.8643 15.23576679386900"
//
// val tle = TLE(l1, l2)
// val sat = Satellite(org.nstart.dep265.tletools.zeptomoby.core.TLE("",l1, l2))
//
// assertTrue { sat.orbit.satId.toInt() == tle.satelliteNumber }
// }
}
@@ -0,0 +1,20 @@
package ballistics.types
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
internal class ModDVTypeTest {
/*!
Проветка приведения типов от Int к enum
*/
@Test
fun testMDFoto() {
val md1 = ModDVType.fromInt(1)
val md2 = ModDVType.fromInt(16)
val md3 = ModDVType.fromInt(100)
assertEquals(ModDVType.FOTO, md1)
assertEquals(ModDVType.BARS, md2)
assertEquals(ModDVType.FOTO, md3)
}
}
@@ -0,0 +1,103 @@
package ballistics.zrv
import ballistics.Ballistics
import ballistics.types.BallisticsError
import ballistics.types.EarthType
import ballistics.types.InitialConditions
import ballistics.types.IntegrationType
import ballistics.types.ModDVType
import ballistics.types.OrbitalPoint
import ballistics.types.PPI
import ballistics.types.TLE
import ballistics.utils.math.Vector3D
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.time.LocalDateTime
import java.time.ZoneOffset
import kotlin.math.PI
internal class ZRVStepperCalculatorTest {
@Test
fun calculate() {
val r = Ballistics()
val p = r.getTLEParams(TLE("1 62141U 24224C 25324.24196334 .00027917 00000-0 10193-2 0 9998",
"2 62141 42.9988 56.0174 0001022 26.8436 333.2469 15.27609213 55582"), "test")
r.earthType = EarthType.PZ90d02
r.modDVType = ModDVType.BARS
r.integrationType = IntegrationType.ADAMS7
val mnu = mutableListOf<InitialConditions>()
mnu.add(
InitialConditions(
OrbitalPoint(
LocalDateTime.of(2021, 12, 3, 0, 5, 45, 0).toEpochSecond(ZoneOffset.UTC).toDouble(),
1,
Vector3D(
-5357933.7872,
4328646.1787,
0.0,
),
Vector3D(
941.8995373,
1150.6919822,
7544.9920840,
),
),
0.0061,
125.0,
),
)
var rez = r.calculateOrbPoints(mnu.toTypedArray(), mnu.first().point.t, mnu.first().point.t + 86400.0 * 3)
if (rez == BallisticsError.OK) {
rez =
r.calculateZRV(
listOf(
PPI(
1,
1,
55.15 * PI / 180,
38.39 * PI / 180,
0.0,
5 * PI / 180,
80 * PI / 180,
// shadowMin = listOf(
// ShadowAU(110.0 * PI / 180, 150 * PI / 180, 25 * PI / 180),
// ShadowAU(175.0 * PI / 180, 183 * PI / 180, 90 * PI / 180),
// ),
// shadowMax = listOf(
// ShadowAU(189.0 * PI / 180, 192 * PI / 180, 0 * PI / 180),
// )
),
),
mnu.first().point.t,
mnu.first().point.t + 86400.0 * 3,
)
if (rez == BallisticsError.OK) {
for (z in r.zrv)
println(
" ${z.vit} \t" +
" ${z.au} \t" +
" ${LocalDateTime.ofEpochSecond(z.zoneIn.t.toLong(), 0, ZoneOffset.UTC)} \t" +
" ${z.zoneIn.azimuth * 180 / PI} \t" +
" ${z.zoneIn.elevation * 180 / PI} \t" +
" ${LocalDateTime.ofEpochSecond(z.zoneOut.t.toLong(), 0, ZoneOffset.UTC)} \t" +
" ${z.zoneOut.azimuth * 180 / PI} \t" +
" ${z.zoneOut.elevation * 180 / PI} \t" +
" ${LocalDateTime.ofEpochSecond(z.zoneMax.t.toLong(), 0, ZoneOffset.UTC)} \t" +
" ${z.zoneMax.azimuth * 180 / PI} \t" +
" ${z.zoneMax.elevation * 180 / PI} \t",
)
}
}
assertTrue((rez == BallisticsError.OK))
}
}