исправлен расчет витка для tle
This commit is contained in:
+41
-1
@@ -23,7 +23,7 @@ class SurveyContourCalculator(
|
||||
fun calculate(result: AngularMotionResult, y: Double,z: Double,focus: Double): String {
|
||||
|
||||
|
||||
val sourcePoints = result.points
|
||||
val sourcePoints = decimateContourSourcePoints(result.points)
|
||||
require(sourcePoints.size >= MIN_CONTOUR_POINTS) {
|
||||
"Для построения контура съемки требуется не менее $MIN_CONTOUR_POINTS точек ПУУД"
|
||||
}
|
||||
@@ -62,6 +62,43 @@ class SurveyContourCalculator(
|
||||
return toWkt(shell)
|
||||
}
|
||||
|
||||
|
||||
private fun decimateContourSourcePoints(points: List<AngularMotionPoint>): List<AngularMotionPoint> {
|
||||
if (points.size <= MIN_CONTOUR_POINTS) {
|
||||
return points
|
||||
}
|
||||
|
||||
val startTime = points.first().t
|
||||
val stopTime = points.last().t
|
||||
val duration = stopTime - startTime
|
||||
if (duration <= 0.0) {
|
||||
return listOf(points.first(), points.last()).distinct()
|
||||
}
|
||||
|
||||
val step = maxOf(
|
||||
DEFAULT_CONTOUR_SOURCE_STEP_SEC,
|
||||
duration / (MAX_CONTOUR_BOUNDARY_POINTS - 1),
|
||||
)
|
||||
|
||||
val selected = ArrayList<AngularMotionPoint>(MAX_CONTOUR_BOUNDARY_POINTS)
|
||||
selected += points.first()
|
||||
var nextTime = startTime + step
|
||||
|
||||
for (index in 1 until points.lastIndex) {
|
||||
val point = points[index]
|
||||
if (point.t + TIME_EPS >= nextTime) {
|
||||
selected += point
|
||||
nextTime += step
|
||||
}
|
||||
}
|
||||
|
||||
if (selected.last() !== points.last()) {
|
||||
selected += points.last()
|
||||
}
|
||||
|
||||
return selected
|
||||
}
|
||||
|
||||
private fun toWkt(points: List<SurveyContourPoint>): String {
|
||||
val coordinates = points.joinToString(", ") { point ->
|
||||
String.format(Locale.US, "%.8f %.8f", point.longitudeDeg, point.latitudeDeg)
|
||||
@@ -85,5 +122,8 @@ class SurveyContourCalculator(
|
||||
|
||||
private companion object {
|
||||
const val MIN_CONTOUR_POINTS = 2
|
||||
const val DEFAULT_CONTOUR_SOURCE_STEP_SEC = 2.0
|
||||
const val MAX_CONTOUR_BOUNDARY_POINTS = 10
|
||||
const val TIME_EPS = 1.0e-9
|
||||
}
|
||||
}
|
||||
|
||||
+87
-1
@@ -10,15 +10,21 @@ import org.nstart.dep265.tletools.zeptomoby.orbit.Satellite
|
||||
import java.util.Calendar
|
||||
import java.util.GregorianCalendar
|
||||
import java.util.TimeZone
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.min
|
||||
|
||||
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
|
||||
private val nextAscendingNodeAfterEpoch: Double
|
||||
private val ascendingNodeCache = mutableMapOf<Int, Double>()
|
||||
|
||||
init {
|
||||
baseEpoch = extractUTCMillis(satellite) / 1000.0 + 10800
|
||||
nextAscendingNodeAfterEpoch = findNextAscendingNodeAfterEpoch()
|
||||
ascendingNodeCache[0] = nextAscendingNodeAfterEpoch
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
@@ -68,7 +74,7 @@ class TLEStepper(str1: String, str2: String, earthType: EarthType) : AbstractSte
|
||||
val ask =
|
||||
OrbitalPoint(
|
||||
t,
|
||||
satellite.orbit.orbitNum + (dt / satellite.orbit.period).toInt(),
|
||||
calculateRevolution(t),
|
||||
Vector3D(
|
||||
pos.position.x * 1000.0,
|
||||
pos.position.y * 1000.0,
|
||||
@@ -87,6 +93,86 @@ class TLEStepper(str1: String, str2: String, earthType: EarthType) : AbstractSte
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateRevolution(t: Double): Int {
|
||||
var nodeIndex = floor((t - nextAscendingNodeAfterEpoch) / satellite.orbit.period).toInt()
|
||||
|
||||
while (t < ascendingNode(nodeIndex)) {
|
||||
--nodeIndex
|
||||
}
|
||||
while (t >= ascendingNode(nodeIndex + 1)) {
|
||||
++nodeIndex
|
||||
}
|
||||
|
||||
return satellite.orbit.orbitNum + nodeIndex + 1
|
||||
}
|
||||
|
||||
private fun ascendingNode(index: Int): Double =
|
||||
ascendingNodeCache.getOrPut(index) {
|
||||
val estimatedNode = nextAscendingNodeAfterEpoch + index * satellite.orbit.period
|
||||
findAscendingNodeNear(estimatedNode)
|
||||
}
|
||||
|
||||
private fun findNextAscendingNodeAfterEpoch(): Double {
|
||||
val scanStep = min(60.0, satellite.orbit.period / 64.0)
|
||||
var left = baseEpoch
|
||||
var leftZ = eciZ(left)
|
||||
var right = left + scanStep
|
||||
val scanLimit = baseEpoch + satellite.orbit.period * 2.0
|
||||
|
||||
while (right <= scanLimit) {
|
||||
val rightZ = eciZ(right)
|
||||
if (leftZ < 0.0 && rightZ >= 0.0) {
|
||||
return refineAscendingNode(left, right)
|
||||
}
|
||||
left = right
|
||||
leftZ = rightZ
|
||||
right += scanStep
|
||||
}
|
||||
|
||||
return baseEpoch + satellite.orbit.period
|
||||
}
|
||||
|
||||
private fun findAscendingNodeNear(estimatedNode: Double): Double {
|
||||
val halfPeriod = satellite.orbit.period / 2.0
|
||||
val scanStep = min(60.0, satellite.orbit.period / 64.0)
|
||||
var left = estimatedNode - halfPeriod
|
||||
var leftZ = eciZ(left)
|
||||
var right = left + scanStep
|
||||
val scanLimit = estimatedNode + halfPeriod
|
||||
|
||||
while (right <= scanLimit) {
|
||||
val rightZ = eciZ(right)
|
||||
if (leftZ < 0.0 && rightZ >= 0.0) {
|
||||
return refineAscendingNode(left, right)
|
||||
}
|
||||
left = right
|
||||
leftZ = rightZ
|
||||
right += scanStep
|
||||
}
|
||||
|
||||
return estimatedNode
|
||||
}
|
||||
|
||||
private fun refineAscendingNode(
|
||||
intervalStart: Double,
|
||||
intervalStop: Double,
|
||||
): Double {
|
||||
var left = intervalStart
|
||||
var right = intervalStop
|
||||
repeat(60) {
|
||||
val middle = (left + right) / 2.0
|
||||
if (eciZ(middle) < 0.0) {
|
||||
left = middle
|
||||
} else {
|
||||
right = middle
|
||||
}
|
||||
}
|
||||
return (left + right) / 2.0
|
||||
}
|
||||
|
||||
private fun eciZ(t: Double): Double =
|
||||
satellite.positionEci((t - baseEpoch) / 60.0).position.z
|
||||
|
||||
override fun calculate(
|
||||
t: Double,
|
||||
p: OrbitalPoint,
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package ballistics.orbitalPoints.timeStepper
|
||||
|
||||
import ballistics.types.EarthType
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
internal class TLEStepperTest {
|
||||
@Test
|
||||
fun `calculate keeps TLE revolution at epoch`() {
|
||||
val stepper = TLEStepper(issTleFirst, issTleSecond, EarthType.PZ90d02)
|
||||
|
||||
val point = stepper.calculate(stepper.baseEpoch)
|
||||
|
||||
assertEquals(40945, point.vit)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `calculate increments revolution at ascending node instead of elapsed period from epoch`() {
|
||||
val stepper = TLEStepper(issTleFirst, issTleSecond, EarthType.PZ90d02)
|
||||
|
||||
val firstPass = stepper.calculate(LocalDateTime.of(2026, 6, 16, 14, 19, 50).toEpochSecond(ZoneOffset.UTC).toDouble())
|
||||
val secondPass = stepper.calculate(LocalDateTime.of(2026, 6, 16, 15, 56, 14).toEpochSecond(ZoneOffset.UTC).toDouble())
|
||||
|
||||
assertEquals(firstPass.vit + 1, secondPass.vit)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val issTleFirst = "1 25994U 99068A 26167.13049375 .00000315 00000-0 73126-4 0 9997"
|
||||
const val issTleSecond = "2 25994 97.9457 216.7186 0001501 193.3628 244.8059 14.61102329409455"
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import ballistics.types.OrbitalPoint
|
||||
import ballistics.types.PPI
|
||||
import ballistics.types.TLE
|
||||
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
|
||||
@@ -17,6 +18,49 @@ import java.time.ZoneOffset
|
||||
import kotlin.math.PI
|
||||
|
||||
internal class ZRVStepperCalculatorTest {
|
||||
@Test
|
||||
fun `calculate keeps different revolutions for consecutive TLE visibility zones`() {
|
||||
val ballistics = Ballistics()
|
||||
val tle = TLE(
|
||||
"1 25994U 99068A 26167.13049375 .00000315 00000-0 73126-4 0 9997",
|
||||
"2 25994 97.9457 216.7186 0001501 193.3628 244.8059 14.61102329409455",
|
||||
)
|
||||
val timeStart = LocalDateTime.of(2026, 6, 16, 10, 41, 50, 189_000_000).toEpochSecond(ZoneOffset.UTC).toDouble()
|
||||
val timeStop = LocalDateTime.of(2026, 6, 18, 0, 41, 50, 189_000_000).toEpochSecond(ZoneOffset.UTC).toDouble()
|
||||
|
||||
assertEquals(BallisticsError.OK, ballistics.calculateOrbPoints(tle, timeStart, timeStop))
|
||||
assertEquals(
|
||||
BallisticsError.OK,
|
||||
ballistics.calculateZRV(
|
||||
listOf(
|
||||
PPI(
|
||||
1,
|
||||
0,
|
||||
69.038863 * PI / 180.0,
|
||||
32.861553 * PI / 180.0,
|
||||
124.67,
|
||||
5 * PI / 180.0,
|
||||
90 * PI / 180.0,
|
||||
),
|
||||
),
|
||||
timeStart,
|
||||
timeStop,
|
||||
),
|
||||
)
|
||||
|
||||
val zones = ballistics.zrv.toList()
|
||||
val firstZone = zones.first {
|
||||
it.zoneIn.t >= LocalDateTime.of(2026, 6, 16, 14, 0).toEpochSecond(ZoneOffset.UTC) &&
|
||||
it.zoneIn.t < LocalDateTime.of(2026, 6, 16, 15, 0).toEpochSecond(ZoneOffset.UTC)
|
||||
}
|
||||
val secondZone = zones.first {
|
||||
it.zoneIn.t >= LocalDateTime.of(2026, 6, 16, 15, 0).toEpochSecond(ZoneOffset.UTC) &&
|
||||
it.zoneIn.t < LocalDateTime.of(2026, 6, 16, 17, 0).toEpochSecond(ZoneOffset.UTC)
|
||||
}
|
||||
|
||||
assertEquals(firstZone.vit + 1, secondZone.vit)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun calculate() {
|
||||
val r = Ballistics()
|
||||
|
||||
Reference in New Issue
Block a user