Расчет витков по TLE
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
|||||||
|
package space.nstart.pcp.pcp_types_lib.dto.ballistics
|
||||||
|
|
||||||
|
class TleRevolutionsRequestDTO(
|
||||||
|
val tle: TLEDTO = TLEDTO(),
|
||||||
|
val days: Double = 1.0
|
||||||
|
)
|
||||||
+5
-1
@@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
import space.nstart.pcp.pcp_request_service.service.TLEService
|
import space.nstart.pcp.pcp_request_service.service.TLEService
|
||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
|
||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TlePointRequestDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TlePointRequestDTO
|
||||||
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TleRevolutionsRequestDTO
|
||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TleRvaRequestDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TleRvaRequestDTO
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -28,4 +29,7 @@ class TLEController {
|
|||||||
@PostMapping("/rva")
|
@PostMapping("/rva")
|
||||||
fun rva(@RequestBody req : TleRvaRequestDTO) = tleService.rva(req)
|
fun rva(@RequestBody req : TleRvaRequestDTO) = tleService.rva(req)
|
||||||
|
|
||||||
}
|
@PostMapping("/revolutions")
|
||||||
|
fun revolutions(@RequestBody req: TleRevolutionsRequestDTO) = tleService.revolutions(req)
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
+62
-1
@@ -4,7 +4,9 @@ import ballistics.Ballistics
|
|||||||
import ballistics.orbitalPoints.timeStepper.TLEStepper
|
import ballistics.orbitalPoints.timeStepper.TLEStepper
|
||||||
import ballistics.types.BallisticsError
|
import ballistics.types.BallisticsError
|
||||||
import ballistics.types.EarthType
|
import ballistics.types.EarthType
|
||||||
|
import ballistics.types.KeplerParams
|
||||||
import ballistics.types.PPI
|
import ballistics.types.PPI
|
||||||
|
import ballistics.types.RevolutionParameter
|
||||||
import ballistics.types.TLE
|
import ballistics.types.TLE
|
||||||
import ballistics.types.TLEParams
|
import ballistics.types.TLEParams
|
||||||
import ballistics.types.VisibilityParametersZRV
|
import ballistics.types.VisibilityParametersZRV
|
||||||
@@ -14,17 +16,25 @@ import ballistics.utils.toDateTime
|
|||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import space.nstart.pcp.pcp_request_service.configuration.CustomErrorException
|
import space.nstart.pcp.pcp_request_service.configuration.CustomErrorException
|
||||||
import space.nstart.pcp.pcp_request_service.configuration.CustomValidationException
|
import space.nstart.pcp.pcp_request_service.configuration.CustomValidationException
|
||||||
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.AscNodeDTO
|
||||||
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.KeplersDTO
|
||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO
|
||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.RadioVisibilityAreaDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.RadioVisibilityAreaDTO
|
||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
|
||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TlePointRequestDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TlePointRequestDTO
|
||||||
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TleRevolutionsRequestDTO
|
||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TargetPositionDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TargetPositionDTO
|
||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TleRvaRequestDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TleRvaRequestDTO
|
||||||
|
import java.time.LocalDateTime
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class TLEService() {
|
class TLEService() {
|
||||||
|
private companion object {
|
||||||
|
const val SECONDS_IN_DAY = 86_400.0
|
||||||
|
const val PZ90_EQUATORIAL_RADIUS = 6_378_136.0
|
||||||
|
}
|
||||||
|
|
||||||
fun mkTP(p : VisibilityParametersZRV) = TargetPositionDTO(
|
fun mkTP(p : VisibilityParametersZRV) = TargetPositionDTO(
|
||||||
toDateTime(p.t),
|
toDateTime(p.t),
|
||||||
@@ -117,4 +127,55 @@ class TLEService() {
|
|||||||
|
|
||||||
return bal.zrv.map { mkRVA(it, tleParams.noradId) }
|
return bal.zrv.map { mkRVA(it, tleParams.noradId) }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
fun revolutions(req: TleRevolutionsRequestDTO): List<AscNodeDTO> {
|
||||||
|
if (!req.days.isFinite() || req.days <= 0.0) {
|
||||||
|
throw CustomValidationException("Интервал расчета должен быть больше 0 суток")
|
||||||
|
}
|
||||||
|
|
||||||
|
val bal = Ballistics()
|
||||||
|
val tle = TLE(req.tle.first, req.tle.second)
|
||||||
|
try {
|
||||||
|
bal.getTLEParams(tle, req.tle.header ?: "empty")
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
throw CustomValidationException("Ошибка формата TLE : ${ex.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = bal.calculateOrbPoints(tle, req.days * SECONDS_IN_DAY)
|
||||||
|
if (result != BallisticsError.OK) {
|
||||||
|
throw CustomErrorException("Ошибка расчета витков по TLE : $result")
|
||||||
|
}
|
||||||
|
|
||||||
|
return bal.revolutions.map { mkAscNode(it, bal.calculateKeplerParams(it.vuz)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mkAscNode(revolution: RevolutionParameter, keps: KeplerParams) = AscNodeDTO(
|
||||||
|
time = toDateTime(revolution.vuz.t),
|
||||||
|
revolution = revolution.vuz.vit,
|
||||||
|
long = revolution.lVuz * 180 / PI,
|
||||||
|
height = revolution.hVuz,
|
||||||
|
x = revolution.vuz.r.x,
|
||||||
|
y = revolution.vuz.r.y,
|
||||||
|
z = revolution.vuz.r.z,
|
||||||
|
vx = revolution.vuz.v.x,
|
||||||
|
vy = revolution.vuz.v.y,
|
||||||
|
vz = revolution.vuz.v.z,
|
||||||
|
keps = KeplersDTO(
|
||||||
|
(keps.ael - PZ90_EQUATORIAL_RADIUS) / 1000.0,
|
||||||
|
keps.e,
|
||||||
|
keps.nakl * 180 / PI,
|
||||||
|
keps.omegab * 180 / PI,
|
||||||
|
keps.omegam * 180 / PI
|
||||||
|
),
|
||||||
|
localMeanTime = tmest(toDateTime(revolution.vuz.t), revolution.lVuz)
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun tmest(t: LocalDateTime, longitudeRad: Double): LocalDateTime {
|
||||||
|
var longitude = longitudeRad
|
||||||
|
if (longitude > PI) {
|
||||||
|
longitude -= 2 * PI
|
||||||
|
}
|
||||||
|
return t.plusSeconds(-60 * 60 * 3)
|
||||||
|
.plusNanos((longitude / 2.0 / PI * SECONDS_IN_DAY * 1e9).toLong())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
package space.nstart.pcp.pcp_request_service.service
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import space.nstart.pcp.pcp_request_service.configuration.CustomValidationException
|
||||||
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
|
||||||
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TleRevolutionsRequestDTO
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class TLEServiceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `revolutions calculates ascending nodes from tle epoch`() {
|
||||||
|
val service = TLEService()
|
||||||
|
|
||||||
|
val revolutions = service.revolutions(
|
||||||
|
TleRevolutionsRequestDTO(
|
||||||
|
tle = issTle,
|
||||||
|
days = 1.0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val epoch = service.parseTLE(issTle).time
|
||||||
|
assertTrue(revolutions.isNotEmpty())
|
||||||
|
assertTrue(revolutions.zipWithNext().all { (previous, next) -> previous.time < next.time })
|
||||||
|
assertTrue(revolutions.zipWithNext().all { (previous, next) -> previous.revolution < next.revolution })
|
||||||
|
assertTrue(revolutions.all { it.time >= epoch })
|
||||||
|
assertTrue(revolutions.all { it.localMeanTime != null })
|
||||||
|
assertTrue(revolutions.all { it.keps != null })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `revolutions rejects non-positive duration`() {
|
||||||
|
val service = TLEService()
|
||||||
|
|
||||||
|
assertFailsWith<CustomValidationException> {
|
||||||
|
service.revolutions(
|
||||||
|
TleRevolutionsRequestDTO(
|
||||||
|
tle = issTle,
|
||||||
|
days = 0.0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
val issTle = TLEDTO(
|
||||||
|
header = "ISS",
|
||||||
|
first = "1 25994U 99068A 26167.13049375 .00000315 00000-0 73126-4 0 9997",
|
||||||
|
second = "2 25994 97.9457 216.7186 0001501 193.3628 244.8059 14.61102329409455"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -13,6 +13,7 @@ import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteICDTO
|
|||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteOrbitAvailabilityDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteOrbitAvailabilityDTO
|
||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
|
||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TlePointRequestDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TlePointRequestDTO
|
||||||
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TleRevolutionsRequestDTO
|
||||||
import space.nstart.pcp.slots_service.dto.tlecomparison.TleParsedDTO
|
import space.nstart.pcp.slots_service.dto.tlecomparison.TleParsedDTO
|
||||||
import space.nstart.pcp.slots_service.configuration.CustomErrorException
|
import space.nstart.pcp.slots_service.configuration.CustomErrorException
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
@@ -115,6 +116,17 @@ class BallisticsService (webClientBuilderProvider: ObjectProvider<WebClient.Buil
|
|||||||
.block()
|
.block()
|
||||||
?: throw CustomErrorException("Не удалось рассчитать положение по TLE на время $time")
|
?: throw CustomErrorException("Не удалось рассчитать положение по TLE на время $time")
|
||||||
|
|
||||||
|
fun tleRevolutions(tle: TLEDTO, days: Double): List<AscNodeDTO> =
|
||||||
|
webClientBuilder.build()
|
||||||
|
.post()
|
||||||
|
.uri("$url/api/tle/revolutions")
|
||||||
|
.bodyValue(TleRevolutionsRequestDTO(tle = tle, days = days))
|
||||||
|
.retrieve()
|
||||||
|
.bodyToFlux(AscNodeDTO::class.java)
|
||||||
|
.collectList()
|
||||||
|
.block()
|
||||||
|
?: emptyList()
|
||||||
|
|
||||||
private fun withTimeInterval(
|
private fun withTimeInterval(
|
||||||
path: String,
|
path: String,
|
||||||
timeStart: LocalDateTime?,
|
timeStart: LocalDateTime?,
|
||||||
|
|||||||
+20
-2
@@ -11,6 +11,7 @@ import space.nstart.pcp.pcp_types_lib.dto.ballistics.InitialConditionsDTO
|
|||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.MovementModel
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.MovementModel
|
||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO
|
||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteICDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteICDTO
|
||||||
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
@@ -60,6 +61,13 @@ class BallisticsServiceTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
val currentInitialConditions = service.currentInitialConditions(101L)
|
val currentInitialConditions = service.currentInitialConditions(101L)
|
||||||
|
val tleRevolutions = service.tleRevolutions(
|
||||||
|
TLEDTO(
|
||||||
|
first = "1 25994U 99068A 26167.13049375 .00000315 00000-0 73126-4 0 9997",
|
||||||
|
second = "2 25994 97.9457 216.7186 0001501 193.3628 244.8059 14.61102329409455"
|
||||||
|
),
|
||||||
|
days = 1.5
|
||||||
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
listOf(
|
listOf(
|
||||||
@@ -69,7 +77,8 @@ class BallisticsServiceTest {
|
|||||||
"/api/satellites/orbit/availability",
|
"/api/satellites/orbit/availability",
|
||||||
"/api/satellites/101/extract-time",
|
"/api/satellites/101/extract-time",
|
||||||
"/api/satellites/receive-rv",
|
"/api/satellites/receive-rv",
|
||||||
"/api/satellites/101/initial-conditions/current"
|
"/api/satellites/101/initial-conditions/current",
|
||||||
|
"/api/tle/revolutions"
|
||||||
),
|
),
|
||||||
requests.map { it.path }
|
requests.map { it.path }
|
||||||
)
|
)
|
||||||
@@ -81,6 +90,7 @@ class BallisticsServiceTest {
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
null
|
null
|
||||||
),
|
),
|
||||||
requests.map { it.query }
|
requests.map { it.query }
|
||||||
@@ -89,9 +99,12 @@ class BallisticsServiceTest {
|
|||||||
assertEquals(101L, availability.single().satelliteId)
|
assertEquals(101L, availability.single().satelliteId)
|
||||||
assertNotNull(exactPoint)
|
assertNotNull(exactPoint)
|
||||||
assertEquals(timeStart, exactPoint.time)
|
assertEquals(timeStart, exactPoint.time)
|
||||||
assertEquals(true, requestBodies.single().contains(""""movementModel":"KONDOR""""))
|
|
||||||
assertNotNull(currentInitialConditions)
|
assertNotNull(currentInitialConditions)
|
||||||
assertEquals(101L, currentInitialConditions.satelliteId)
|
assertEquals(101L, currentInitialConditions.satelliteId)
|
||||||
|
assertEquals(emptyList(), tleRevolutions)
|
||||||
|
assertEquals(2, requestBodies.size)
|
||||||
|
assertEquals(true, requestBodies.any { it.contains(""""movementModel":"KONDOR"""") })
|
||||||
|
assertEquals(true, requestBodies.any { it.contains(""""days":1.5""") })
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createService(serverUrl: String): BallisticsService {
|
private fun createService(serverUrl: String): BallisticsService {
|
||||||
@@ -164,6 +177,11 @@ class BallisticsServiceTest {
|
|||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
startedServer.createContext("/api/tle/revolutions") { exchange ->
|
||||||
|
requests.add(exchange.requestURI)
|
||||||
|
requestBodies.add(exchange.requestBody.bufferedReader().use { it.readText() })
|
||||||
|
respond(exchange, "[]")
|
||||||
|
}
|
||||||
startedServer.start()
|
startedServer.start()
|
||||||
server = startedServer
|
server = startedServer
|
||||||
return "http://localhost:${startedServer.address.port}"
|
return "http://localhost:${startedServer.address.port}"
|
||||||
|
|||||||
Reference in New Issue
Block a user