Init
This commit is contained in:
+93
@@ -0,0 +1,93 @@
|
||||
package space.nstart.pcp.pcp_request_service
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.runApplication
|
||||
import org.springframework.context.ApplicationListener
|
||||
import org.springframework.core.env.ConfigurableEnvironment
|
||||
import org.springframework.core.env.EnumerablePropertySource
|
||||
import org.springframework.core.env.PropertySource
|
||||
import org.springframework.kafka.annotation.EnableKafka
|
||||
import org.springframework.scheduling.annotation.EnableAsync
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableAsync
|
||||
@EnableKafka
|
||||
class PcpBallisticsServiceApplication
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
runApplication<PcpBallisticsServiceApplication>(*args) {
|
||||
addListeners(BallisticsStartupConfigurationLogger())
|
||||
}
|
||||
}
|
||||
|
||||
private class BallisticsStartupConfigurationLogger : ApplicationListener<ApplicationEnvironmentPreparedEvent> {
|
||||
private val logger = LoggerFactory.getLogger(BallisticsStartupConfigurationLogger::class.java)
|
||||
|
||||
override fun onApplicationEvent(event: ApplicationEnvironmentPreparedEvent) {
|
||||
val environment = event.environment
|
||||
val activeProfiles = environment.activeProfiles.takeIf { it.isNotEmpty() } ?: environment.defaultProfiles
|
||||
|
||||
logger.info(
|
||||
"Ballistics startup config client: activeProfiles={}, configServerUri={}, configProfile={}, configLabel={}",
|
||||
activeProfiles.joinToString(","),
|
||||
maskIfSensitive("spring.cloud.config.uri", environment.getProperty("spring.cloud.config.uri")),
|
||||
environment.getProperty("spring.cloud.config.profile"),
|
||||
environment.getProperty("spring.cloud.config.label"),
|
||||
)
|
||||
|
||||
val configSources = environment.configServerPropertySources()
|
||||
if (configSources.isEmpty()) {
|
||||
logger.warn("Ballistics received no config server property sources")
|
||||
return
|
||||
}
|
||||
|
||||
configSources.forEach { source ->
|
||||
logger.info("Ballistics received config source '{}': {}", source.name, source.printableProperties(environment))
|
||||
}
|
||||
}
|
||||
|
||||
private fun ConfigurableEnvironment.configServerPropertySources(): List<PropertySource<*>> =
|
||||
propertySources
|
||||
.asSequence()
|
||||
.filter { it.name.startsWith("configserver:") || it.name == "configClient" }
|
||||
.toList()
|
||||
|
||||
private fun PropertySource<*>.printableProperties(environment: ConfigurableEnvironment): String {
|
||||
if (this !is EnumerablePropertySource<*>) {
|
||||
return "<non-enumerable>"
|
||||
}
|
||||
|
||||
return propertyNames
|
||||
.sorted()
|
||||
.joinToString(", ") { propertyName ->
|
||||
formatProperty(propertyName, getProperty(propertyName), environment.getProperty(propertyName))
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatProperty(propertyName: String, rawValue: Any?, resolvedValue: String?): String {
|
||||
val printableResolvedValue = maskIfSensitive(propertyName, resolvedValue)
|
||||
val printableRawValue = maskIfSensitive(propertyName, rawValue)
|
||||
|
||||
if (printableRawValue == printableResolvedValue) {
|
||||
return "$propertyName=$printableResolvedValue"
|
||||
}
|
||||
|
||||
return "$propertyName=$printableResolvedValue (raw=$printableRawValue)"
|
||||
}
|
||||
|
||||
private fun maskIfSensitive(propertyName: String, value: Any?): String {
|
||||
if (value == null) {
|
||||
return "<null>"
|
||||
}
|
||||
|
||||
val normalizedName = propertyName.lowercase()
|
||||
val sensitiveMarkers = listOf("password", "passwd", "secret", "token", "credential", "private-key", "api-key", "apikey")
|
||||
return if (sensitiveMarkers.any { normalizedName.contains(it) }) {
|
||||
"******"
|
||||
} else {
|
||||
value.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package space.nstart.pcp.pcp_request_service.configuration
|
||||
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.validation.FieldError
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import org.springframework.web.bind.support.WebExchangeBindException
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
class CustomValidationException(message: String) : RuntimeException(message)
|
||||
class CustomErrorException(message: String) : RuntimeException(message)
|
||||
|
||||
|
||||
@ControllerAdvice
|
||||
class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(CustomValidationException::class)
|
||||
fun handleValidation(ex: CustomValidationException): ResponseEntity<Map<String, String>> {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(mapOf("error" to ex.message!!))
|
||||
}
|
||||
@ExceptionHandler(CustomErrorException::class)
|
||||
fun handleError(ex: CustomErrorException): ResponseEntity<Map<String, String>> {
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(mapOf("error" to ex.message!!))
|
||||
}
|
||||
@ExceptionHandler(WebExchangeBindException::class)
|
||||
fun handleValidationExceptions(ex: WebExchangeBindException): Mono<ResponseEntity<Map<String, String>>> {
|
||||
val errors = mutableMapOf<String, String>()
|
||||
ex.bindingResult.fieldErrors.forEach { error ->
|
||||
errors[error.field] = error.defaultMessage ?: "Validation error"
|
||||
}
|
||||
return Mono.just(ResponseEntity.badRequest().body(errors))
|
||||
}
|
||||
@ExceptionHandler(MethodArgumentNotValidException::class)
|
||||
fun handleValidationExceptions(ex: MethodArgumentNotValidException): ResponseEntity<Map<String, String>> {
|
||||
val errors = mutableMapOf<String, String>()
|
||||
ex.bindingResult.allErrors.forEach { error ->
|
||||
val fieldName = (error as FieldError).field
|
||||
val errorMessage = error.defaultMessage
|
||||
errors[fieldName] = errorMessage ?: "Validation error"
|
||||
}
|
||||
return ResponseEntity.badRequest().body(errors)
|
||||
}
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
package space.nstart.pcp.pcp_request_service.configuration
|
||||
|
||||
import org.apache.kafka.clients.admin.AdminClientConfig
|
||||
import org.apache.kafka.clients.admin.NewTopic
|
||||
import org.apache.kafka.clients.consumer.ConsumerConfig
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord
|
||||
import org.apache.kafka.clients.producer.ProducerConfig
|
||||
import org.apache.kafka.common.header.Header
|
||||
import org.apache.kafka.common.serialization.StringDeserializer
|
||||
import org.apache.kafka.common.serialization.StringSerializer
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory
|
||||
import org.springframework.kafka.core.ConsumerFactory
|
||||
import org.springframework.kafka.core.DefaultKafkaConsumerFactory
|
||||
import org.springframework.kafka.core.DefaultKafkaProducerFactory
|
||||
import org.springframework.kafka.core.KafkaAdmin
|
||||
import org.springframework.kafka.core.KafkaTemplate
|
||||
import org.springframework.kafka.core.ProducerFactory
|
||||
import org.springframework.kafka.listener.adapter.RecordFilterStrategy
|
||||
import space.nstart.pcp.pcp_types_lib.message.PcpKafkaEvent
|
||||
import java.nio.charset.StandardCharsets
|
||||
import kotlin.String
|
||||
|
||||
|
||||
@ConditionalOnProperty(name=["spring.kafka.bootstrap-servers"], matchIfMissing = false)
|
||||
@Configuration
|
||||
class KafkaConfig {
|
||||
companion object {
|
||||
const val HEADER_NAME: String = "type"
|
||||
const val TARGET_CONSUMER_HEADER_NAME: String = "target_consumer"
|
||||
}
|
||||
|
||||
@Value("\${spring.kafka.template.default-topic}")
|
||||
private lateinit var defaultTopic: String
|
||||
|
||||
@Bean
|
||||
fun tleTopic(): NewTopic =
|
||||
NewTopic(defaultTopic, 1, 1)
|
||||
|
||||
|
||||
@Value("\${spring.kafka.bootstrap-servers}")
|
||||
private lateinit var addr: String
|
||||
|
||||
@Value("\${spring.kafka.consumer.group-id}")
|
||||
private lateinit var groupId: String
|
||||
|
||||
@Bean
|
||||
fun template(): KafkaTemplate<String, Any> {
|
||||
return KafkaTemplate(producerFactory())
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun kafkaAdmin(): KafkaAdmin {
|
||||
|
||||
val configs = hashMapOf<String, Any>()
|
||||
configs[AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG] = addr
|
||||
|
||||
return KafkaAdmin(configs)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun consumerFactory(): ConsumerFactory<String, Any> {
|
||||
val props = hashMapOf<String, Any>()
|
||||
props[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = addr
|
||||
props[ConsumerConfig.GROUP_ID_CONFIG] = groupId
|
||||
props[ConsumerConfig.AUTO_OFFSET_RESET_CONFIG] = "latest"
|
||||
props[ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG] = false
|
||||
props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java
|
||||
props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java
|
||||
return DefaultKafkaConsumerFactory(props)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun kafkaListenerContainerFactory(): ConcurrentKafkaListenerContainerFactory<String, Any> =
|
||||
ConcurrentKafkaListenerContainerFactory<String, Any>()
|
||||
.apply { this.setConsumerFactory(consumerFactory()) }
|
||||
|
||||
@Bean
|
||||
fun producerFactory(): ProducerFactory<String, Any> {
|
||||
|
||||
val configProps = hashMapOf<String, Any>()
|
||||
configProps[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = addr
|
||||
configProps[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java
|
||||
configProps[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java
|
||||
|
||||
return DefaultKafkaProducerFactory(configProps)
|
||||
}
|
||||
|
||||
private fun getRecordFilterStrategy(event: PcpKafkaEvent): RecordFilterStrategy<String, String> {
|
||||
return RecordFilterStrategy { consumerRecord: ConsumerRecord<String, String> ->
|
||||
val eventTypeHeader: Header? = consumerRecord.headers().lastHeader(HEADER_NAME)
|
||||
if (eventTypeHeader != null) {
|
||||
val headerValue = String(eventTypeHeader.value(), StandardCharsets.UTF_8)
|
||||
return@RecordFilterStrategy event.name != headerValue
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRecordFilterStrategyByConsumer(): RecordFilterStrategy<String, String> {
|
||||
return RecordFilterStrategy { consumerRecord: ConsumerRecord<String, String> ->
|
||||
val consumerGroupHeader: Header? = consumerRecord.headers().lastHeader(TARGET_CONSUMER_HEADER_NAME)
|
||||
if (consumerGroupHeader != null) {
|
||||
val headerValue = String(consumerGroupHeader.value(), StandardCharsets.UTF_8)
|
||||
return@RecordFilterStrategy "respondedListener" != headerValue
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@Bean("placedIcFilter")
|
||||
fun placedFilter(): RecordFilterStrategy<String, String> {
|
||||
return getRecordFilterStrategy(PcpKafkaEvent.ICPlacedEvent)
|
||||
}
|
||||
|
||||
@Bean("placedIcRvFilter")
|
||||
fun placedIcRvFilter(): RecordFilterStrategy<String, String> {
|
||||
return getRecordFilterStrategy(PcpKafkaEvent.ICRVPlacedEvent)
|
||||
}
|
||||
|
||||
@Bean("updatedIcFilter")
|
||||
fun updatedIcFilter(): RecordFilterStrategy<String, String> {
|
||||
return getRecordFilterStrategy(PcpKafkaEvent.ICUpdatedEvent)
|
||||
}
|
||||
|
||||
@Bean("satelliteDeletedFilter")
|
||||
fun satelliteDeletedFilter(): RecordFilterStrategy<String, String> {
|
||||
return getRecordFilterStrategy(PcpKafkaEvent.SatelliteDeletedEvent)
|
||||
}
|
||||
|
||||
@Bean("respondedFilter")
|
||||
fun respondedFilter(): RecordFilterStrategy<String, String> {
|
||||
return getRecordFilterStrategyByConsumer()
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package space.nstart.pcp.pcp_request_service.configuration
|
||||
|
||||
import jakarta.validation.Validation
|
||||
import jakarta.validation.Validator
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
@Configuration
|
||||
class ValidationConfig {
|
||||
@Bean
|
||||
fun validator(): Validator {
|
||||
return Validation.buildDefaultValidatorFactory().validator
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package space.nstart.pcp.pcp_request_service.controller
|
||||
|
||||
import jakarta.validation.Valid
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import space.nstart.pcp.pcp_request_service.service.SatelliteService
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.ObjViewRequestDTO
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/obj-view")
|
||||
class ObjViewController {
|
||||
|
||||
@Autowired
|
||||
private lateinit var satelliteService: SatelliteService
|
||||
|
||||
@PostMapping("/mpl-square")
|
||||
fun mplSquare(
|
||||
@Valid @RequestBody body : ObjViewRequestDTO
|
||||
) =
|
||||
satelliteService.mplSquare(body)
|
||||
|
||||
@PostMapping("/mpl-point")
|
||||
fun mplPoint(
|
||||
@Valid @RequestBody body : ObjViewRequestDTO
|
||||
) =
|
||||
satelliteService.mplPoint(body)
|
||||
|
||||
@GetMapping("cell-coverage/{number}")
|
||||
fun cellCov(@PathVariable number : Long) = satelliteService.getCellCovering(number)
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package space.nstart.pcp.pcp_request_service.controller
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import space.nstart.pcp.pcp_request_service.service.RVAService
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.IcRvaRequestDTO
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("api/rva")
|
||||
class RVAController {
|
||||
|
||||
@Autowired
|
||||
private lateinit var rvaService: RVAService
|
||||
|
||||
@PostMapping
|
||||
fun getZRV(@RequestBody body : IcRvaRequestDTO) = rvaService.calculateRVA(body)
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package space.nstart.pcp.pcp_request_service.controller
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.Parameter
|
||||
import jakarta.validation.Valid
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import space.nstart.pcp.pcp_request_service.service.SatelliteService
|
||||
import space.nstart.pcp.pcp_types_lib.configuration.CustomValidationException
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.ExactTimePositionRequestDTO
|
||||
import java.time.LocalDateTime
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/satellites")
|
||||
class SatelliteController {
|
||||
@Autowired
|
||||
private lateinit var satelliteService: SatelliteService
|
||||
|
||||
@PostMapping("/{satellite_id}/extract-time")
|
||||
fun extractTime(
|
||||
@PathVariable("satellite_id") satelliteId : Long,
|
||||
@Valid @RequestBody body : ExactTimePositionRequestDTO
|
||||
) =
|
||||
satelliteService.exactTime(satelliteId, body)
|
||||
|
||||
|
||||
|
||||
@Operation(
|
||||
summary = "Получить ПДЦМ",
|
||||
description = "Возвраает ПДЦМ для заданного аппарата на заданном интервале времени с шагом 60 с"
|
||||
)
|
||||
@GetMapping("/{norad_id}/orbit")
|
||||
fun getPoints(
|
||||
@Parameter(description = "Идентификатор NORAD", example = "56756")
|
||||
@PathVariable("norad_id") noradId : Long,
|
||||
@Parameter(description = "Время начала (если null, то мин. время для выбранного КА)")
|
||||
@RequestParam("time_start") timeStart : LocalDateTime?,
|
||||
@Parameter(description = "Время конца (если null, то макс. время для выбранного КА)")
|
||||
@RequestParam("time_stop") timeStrop : LocalDateTime?
|
||||
) =
|
||||
satelliteService.getOrbPoints(noradId, timeStart, timeStrop)
|
||||
|
||||
@Operation(
|
||||
summary = "Получить доступные интервалы прогноза по всем КА",
|
||||
description = "Возвращает минимальное и максимальное время ПДЦМ для каждого КА. Если time задан, возвращаются только КА, у которых это время попадает в доступный интервал"
|
||||
)
|
||||
@GetMapping("/orbit/availability")
|
||||
fun getOrbitAvailability(
|
||||
@Parameter(description = "Проверяемое время. Если null, возвращаются все КА, для которых есть точки ПДЦМ")
|
||||
@RequestParam("time", required = false) time: LocalDateTime?
|
||||
) =
|
||||
satelliteService.getOrbitAvailability(time)
|
||||
|
||||
|
||||
@GetMapping("/orbit/availability/{id}")
|
||||
fun getOrbitAvailabilityById(
|
||||
@PathVariable id : Long
|
||||
) =
|
||||
satelliteService.getOrbitAvailability(null).filter { it.satelliteId == id }.firstOrNull()?:
|
||||
throw CustomValidationException("Спутник с заданным идентификатором не зарегистрирован")
|
||||
|
||||
|
||||
@Operation(
|
||||
summary = "Получить параметры прохождения ВУЗов",
|
||||
description = "Возвраает массив ВУЗов для заданного аппарата на заданном интервале времени"
|
||||
)
|
||||
@GetMapping("/{norad_id}/asc-node")
|
||||
fun getAskNodes(
|
||||
@Parameter(description = "Идентификатор NORAD", example = "56756")
|
||||
@PathVariable("norad_id") noradId : Long,
|
||||
@Parameter(description = "Время начала (если null, то мин. время для выбранного КА)")
|
||||
@RequestParam("time_start") timeStart : LocalDateTime?,
|
||||
@Parameter(description = "Время конца (если null, то макс. время для выбранного КА)")
|
||||
@RequestParam("time_stop") timeStrop : LocalDateTime?
|
||||
) =
|
||||
satelliteService.getAscNodes(noradId, timeStart, timeStrop)
|
||||
|
||||
|
||||
@Operation(
|
||||
summary = "Получить параметры трассы полета и полосы обзора",
|
||||
description = "Возвращает параметры ТП и ПО на заданном интервале времен с шагом 60 с"
|
||||
)
|
||||
@GetMapping("/{norad_id}/flight-line")
|
||||
fun getFL(
|
||||
@Parameter(description = "Идентификатор NORAD", example = "56756")
|
||||
@PathVariable("norad_id") noradId : Long,
|
||||
@Parameter(description = "Время начала (если null, то мин. время для выбранного КА)")
|
||||
@RequestParam("time_start") timeStart : LocalDateTime?,
|
||||
@Parameter(description = "Время конца (если null, то макс. время для выбранного КА)")
|
||||
@RequestParam("time_stop") timeStrop : LocalDateTime?
|
||||
) =
|
||||
satelliteService.getFL(noradId, timeStart, timeStrop, 60.0)
|
||||
|
||||
|
||||
|
||||
|
||||
@Operation(
|
||||
summary = "Получить параметры ЗРВ",
|
||||
description = "Возвращает параметры ЗРВ на заданном интервале времен"
|
||||
)
|
||||
@GetMapping("/{norad_id}/rva")
|
||||
fun getRVA(
|
||||
@Parameter(description = "Идентификатор NORAD", example = "56756")
|
||||
@PathVariable("norad_id") noradId : Long,
|
||||
@Parameter(description = "Время начала (если null, то мин. время для выбранного КА)")
|
||||
@RequestParam("time_start") timeStart : LocalDateTime,
|
||||
@Parameter(description = "Время конца (если null, то макс. время для выбранного КА)")
|
||||
@RequestParam("time_stop") timeStrop : LocalDateTime
|
||||
) =
|
||||
satelliteService.getRVA(noradId, timeStart, timeStrop)
|
||||
|
||||
|
||||
@PostMapping("/{satellite_id}/clear")
|
||||
fun clear(
|
||||
@PathVariable("satellite_id") satelliteId : Long
|
||||
) =
|
||||
satelliteService.clear(satelliteId)
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package space.nstart.pcp.pcp_request_service.controller
|
||||
|
||||
import ballistics.types.OrbitalPoint
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import space.nstart.pcp.pcp_request_service.service.TLEService
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TleRvaRequestDTO
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/tle")
|
||||
class TLEController {
|
||||
|
||||
@Autowired
|
||||
private lateinit var tleService: TLEService
|
||||
|
||||
|
||||
@PostMapping("/parse")
|
||||
fun orbit(@RequestBody tle : TLEDTO) = tleService.parseTLE(tle)
|
||||
|
||||
|
||||
@PostMapping("/rva")
|
||||
fun rva(@RequestBody req : TleRvaRequestDTO) = tleService.rva(req)
|
||||
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package space.nstart.pcp.pcp_request_service.entity
|
||||
|
||||
import ballistics.types.KeplerParams
|
||||
import ballistics.types.RevolutionParameter
|
||||
import ballistics.utils.fromDateTime
|
||||
import ballistics.utils.toDateTime
|
||||
import jakarta.persistence.Column
|
||||
import jakarta.persistence.Entity
|
||||
import jakarta.persistence.GeneratedValue
|
||||
import jakarta.persistence.GenerationType
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.Table
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.AscNodeDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.KeplersDTO
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.math.PI
|
||||
|
||||
|
||||
@Entity
|
||||
@Table(name = "asc_node")
|
||||
class AscNodeEntity(
|
||||
@Id
|
||||
@Column(name = "asc_node_id")
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
val id : Long? = null,
|
||||
@Column(nullable = false)
|
||||
val satelliteId : Long = 0,
|
||||
@Column(name = "t", nullable = false)
|
||||
val time : LocalDateTime = LocalDateTime.now(),
|
||||
@Column(nullable = false)
|
||||
val revolution : Long = 0,
|
||||
val longitude : Double = 0.0,
|
||||
val height : Double = 0.0,
|
||||
val x: Double = 0.0,
|
||||
val y: Double = 0.0,
|
||||
val z: Double = 0.0,
|
||||
val vx: Double = 0.0,
|
||||
val vy: Double = 0.0,
|
||||
val vz: Double = 0.0,
|
||||
) {
|
||||
constructor(an : RevolutionParameter, sat : Long) : this(
|
||||
null,
|
||||
sat,
|
||||
toDateTime(an.vuz.t),
|
||||
an.vuz.vit.toLong(),
|
||||
an.lVuz * 180 / PI,
|
||||
an.hVuz,
|
||||
an.vuz.r.x,
|
||||
an.vuz.r.y,
|
||||
an.vuz.r.z,
|
||||
an.vuz.v.x,
|
||||
an.vuz.v.y,
|
||||
an.vuz.v.z,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val SECONDS_IN_DAY = 86_400.0
|
||||
const val NANOS_IN_DAY = 86_400_000_000_000L
|
||||
}
|
||||
|
||||
private fun positiveModulo(value: Double, modulo: Double): Double {
|
||||
val remainder = value % modulo
|
||||
return if (remainder < 0) remainder + modulo else remainder
|
||||
}
|
||||
|
||||
/**
|
||||
* Местное среднее солнечное время на долготе longitudeRad.
|
||||
* Используется та же идея, что в tmest(t, Lvuz): UTC-время суток плюс поправка по долготе.
|
||||
*/
|
||||
private fun localMeanSolarSeconds(epochSeconds: Double, longitudeRad: Double): Double {
|
||||
val utcSecondsOfDay = positiveModulo(epochSeconds, SECONDS_IN_DAY)
|
||||
val longitudeShiftSeconds = longitudeRad / (2.0 * PI) * SECONDS_IN_DAY
|
||||
return positiveModulo(utcSecondsOfDay + longitudeShiftSeconds, SECONDS_IN_DAY)
|
||||
}
|
||||
|
||||
|
||||
fun tmest(t : LocalDateTime, l : Double) : LocalDateTime
|
||||
{
|
||||
var dl = l;
|
||||
if (dl > PI)
|
||||
dl = dl - 2 * PI;
|
||||
var tm = t.plusSeconds(-60*60*3);
|
||||
tm = tm.plusNanos((dl/2.0/PI * 86400.0*1e9).toLong());
|
||||
return tm;
|
||||
|
||||
}
|
||||
|
||||
fun toDTO(keps : KeplerParams, rekv : Double) = AscNodeDTO(
|
||||
time = this.time,
|
||||
revolution = this.revolution.toInt(),
|
||||
long = this.longitude,
|
||||
height = this.height,
|
||||
x = this.x,
|
||||
y = this.y,
|
||||
z = this.z,
|
||||
vx = this.vx,
|
||||
vy = this.vy,
|
||||
vz = this.vz,
|
||||
keps = KeplersDTO(
|
||||
(keps.ael - rekv) / 1000.0,
|
||||
keps.e,
|
||||
keps.nakl * 180 / PI,
|
||||
keps.omegab * 180 / PI,
|
||||
keps.omegam * 180 / PI
|
||||
),
|
||||
localMeanTime = tmest(this.time, this.longitude * PI / 180.0)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package space.nstart.pcp.pcp_request_service.entity
|
||||
|
||||
import ballistics.utils.toDateTime
|
||||
import jakarta.persistence.Column
|
||||
import jakarta.persistence.Entity
|
||||
import jakarta.persistence.GeneratedValue
|
||||
import jakarta.persistence.GenerationType
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.Table
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.RevolutionSign
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SquareViewParamDTO
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.math.PI
|
||||
|
||||
@Entity
|
||||
@Table(name = "cell_covering")
|
||||
class EarthCellViewsEntity(
|
||||
@Id
|
||||
@Column(name = "cell_covering_id")
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
val id : Long? = null,
|
||||
@Column(nullable = false)
|
||||
val satelliteId : Long = 0,
|
||||
@Column(nullable = false)
|
||||
val cellNum : Long = 0,
|
||||
@Column(name = "rev_begin", nullable = false)
|
||||
val revolutionBegin : Long = 0,
|
||||
@Column(nullable = false)
|
||||
val timeBegin : LocalDateTime = LocalDateTime.now(),
|
||||
@Column(nullable = false)
|
||||
val revSignBegin : Int = 0,
|
||||
@Column(name = "rev_end", nullable = false)
|
||||
val revolutionEnd: Long = 0,
|
||||
@Column(nullable = false)
|
||||
val timeEnd : LocalDateTime = LocalDateTime.now(),
|
||||
@Column(nullable = false)
|
||||
val revSignEnd : Int = 0,
|
||||
@Column(name = "roll_min")
|
||||
val gammaMin : Double = 0.0,
|
||||
@Column(name = "roll_max")
|
||||
val gammaMax : Double = 0.0,
|
||||
@Column(name = "contour_wkt", nullable = false)
|
||||
val contour : String = ""
|
||||
) {
|
||||
|
||||
fun toDTO() = SquareViewParamDTO(
|
||||
this.satelliteId,
|
||||
this.cellNum.toString(),
|
||||
this.revolutionBegin,
|
||||
this.timeBegin,
|
||||
if (this.revSignBegin == 0) RevolutionSign.ASC else RevolutionSign.DESC,
|
||||
this.revolutionBegin,
|
||||
this.timeEnd,
|
||||
if (this.revSignEnd == 0) RevolutionSign.ASC else RevolutionSign.DESC,
|
||||
this.gammaMin,
|
||||
this.gammaMax
|
||||
)
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package space.nstart.pcp.pcp_request_service.entity
|
||||
|
||||
import ballistics.types.FleghtLineSector
|
||||
import ballistics.utils.toDateTime
|
||||
import jakarta.persistence.Column
|
||||
import jakarta.persistence.Entity
|
||||
import jakarta.persistence.GeneratedValue
|
||||
import jakarta.persistence.GenerationType
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.Table
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Entity
|
||||
@Table(name = "earth_coverage")
|
||||
class EarthCoverageEntity(
|
||||
@Id
|
||||
@Column(name = "earth_coverage_id")
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
val id : Long? = null,
|
||||
@Column(nullable = false)
|
||||
val satelliteId : Long = 0,
|
||||
@Column(nullable = false)
|
||||
val cellNumber : Long = 0,
|
||||
@Column(nullable = false)
|
||||
val tStart : LocalDateTime = LocalDateTime.now(),
|
||||
@Column(nullable = false)
|
||||
val tStop : LocalDateTime = LocalDateTime.now(),
|
||||
) {
|
||||
constructor(fl : FleghtLineSector, sector : Int, sat : Long) : this(
|
||||
null,
|
||||
sat,
|
||||
sector.toLong(),
|
||||
toDateTime(fl.tStart),
|
||||
toDateTime(fl.tStop)
|
||||
)
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package space.nstart.pcp.pcp_request_service.entity
|
||||
|
||||
import ballistics.types.FlightLine
|
||||
import ballistics.utils.toDateTime
|
||||
import jakarta.persistence.*
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.FlightLineDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.RevolutionSign
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.math.PI
|
||||
|
||||
|
||||
@Entity
|
||||
@Table(name = "flight_line")
|
||||
class FlightLineEntity(
|
||||
@Id
|
||||
@Column(name = "flight_line_id")
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
val id : Long? = null,
|
||||
@Column(nullable = false)
|
||||
val satelliteId : Long = 0,
|
||||
@Column(name = "t", nullable = false)
|
||||
val time : LocalDateTime = LocalDateTime.now(),
|
||||
@Column(nullable = false)
|
||||
val revolution : Long = 0,
|
||||
@Column(nullable = false)
|
||||
val revolutionSign: String = RevolutionSign.ASC.toString(),
|
||||
val longitude : Double = 0.0,
|
||||
val latitude : Double = 0.0,
|
||||
val longitudeOuterLeft : Double = 0.0,
|
||||
val latitudeOuterLeft : Double = 0.0,
|
||||
val longitudeInnerLeft : Double = 0.0,
|
||||
val latitudeInnerLeft : Double = 0.0,
|
||||
val longitudeInnerRight : Double = 0.0,
|
||||
val latitudeInnerRight : Double = 0.0,
|
||||
val longitudeOuterRight : Double = 0.0,
|
||||
val latitudeOuterRight : Double = 0.0,
|
||||
) {
|
||||
constructor(fl : FlightLine, sat : Long) : this(
|
||||
null,
|
||||
sat,
|
||||
toDateTime(fl.t),
|
||||
fl.vit.toLong(),
|
||||
if (fl.pv == 0) RevolutionSign.ASC.toString() else RevolutionSign.DESC.toString(),
|
||||
|
||||
fl.flightLine.long * 180 / PI,
|
||||
fl.flightLine.lat * 180 / PI,
|
||||
|
||||
|
||||
fl.leftOuterSwath.long * 180 / PI,
|
||||
fl.leftOuterSwath.lat * 180 / PI,
|
||||
|
||||
fl.leftInternalSwath.long * 180 / PI,
|
||||
fl.leftInternalSwath.lat * 180 / PI,
|
||||
|
||||
fl.rightInternalSwath.long * 180 / PI,
|
||||
fl.rightInternalSwath.lat * 180 / PI,
|
||||
|
||||
fl.rightOuterSwath.long * 180 / PI,
|
||||
fl.rightOuterSwath.lat * 180 / PI,
|
||||
)
|
||||
|
||||
fun toDTO() = FlightLineDTO(
|
||||
time = time,
|
||||
revolution = revolution,
|
||||
lat = latitude ,
|
||||
long = longitude,
|
||||
latLeft = latitudeOuterLeft,
|
||||
longLeft = longitudeOuterLeft,
|
||||
latInnerLeft = latitudeInnerLeft,
|
||||
longInnerLeft = longitudeInnerLeft,
|
||||
latInnerRight = latitudeInnerRight,
|
||||
longInnerRight = longitudeInnerRight,
|
||||
latRight = latitudeOuterRight,
|
||||
longRight = longitudeOuterRight,
|
||||
revSign = RevolutionSign.valueOf(revolutionSign)
|
||||
)
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package space.nstart.pcp.pcp_request_service.entity
|
||||
|
||||
import ballistics.types.OrbitalPoint
|
||||
import ballistics.utils.fromDateTime
|
||||
import ballistics.utils.math.Vector3D
|
||||
import ballistics.utils.toDateTime
|
||||
import jakarta.persistence.*
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Entity
|
||||
@Table(name = "pdcm")
|
||||
class PDCMEntity (
|
||||
@Id
|
||||
@Column(name = "pdcm_id")
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
val id : Long? = null,
|
||||
@Column(nullable = false)
|
||||
val satelliteId : Long = 0,
|
||||
@Column(name = "t", nullable = false)
|
||||
val time : LocalDateTime = LocalDateTime.now(),
|
||||
@Column(nullable = false)
|
||||
val revolution : Long = 0,
|
||||
val x: Double = 0.0,
|
||||
val y: Double = 0.0,
|
||||
val z: Double = 0.0,
|
||||
val vx: Double = 0.0,
|
||||
val vy: Double = 0.0,
|
||||
val vz: Double = 0.0,
|
||||
){
|
||||
constructor(op : OrbitalPoint, sat : Long) : this(
|
||||
null,
|
||||
sat,
|
||||
toDateTime(op.t),
|
||||
op.vit.toLong(),
|
||||
op.r.x,
|
||||
op.r.y,
|
||||
op.r.z,
|
||||
op.v.x,
|
||||
op.v.y,
|
||||
op.v.z
|
||||
)
|
||||
|
||||
fun toOrbitalPoint() = OrbitalPoint(
|
||||
t = fromDateTime(time),
|
||||
vit = revolution.toInt(),
|
||||
r = Vector3D(x,y,z),
|
||||
v = Vector3D(vx, vy,vz)
|
||||
)
|
||||
|
||||
fun toDTO() = OrbPointDTO(
|
||||
time = time,
|
||||
revolution,
|
||||
vx,vy,vz,x,y,z
|
||||
)
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package space.nstart.pcp.pcp_request_service.message
|
||||
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.kafka.annotation.KafkaListener
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
|
||||
@Component
|
||||
class KafkaListener {
|
||||
private var log: Logger = LoggerFactory.getLogger(this::class.java)
|
||||
|
||||
@KafkaListener(id = "placedIcFilter", topics = ["\${spring.kafka.template.default-topic}"], filter = "placedIcFilter")
|
||||
fun listener(data: String?) {
|
||||
log.info("Received message [{}] in group1 with header1", data)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package space.nstart.pcp.pcp_request_service.message
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.ObjectProvider
|
||||
import org.springframework.kafka.annotation.KafkaListener
|
||||
import org.springframework.stereotype.Component
|
||||
import space.nstart.pcp.pcp_request_service.service.SatelliteService
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteDeletedEventDTO
|
||||
import tools.jackson.databind.ObjectMapper
|
||||
|
||||
private const val SATELLITE_DELETED_CONSUMER_GROUP = "pcp-ballistics-service-satellite-deleted"
|
||||
|
||||
@Component
|
||||
class SatelliteDeletedKafkaListener(
|
||||
objectMapperProvider: ObjectProvider<ObjectMapper>,
|
||||
private val satelliteService: SatelliteService
|
||||
) {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||
private val objectMapper = objectMapperProvider.ifAvailable
|
||||
?: throw IllegalStateException("ObjectMapper bean is required for SatelliteDeletedKafkaListener")
|
||||
|
||||
@KafkaListener(
|
||||
id = "satelliteDeletedFilter",
|
||||
topics = ["\${app.kafka.topics.satellites:pcp.satellites}"],
|
||||
groupId = SATELLITE_DELETED_CONSUMER_GROUP,
|
||||
filter = "satelliteDeletedFilter"
|
||||
)
|
||||
fun consume(message: String?) {
|
||||
if (message.isNullOrBlank()) {
|
||||
logger.warn("Received empty SatelliteDeletedEvent Kafka message")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val rootNode = objectMapper.readTree(message)
|
||||
val payloadNode = rootNode["data"] ?: throw IllegalArgumentException("В сообщении отсутствует поле data")
|
||||
val event = objectMapper.treeToValue(payloadNode, SatelliteDeletedEventDTO::class.java)
|
||||
logger.info(
|
||||
"Received SatelliteDeletedEvent in ballistics-service: satelliteId={}, noradId={}, consumerGroup={}",
|
||||
event.satelliteId,
|
||||
event.noradId,
|
||||
SATELLITE_DELETED_CONSUMER_GROUP
|
||||
)
|
||||
deleteByIdentifiers(event)
|
||||
} catch (exception: Exception) {
|
||||
logger.error("Failed to process SatelliteDeletedEvent in ballistics-service: {}", exception.message, exception)
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteByIdentifiers(event: SatelliteDeletedEventDTO) {
|
||||
event.deleteIdentifiers().forEach { satelliteId ->
|
||||
satelliteService.clear(satelliteId)
|
||||
}
|
||||
logger.info(
|
||||
"SatelliteDeletedEvent processed in ballistics-service: satelliteId={}, noradId={}, consumerGroup={}",
|
||||
event.satelliteId,
|
||||
event.noradId,
|
||||
SATELLITE_DELETED_CONSUMER_GROUP
|
||||
)
|
||||
}
|
||||
|
||||
private fun SatelliteDeletedEventDTO.deleteIdentifiers(): List<Long> =
|
||||
listOfNotNull(satelliteId, noradId).distinct()
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package space.nstart.pcp.pcp_request_service.message
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.ObjectProvider
|
||||
import org.springframework.kafka.annotation.KafkaListener
|
||||
import org.springframework.stereotype.Component
|
||||
import space.nstart.pcp.pcp_request_service.service.SatelliteIcEventService
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.InitialConditionsDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteICDTO
|
||||
import tools.jackson.databind.ObjectMapper
|
||||
import tools.jackson.databind.node.ObjectNode
|
||||
|
||||
@Component
|
||||
class SatelliteIcRvKafkaListener(
|
||||
objectMapperProvider: ObjectProvider<ObjectMapper>,
|
||||
private val satelliteIcEventService: SatelliteIcEventService
|
||||
) {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||
private val objectMapper = objectMapperProvider.ifAvailable
|
||||
?: throw IllegalStateException("ObjectMapper bean is required for SatelliteIcRvKafkaListener")
|
||||
|
||||
@KafkaListener(
|
||||
id = "placedIcRvFilter",
|
||||
topics = ["\${spring.kafka.template.default-topic}"],
|
||||
filter = "placedIcRvFilter"
|
||||
)
|
||||
fun listener(data: String?) {
|
||||
consume(data)
|
||||
}
|
||||
|
||||
fun consume(data: String?) {
|
||||
if (data.isNullOrBlank()) {
|
||||
logger.warn("Received empty satellite initial conditions Kafka message")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info("Received satellite initial conditions Kafka message body: {}", data)
|
||||
val rootNode = objectMapper.readTree(data)
|
||||
val payloadNode = rootNode["data"] ?: throw IllegalArgumentException("В сообщении отсутствует поле data")
|
||||
val payload = objectMapper.treeToValue(normalizePayload(payloadNode), SatelliteICDTO::class.java)
|
||||
satelliteIcEventService.handlePlacedIcRv(payload)
|
||||
} catch (exception: Exception) {
|
||||
logger.error("Ошибка обработки сообщения о начальных условиях КА: {}", exception.message, exception)
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps consumer backward compatible with already published messages where optional IC coefficients were null.
|
||||
*/
|
||||
private fun normalizePayload(payloadNode: tools.jackson.databind.JsonNode): tools.jackson.databind.JsonNode {
|
||||
val normalizedPayload = payloadNode.deepCopy()
|
||||
val initialConditionsNode = normalizedPayload["ic"]
|
||||
if (initialConditionsNode is ObjectNode) {
|
||||
if (initialConditionsNode["sBall"] == null || initialConditionsNode["sBall"].isNull) {
|
||||
initialConditionsNode.put("sBall", DEFAULT_INITIAL_CONDITIONS.sBall)
|
||||
}
|
||||
if (initialConditionsNode["f81"] == null || initialConditionsNode["f81"].isNull) {
|
||||
initialConditionsNode.put("f81", DEFAULT_INITIAL_CONDITIONS.f81)
|
||||
}
|
||||
}
|
||||
return normalizedPayload
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DEFAULT_INITIAL_CONDITIONS = InitialConditionsDTO()
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package space.nstart.pcp.pcp_request_service.model
|
||||
|
||||
import ballistics.utils.toDateTime
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.RevolutionSign
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SquareViewParamDTO
|
||||
import kotlin.math.PI
|
||||
|
||||
data class SquareViewParamsModel(
|
||||
var id: Long = 0,
|
||||
var objID : String,
|
||||
var pointNumb: Int = 0,
|
||||
var vitN: Int = 0,
|
||||
var tN: Double = 0.0,
|
||||
var pvN : RevolutionSign = RevolutionSign.ASC,
|
||||
var vitK: Int = 0,
|
||||
var tK: Double = 0.0,
|
||||
var pvK: RevolutionSign = RevolutionSign.ASC,
|
||||
var krenMin: Double = 0.0,
|
||||
var krenMax: Double = 0.0
|
||||
): Cloneable {
|
||||
|
||||
fun toDTO(): SquareViewParamDTO =
|
||||
SquareViewParamDTO(
|
||||
this.id,
|
||||
this.objID,
|
||||
this.vitN.toLong(),
|
||||
toDateTime(this.tN),
|
||||
this.pvN,
|
||||
this.vitK.toLong(),
|
||||
toDateTime(this.tK),
|
||||
this.pvK,
|
||||
this.krenMin * 180 / PI,
|
||||
this.krenMax * 180 / PI,
|
||||
)
|
||||
|
||||
public override fun clone(): SquareViewParamsModel {
|
||||
return super.clone() as SquareViewParamsModel
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package space.nstart.pcp.pcp_request_service.repository
|
||||
|
||||
import jakarta.transaction.Transactional
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.Modifying
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.data.repository.query.Param
|
||||
import space.nstart.pcp.pcp_request_service.entity.AscNodeEntity
|
||||
import java.time.LocalDateTime
|
||||
|
||||
|
||||
interface AscNodeRepository : JpaRepository<AscNodeEntity, Long>{
|
||||
|
||||
@Modifying
|
||||
@Transactional
|
||||
@Query("DELETE FROM asc_node WHERE satellite_id =:sat", nativeQuery = true)
|
||||
fun deleteBySatId(@Param("sat") sat : Long) : Int
|
||||
|
||||
|
||||
fun findBySatelliteId(id : Long) : List<AscNodeEntity>
|
||||
|
||||
fun findBySatelliteIdAndTimeBetween(id : Long, t1 : LocalDateTime, t2 : LocalDateTime) : List<AscNodeEntity>
|
||||
|
||||
fun findBySatelliteIdAndTimeAfter(id : Long, t : LocalDateTime): List<AscNodeEntity>
|
||||
|
||||
fun findBySatelliteIdAndTimeBefore(id : Long, t : LocalDateTime): List<AscNodeEntity>
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package space.nstart.pcp.pcp_request_service.repository
|
||||
|
||||
import jakarta.transaction.Transactional
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.Modifying
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.data.repository.query.Param
|
||||
import space.nstart.pcp.pcp_request_service.entity.AscNodeEntity
|
||||
import space.nstart.pcp.pcp_request_service.entity.EarthCellViewsEntity
|
||||
|
||||
interface EarthCellViewRepository : JpaRepository<EarthCellViewsEntity, Long>{
|
||||
@Modifying
|
||||
@Transactional
|
||||
@Query("DELETE FROM cell_covering WHERE satellite_id =:sat", nativeQuery = true)
|
||||
fun deleteBySatId(@Param("sat") sat : Long) : Int
|
||||
|
||||
|
||||
fun findBySatelliteId(id : Long) : List<EarthCellViewsEntity>
|
||||
|
||||
fun findByCellNum(num : Long) : List<EarthCellViewsEntity>
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package space.nstart.pcp.pcp_request_service.repository
|
||||
|
||||
import jakarta.transaction.Transactional
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.Modifying
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.data.repository.query.Param
|
||||
import space.nstart.pcp.pcp_request_service.entity.EarthCoverageEntity
|
||||
import java.time.LocalDateTime
|
||||
|
||||
|
||||
interface EarthCoverageRepository : JpaRepository<EarthCoverageEntity, Long>{
|
||||
|
||||
@Modifying
|
||||
@Transactional
|
||||
@Query("DELETE FROM earth_coverage WHERE satellite_id =:sat", nativeQuery = true)
|
||||
fun deleteBySatId(@Param("sat") sat : Long) : Int
|
||||
|
||||
|
||||
fun findBySatelliteIdAndTStartGreaterThanAndTStopLessThanAndCellNumberIn
|
||||
(id : Long, tn : LocalDateTime, tk: LocalDateTime, cells : List<Long>) : List<EarthCoverageEntity>
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package space.nstart.pcp.pcp_request_service.repository
|
||||
|
||||
import jakarta.transaction.Transactional
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.Modifying
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.data.repository.query.Param
|
||||
import space.nstart.pcp.pcp_request_service.entity.FlightLineEntity
|
||||
import java.time.LocalDateTime
|
||||
|
||||
|
||||
interface FlightLineRepository : JpaRepository<FlightLineEntity, Long>{
|
||||
|
||||
@Modifying
|
||||
@Transactional
|
||||
@Query("DELETE FROM flight_line WHERE satellite_id =:sat", nativeQuery = true)
|
||||
fun deleteBySatId(@Param("sat") sat : Long) : Int
|
||||
|
||||
|
||||
fun findBySatelliteIdAndTimeBetween(id : Long, tn : LocalDateTime, tk: LocalDateTime) : List<FlightLineEntity>
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package space.nstart.pcp.pcp_request_service.repository
|
||||
|
||||
import jakarta.transaction.Transactional
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.Modifying
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.data.repository.query.Param
|
||||
import space.nstart.pcp.pcp_request_service.entity.PDCMEntity
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteOrbitAvailabilityDTO
|
||||
import java.time.LocalDateTime
|
||||
|
||||
interface PDCMRepository : JpaRepository<PDCMEntity, Long>{
|
||||
|
||||
@Modifying
|
||||
@Transactional
|
||||
@Query("DELETE FROM pdcm WHERE satellite_id =:sat", nativeQuery = true)
|
||||
fun deleteBySatId(@Param("sat") sat : Long) : Int
|
||||
|
||||
fun findBySatelliteIdAndTimeBetween(id : Long, tBegin : LocalDateTime, tEnd : LocalDateTime) : List<PDCMEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
select new space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteOrbitAvailabilityDTO(
|
||||
p.satelliteId,
|
||||
min(p.time),
|
||||
max(p.time)
|
||||
)
|
||||
from PDCMEntity p
|
||||
group by p.satelliteId
|
||||
order by p.satelliteId
|
||||
"""
|
||||
)
|
||||
fun findSatelliteOrbitAvailability(): List<SatelliteOrbitAvailabilityDTO>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
select new space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteOrbitAvailabilityDTO(
|
||||
p.satelliteId,
|
||||
min(p.time),
|
||||
max(p.time)
|
||||
)
|
||||
from PDCMEntity p
|
||||
group by p.satelliteId
|
||||
having :time between min(p.time) and max(p.time)
|
||||
order by p.satelliteId
|
||||
"""
|
||||
)
|
||||
fun findSatelliteOrbitAvailabilityAtTime(@Param("time") time: LocalDateTime): List<SatelliteOrbitAvailabilityDTO>
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package space.nstart.pcp.pcp_request_service.service
|
||||
|
||||
import ballistics.Ballistics
|
||||
import ballistics.types.BallisticsError
|
||||
import ballistics.types.InitialConditions
|
||||
import ballistics.types.ModDVType
|
||||
import ballistics.types.OrbitalPoint
|
||||
import ballistics.types.PPI
|
||||
import ballistics.types.VisibilityParametersZRV
|
||||
import ballistics.types.ZRV
|
||||
import ballistics.utils.fromDateTime
|
||||
import ballistics.utils.math.Vector3D
|
||||
import ballistics.utils.toDateTime
|
||||
import org.springframework.stereotype.Service
|
||||
import space.nstart.pcp.pcp_request_service.configuration.CustomErrorException
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.IcRvaRequestDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.RadioVisibilityAreaDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TargetPositionDTO
|
||||
import kotlin.math.PI
|
||||
|
||||
|
||||
@Service
|
||||
class RVAService {
|
||||
|
||||
|
||||
|
||||
fun calculateRVA(icRvaRequest : IcRvaRequestDTO) : List<RadioVisibilityAreaDTO> {
|
||||
|
||||
|
||||
val bal = Ballistics()
|
||||
val mdv = ModDVType.fromInt((icRvaRequest.modDvType ?: 1).toInt())
|
||||
|
||||
bal.modDVType = mdv
|
||||
var result = bal.calculateOrbPoints(
|
||||
InitialConditions(
|
||||
OrbitalPoint(
|
||||
fromDateTime(icRvaRequest.ic.orbPoint.time),
|
||||
icRvaRequest.ic.orbPoint.revolution.toInt(),
|
||||
Vector3D(
|
||||
icRvaRequest.ic.orbPoint.x,
|
||||
icRvaRequest.ic.orbPoint.y,
|
||||
icRvaRequest.ic.orbPoint.z,
|
||||
),
|
||||
Vector3D(
|
||||
icRvaRequest.ic.orbPoint.vx,
|
||||
icRvaRequest.ic.orbPoint.vy,
|
||||
icRvaRequest.ic.orbPoint.vz,
|
||||
)
|
||||
),
|
||||
icRvaRequest.ic.sBall,
|
||||
icRvaRequest.ic.f81
|
||||
),
|
||||
fromDateTime(icRvaRequest.timeStart),
|
||||
fromDateTime(icRvaRequest.timeStop)
|
||||
)
|
||||
|
||||
if (result != BallisticsError.OK)
|
||||
throw CustomErrorException("Ошибка расчета ПДЦМ. Код ошибки : $result")
|
||||
|
||||
result = bal.calculateZRV(
|
||||
listOf(
|
||||
PPI(
|
||||
1, 1,
|
||||
icRvaRequest.station.position.lat / 180 * PI,
|
||||
icRvaRequest.station.position.long / 180 * PI,
|
||||
icRvaRequest.station.position.height,
|
||||
icRvaRequest.station.elevationMin / 180 * PI,
|
||||
icRvaRequest.station.elevationMax / 180 * PI,
|
||||
null, null
|
||||
)
|
||||
),
|
||||
fromDateTime(icRvaRequest.timeStart),
|
||||
fromDateTime(icRvaRequest.timeStop)
|
||||
)
|
||||
if (result != BallisticsError.OK)
|
||||
throw CustomErrorException("Ошибка расчета ЗРВ. Код ошибки : $result")
|
||||
|
||||
return bal.zrv.map { mkRVA(it, 0L) }
|
||||
}
|
||||
|
||||
|
||||
fun mkTP(p : VisibilityParametersZRV) = TargetPositionDTO(
|
||||
toDateTime(p.t),
|
||||
p.elevation * 180 / PI,
|
||||
p.azimuth * 180 / PI,
|
||||
p.range
|
||||
)
|
||||
|
||||
fun mkRVA(p : ZRV, id : Long) = RadioVisibilityAreaDTO(
|
||||
noradId = id,
|
||||
stationId = p.ppi.toLong(),
|
||||
revolution = p.vit.toLong(),
|
||||
onStart = mkTP(p.zoneIn),
|
||||
onMaximum = mkTP(p.zoneMax),
|
||||
onStop = mkTP(p.zoneOut)
|
||||
)
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package space.nstart.pcp.pcp_request_service.service
|
||||
|
||||
import ballistics.Ballistics
|
||||
import ballistics.types.BallisticsError
|
||||
import ballistics.types.TLE
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.stereotype.Service
|
||||
import space.nstart.pcp.pcp_request_service.entity.AscNodeEntity
|
||||
import space.nstart.pcp.pcp_request_service.entity.EarthCoverageEntity
|
||||
import space.nstart.pcp.pcp_request_service.entity.FlightLineEntity
|
||||
import space.nstart.pcp.pcp_request_service.entity.PDCMEntity
|
||||
import space.nstart.pcp.pcp_request_service.repository.AscNodeRepository
|
||||
import space.nstart.pcp.pcp_request_service.repository.EarthCellViewRepository
|
||||
import space.nstart.pcp.pcp_request_service.repository.EarthCoverageRepository
|
||||
import space.nstart.pcp.pcp_request_service.repository.FlightLineRepository
|
||||
import space.nstart.pcp.pcp_request_service.repository.PDCMRepository
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteICDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
|
||||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
import kotlin.math.PI
|
||||
|
||||
@Service
|
||||
open class SatelliteIcEventService {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||
|
||||
@Autowired
|
||||
private lateinit var satelliteService: SatelliteService
|
||||
|
||||
open fun handlePlacedIcRv(message: SatelliteICDTO) {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
satelliteService.resieveRV(message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
+882
@@ -0,0 +1,882 @@
|
||||
package space.nstart.pcp.pcp_request_service.service
|
||||
|
||||
import ballistics.Ballistics
|
||||
import ballistics.orbitalPoints.timeStepper.RungeStepper
|
||||
import ballistics.types.BallisticsError
|
||||
import ballistics.types.EarthType
|
||||
import ballistics.types.FleghtLineSector
|
||||
import ballistics.types.InitialConditions
|
||||
import ballistics.types.KeplerParams
|
||||
import ballistics.types.OPKatObj
|
||||
import ballistics.types.OrbitalPoint
|
||||
import ballistics.types.PPI
|
||||
import ballistics.types.PointViewParams
|
||||
import ballistics.types.TLE
|
||||
import ballistics.types.VisibilityParametersZRV
|
||||
import ballistics.types.ZRV
|
||||
import ballistics.utils.astro.AstronomerJ2000
|
||||
import ballistics.utils.fromDateTime
|
||||
import ballistics.utils.math.Vector3D
|
||||
import ballistics.utils.toDateTime
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.nstart.dep265.tletools.zeptomoby.core.Globals.sqr
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.kafka.annotation.KafkaListener
|
||||
import org.springframework.stereotype.Service
|
||||
import reactor.core.publisher.Flux
|
||||
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.entity.AscNodeEntity
|
||||
import space.nstart.pcp.pcp_request_service.entity.EarthCellViewsEntity
|
||||
import space.nstart.pcp.pcp_request_service.entity.EarthCoverageEntity
|
||||
import space.nstart.pcp.pcp_request_service.entity.FlightLineEntity
|
||||
import space.nstart.pcp.pcp_request_service.entity.PDCMEntity
|
||||
import space.nstart.pcp.pcp_request_service.model.SquareViewParamsModel
|
||||
import space.nstart.pcp.pcp_request_service.repository.AscNodeRepository
|
||||
import space.nstart.pcp.pcp_request_service.repository.EarthCellViewRepository
|
||||
import space.nstart.pcp.pcp_request_service.repository.EarthCoverageRepository
|
||||
import space.nstart.pcp.pcp_request_service.repository.FlightLineRepository
|
||||
import space.nstart.pcp.pcp_request_service.repository.PDCMRepository
|
||||
import space.nstart.pcp.pcp_request_service.utils.ContourClipService
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.AscNodeDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.ExactTimePositionRequestDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.InitialConditionsDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.ObjDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.PointViewParamDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.PositionDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.RevolutionSign
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.ObjViewRequestDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.RadioVisibilityAreaDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteICDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SquareViewParamDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SquareViewParamsWithPointsDTO
|
||||
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.TargetPositionDTO
|
||||
import tools.jackson.databind.ObjectMapper
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.collections.forEach
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.asin
|
||||
import kotlin.math.atan
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.log
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
import kotlin.math.truncate
|
||||
|
||||
|
||||
@Service
|
||||
class SatelliteService {
|
||||
|
||||
|
||||
@Value("\${settings.calculation-interval:7}")
|
||||
val intervalForward : Int = 7
|
||||
|
||||
@Autowired
|
||||
private lateinit var om: ObjectMapper
|
||||
|
||||
private val logger : Logger = LoggerFactory.getLogger(this::class.java)
|
||||
|
||||
@Autowired
|
||||
private lateinit var pdcmRepository: PDCMRepository
|
||||
@Autowired
|
||||
private lateinit var ascNodeRepository: AscNodeRepository
|
||||
@Autowired
|
||||
private lateinit var flightLineRepository: FlightLineRepository
|
||||
@Autowired
|
||||
private lateinit var earthCoverageRepository: EarthCoverageRepository
|
||||
@Autowired
|
||||
private lateinit var earthCellViewRepository: EarthCellViewRepository
|
||||
|
||||
|
||||
val astro = AstronomerJ2000(EarthType.PZ90d02)
|
||||
|
||||
@KafkaListener(id = "updatedIcFilter", topics = ["\${spring.kafka.template.default-topic}"], groupId = "\${spring.kafka.consumer.group-id}", filter = "updatedIcFilter")
|
||||
fun listener2(data: String?) {
|
||||
|
||||
val tleMessage: TLEDTO =
|
||||
try {
|
||||
val rootNode = om.readTree(data)
|
||||
val payloadNode = rootNode["data"] ?: throw IllegalArgumentException("В сообщении отсутствует поле data")
|
||||
om.treeToValue(payloadNode, TLEDTO::class.java)
|
||||
}
|
||||
catch (e: Exception) {
|
||||
logger.error("Ошибка парсинга сообщения от каталога: ${e.message}")
|
||||
return
|
||||
}
|
||||
|
||||
logger.info("Полученно сообщение об изменении TLE [{}]", tleMessage)
|
||||
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
resieveTLE(tleMessage)
|
||||
}
|
||||
|
||||
logger.info("Выход из KafkaListener")
|
||||
}
|
||||
|
||||
suspend fun resieveTLE(tle: TLEDTO) {
|
||||
val bal = Ballistics()
|
||||
val params = bal.getTLEParams(TLE(tle.first, tle.second), tle.header ?: "")
|
||||
|
||||
logger.info("Начало расчета баллистики по TLE noradId = ${params.noradId}")
|
||||
|
||||
bal.rollMax = (45.0 * PI / 180.0)
|
||||
bal.rollMin = (20.0 * PI / 180.0)
|
||||
|
||||
var br = bal.calculateOrbPoints(
|
||||
TLE(tle1 = tle.first, tle.second),
|
||||
86400.0 * intervalForward
|
||||
)
|
||||
if (br != BallisticsError.OK) {
|
||||
logger.warn("Ошибка при расчете ПДЦМ для noradId=${params.noradId}. Код ошибки: $br")
|
||||
return
|
||||
}
|
||||
|
||||
pdcmRepository.deleteBySatId(params.noradId)
|
||||
pdcmRepository.saveAll(bal.points.map { p -> PDCMEntity(p, params.noradId) })
|
||||
logger.info("Точки орбиты КА ${params.noradId} сохранены в БД")
|
||||
|
||||
ascNodeRepository.deleteBySatId(params.noradId)
|
||||
ascNodeRepository.saveAll(bal.revolutions.map { AscNodeEntity(it, params.noradId) })
|
||||
logger.info("ВУЗы орбиты КА ${params.noradId} сохранены в БД")
|
||||
|
||||
val tInt = bal.points.first().t
|
||||
br = bal.calculateFlightLine(tInt, tInt + 86400 * intervalForward)
|
||||
if (br != BallisticsError.OK) {
|
||||
logger.warn("Ошибка при расчете ТП и ПО для ka=${params.noradId}. Код ошибки: $br")
|
||||
return
|
||||
}
|
||||
flightLineRepository.deleteBySatId(params.noradId)
|
||||
flightLineRepository.saveAll(bal.flightLine.map { FlightLineEntity(it, params.noradId) })
|
||||
|
||||
logger.info("Параметры трассы полеты и полосы обзора КА ${params.noradId} сохранены в БД")
|
||||
|
||||
|
||||
earthCoverageRepository.deleteBySatId(params.noradId)
|
||||
earthCoverageRepository.saveAll(bal.coverings.flatMap { (k,v) -> mergeSectors(v).map { EarthCoverageEntity(it, k , params.noradId) } })
|
||||
logger.info("Параметры покрытия земной поверхности КА ${params.noradId} сохранены в БД")
|
||||
|
||||
prepareEarthCovering(bal, params.noradId)
|
||||
|
||||
bal.clear()
|
||||
logger.info("Расчет для КА ${params.noradId} завершился успешно")
|
||||
}
|
||||
|
||||
|
||||
private fun InitialConditionsDTO.toIC() = InitialConditions(
|
||||
OrbitalPoint(
|
||||
t = fromDateTime(orbPoint.time),
|
||||
vit = orbPoint.revolution.toInt(),
|
||||
r = Vector3D(orbPoint.x,orbPoint.y,orbPoint.z),
|
||||
v = Vector3D(orbPoint.vx,orbPoint.vy,orbPoint.vz)
|
||||
),
|
||||
f81 = f81,
|
||||
sBall = sBall
|
||||
)
|
||||
|
||||
suspend fun resieveRV(rv: SatelliteICDTO) {
|
||||
val bal = Ballistics()
|
||||
|
||||
logger.info("Начало расчета баллистики по RV id = ${rv.satelliteId}")
|
||||
|
||||
bal.rollMax = (45.0 * PI / 180.0)
|
||||
bal.rollMin = (20.0 * PI / 180.0)
|
||||
|
||||
var br = bal.calculateOrbPoints(
|
||||
rv.ic.toIC(),
|
||||
fromDateTime(rv.ic.orbPoint.time),
|
||||
fromDateTime(rv.ic.orbPoint.time)+ 86400 * intervalForward
|
||||
|
||||
)
|
||||
if (br != BallisticsError.OK) {
|
||||
logger.warn("Ошибка при расчете ПДЦМ для noradId=${rv.satelliteId}. Код ошибки: $br")
|
||||
return
|
||||
}
|
||||
|
||||
pdcmRepository.deleteBySatId(rv.satelliteId)
|
||||
pdcmRepository.saveAll(bal.points.map { p -> PDCMEntity(p, rv.satelliteId) })
|
||||
logger.info("Точки орбиты КА ${rv.satelliteId} сохранены в БД")
|
||||
|
||||
ascNodeRepository.deleteBySatId(rv.satelliteId)
|
||||
ascNodeRepository.saveAll(bal.revolutions.map { AscNodeEntity(it, rv.satelliteId) })
|
||||
logger.info("ВУЗы орбиты КА ${rv.satelliteId} сохранены в БД")
|
||||
|
||||
val tInt = bal.points.first().t
|
||||
br = bal.calculateFlightLine(tInt, tInt + 86400 * intervalForward)
|
||||
if (br != BallisticsError.OK) {
|
||||
logger.warn("Ошибка при расчете ТП и ПО для ka=${rv.satelliteId}. Код ошибки: $br")
|
||||
return
|
||||
}
|
||||
flightLineRepository.deleteBySatId(rv.satelliteId)
|
||||
flightLineRepository.saveAll(bal.flightLine.map { FlightLineEntity(it, rv.satelliteId) })
|
||||
|
||||
logger.info("Параметры трассы полеты и полосы обзора КА ${rv.satelliteId} сохранены в БД")
|
||||
|
||||
|
||||
earthCoverageRepository.deleteBySatId(rv.satelliteId)
|
||||
earthCoverageRepository.saveAll(bal.coverings.flatMap { (k,v) -> mergeSectors(v).map { EarthCoverageEntity(it, k , rv.satelliteId) } })
|
||||
logger.info("Параметры покрытия земной поверхности КА ${rv.satelliteId} сохранены в БД")
|
||||
|
||||
prepareEarthCovering(bal, rv.satelliteId)
|
||||
|
||||
bal.clear()
|
||||
logger.info("Расчет для КА ${rv.satelliteId} завершился успешно")
|
||||
}
|
||||
|
||||
|
||||
fun mergeSectors(sectors: List<FleghtLineSector>): List<FleghtLineSector> {
|
||||
if (sectors.isEmpty()) return emptyList()
|
||||
|
||||
val sorted = sectors.sortedBy { it.tStart }
|
||||
val result = mutableListOf<FleghtLineSector>()
|
||||
var current = sorted[0]
|
||||
|
||||
for (i in 1 until sorted.size) {
|
||||
val next = sorted[i]
|
||||
if (abs(next.tStart - current.tStop) < 2) {
|
||||
// Объединяем: расширяем tStop текущего сектора
|
||||
current = FleghtLineSector(current.tStart, maxOf(current.tStop, next.tStop))
|
||||
} else {
|
||||
result.add(current)
|
||||
current = next
|
||||
}
|
||||
}
|
||||
result.add(current)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
fun clear(id : Long): SatelliteBallisticsDeletionSummary {
|
||||
val pdcmDeleted = pdcmRepository.deleteBySatId(id)
|
||||
val ascNodesDeleted = ascNodeRepository.deleteBySatId(id)
|
||||
val flightLinesDeleted = flightLineRepository.deleteBySatId(id)
|
||||
val earthCoverageDeleted = earthCoverageRepository.deleteBySatId(id)
|
||||
val earthCellViewsDeleted = earthCellViewRepository.deleteBySatId(id)
|
||||
val summary = SatelliteBallisticsDeletionSummary(
|
||||
satelliteId = id,
|
||||
pdcmDeleted = pdcmDeleted,
|
||||
ascNodesDeleted = ascNodesDeleted,
|
||||
flightLinesDeleted = flightLinesDeleted,
|
||||
earthCoverageDeleted = earthCoverageDeleted,
|
||||
earthCellViewsDeleted = earthCellViewsDeleted
|
||||
)
|
||||
logger.info(
|
||||
"Deleted ballistics data for satellite: satelliteId={}, pdcm={}, ascNodes={}, flightLines={}, earthCoverage={}, earthCellViews={}",
|
||||
summary.satelliteId,
|
||||
summary.pdcmDeleted,
|
||||
summary.ascNodesDeleted,
|
||||
summary.flightLinesDeleted,
|
||||
summary.earthCoverageDeleted,
|
||||
summary.earthCellViewsDeleted
|
||||
)
|
||||
return summary
|
||||
}
|
||||
|
||||
fun exactTime(satId : Long, request : ExactTimePositionRequestDTO) = Flux.create {
|
||||
sink ->
|
||||
logger.info("Запрос расчета выхода на заданное время для КА $satId ")
|
||||
val points = pdcmRepository.findBySatelliteIdAndTimeBetween(
|
||||
satId,
|
||||
request.time.minusSeconds(100),
|
||||
request.timeStop?.plusSeconds(100) ?: request.time.plusSeconds(100)
|
||||
)
|
||||
if (points.isEmpty())
|
||||
throw CustomErrorException("Нет точек орбиты для КА $satId на интервале ${request.time} - ${request.timeStop ?: request.time}")
|
||||
val stepper = RungeStepper(points.map { p -> p.toOrbitalPoint() }.toMutableList(), EarthType.PZ90d02)
|
||||
var t = request.time
|
||||
val tk = request.timeStop ?: t.plusNanos(5)
|
||||
var step = request.stepMs ?: 125
|
||||
if (step < 1)
|
||||
step = 125
|
||||
while (t <= tk) {
|
||||
|
||||
val op = stepper.calculate(fromDateTime(t))
|
||||
if (op == null) {
|
||||
logger.error("Ошибка расчета выхода на заданное время $t")
|
||||
throw CustomValidationException("Внутренняя ошибка расчета выхода на заданное время")
|
||||
}
|
||||
|
||||
sink.next( OrbPointDTO(
|
||||
toDateTime(op.t),
|
||||
op.vit.toLong(),
|
||||
op.v.x,
|
||||
op.v.y,
|
||||
op.v.z,
|
||||
op.r.x,
|
||||
op.r.y,
|
||||
op.r.z)
|
||||
)
|
||||
t = t.plusNanos(step * 1000000)
|
||||
}
|
||||
if (request.timeStop != null && t.minusNanos(step * 1000000) < tk) {
|
||||
val op = stepper.calculate(fromDateTime(tk))
|
||||
if (op == null) {
|
||||
logger.error("Ошибка расчета выхода на заданное время $t")
|
||||
throw CustomValidationException("Внутренняя ошибка расчета выхода на заданное время")
|
||||
}
|
||||
|
||||
sink.next(OrbPointDTO(
|
||||
toDateTime(op.t),
|
||||
op.vit.toLong(),
|
||||
op.v.x,
|
||||
op.v.y,
|
||||
op.v.z,
|
||||
op.r.x,
|
||||
op.r.y,
|
||||
op.r.z)
|
||||
)
|
||||
}
|
||||
|
||||
sink.complete()
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun getCellCovering(num : Long) : List<SquareViewParamsWithPointsDTO>{
|
||||
return earthCellViewRepository.findByCellNum(num).map { view ->
|
||||
val points = pdcmRepository.findBySatelliteIdAndTimeBetween(
|
||||
view.satelliteId,
|
||||
view.timeBegin.minusSeconds(100),
|
||||
view.timeEnd.plusSeconds(100)
|
||||
)
|
||||
SquareViewParamsWithPointsDTO(
|
||||
view.toDTO(),
|
||||
points.map { it.toDTO() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun mplPoint(req : ObjViewRequestDTO) : Flux<PointViewParamDTO>{
|
||||
|
||||
return Flux.fromIterable(
|
||||
req.satellites
|
||||
.flatMap { sat ->
|
||||
val bal = Ballistics()
|
||||
bal.setOrbitalPoints(
|
||||
loadOrbitalPoints(sat, req.timeStart, req.timeStop),
|
||||
EarthType.PZ90d02
|
||||
)
|
||||
|
||||
bal.rollMin = 0.0
|
||||
bal.rollMax = 45.0 / 180 * PI
|
||||
|
||||
val indexes = mkCovIndexes(listOf(req.obj.position?: PositionDTO()))
|
||||
val sectors = loadEarthCoverageSectors(sat, req.timeStart, req.timeStop, indexes)
|
||||
|
||||
bal.setEarthCoverage(
|
||||
sectors
|
||||
)
|
||||
|
||||
val t1 = req.timeStart
|
||||
val t2 = req.timeStop
|
||||
bal.sunAngleMin = req.sunAngleMin?.let { it / 180 * PI } ?: (-PI / 2.0)
|
||||
|
||||
val r = bal.calculateMPL(
|
||||
fromDateTime(t1),
|
||||
fromDateTime(t2),
|
||||
listOf(OPKatObj(0,0,"",0,
|
||||
(req.obj.position?.lat?:0.0) * PI / 180,
|
||||
(req.obj.position?.long?:0.0) * PI / 180,
|
||||
req.obj.position?.height?:0.0,
|
||||
0.0, 0.0, 0.0
|
||||
)
|
||||
)
|
||||
)
|
||||
if (r != BallisticsError.OK) {
|
||||
logger.error("Ошибка при расчете параметров наблюдения объекта (${req.obj}) для КА $sat. Код ошибки : $r")
|
||||
listOf()
|
||||
} else
|
||||
bal.mpl.map { PointViewParamDTO(
|
||||
noradId = sat,
|
||||
objectId = req.obj.id?:"",
|
||||
time = toDateTime(it.traverz),
|
||||
revolution = it.vit.toLong(),
|
||||
gamma = it.orientation.kren * 180 / PI,
|
||||
sunAngle = it.sunAngle * 180 / PI,
|
||||
range = it.range,
|
||||
revSign = if (it.pv ==0 ) RevolutionSign.ASC else RevolutionSign.DESC
|
||||
) }
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fun mplSquare(req : ObjViewRequestDTO) : Flux<SquareViewParamDTO>{
|
||||
|
||||
logger.info("Запрос МПЛ по площадному объекту")
|
||||
return Flux.fromIterable(
|
||||
req.satellites
|
||||
.flatMap { sat ->
|
||||
val bal = Ballistics()
|
||||
|
||||
bal.setOrbitalPoints(
|
||||
loadOrbitalPoints(sat, req.timeStart, req.timeStop),
|
||||
EarthType.PZ90d02
|
||||
)
|
||||
|
||||
bal.rollMin = 0.0
|
||||
bal.rollMax = 45.0 / 180 * PI
|
||||
|
||||
val contourService = ContourClipService()
|
||||
val contour = contourService.getObjContour(req.obj)
|
||||
if (contour == null || contour.count() < 3) {
|
||||
logger.error("Контур площадного объекта пуст или содержит меньше 3-х точек")
|
||||
listOf()
|
||||
}
|
||||
else {
|
||||
val indexes = mkCovIndexes(contour)
|
||||
val sectors = loadEarthCoverageSectors(sat, req.timeStart, req.timeStop, indexes)
|
||||
|
||||
bal.setEarthCoverage(
|
||||
sectors
|
||||
)
|
||||
val t1 = req.timeStart
|
||||
val t2 = req.timeStop
|
||||
bal.sunAngleMin = req.sunAngleMin?.let { it / 180 * PI } ?: (-PI / 2.0)
|
||||
|
||||
|
||||
val r = bal.calculateMPL(
|
||||
fromDateTime(t1),
|
||||
fromDateTime(t2),
|
||||
contourService.splitPolySides(contour.map { p ->
|
||||
PositionDTO(
|
||||
p.lat / 180 * PI,
|
||||
p.long / 180 * PI,
|
||||
p.height
|
||||
)
|
||||
})
|
||||
)
|
||||
if (r != BallisticsError.OK) {
|
||||
logger.error("Ошибка при расчете параметров наблюдения площадного объекта (${req.obj}) для КА $sat. Код ошибки : $r")
|
||||
listOf()
|
||||
} else
|
||||
squareViewFromMPL(sat, bal.mpl, req.obj.id ?: "")
|
||||
}
|
||||
}
|
||||
).map { square -> square.toDTO() }
|
||||
}
|
||||
|
||||
|
||||
private fun squareViewFromMPL(id : Long, mpl: Iterable<PointViewParams>, n: String): Iterable<SquareViewParamsModel>{
|
||||
|
||||
val svp = mutableListOf<SquareViewParamsModel>()
|
||||
val vp = SquareViewParamsModel(
|
||||
tN = -1.0,
|
||||
tK = 0.0,
|
||||
objID = n,
|
||||
id = id
|
||||
)
|
||||
sortPointViews(mpl).forEach { m ->
|
||||
if (m.traverz - vp.tK > 60.0*3) {
|
||||
// начало нового интервала наблюдения! Надо сохранить старый, если такой есть.
|
||||
if (vp.tN > 0)
|
||||
svp.add(vp.clone())
|
||||
|
||||
vp.tN = m.traverz
|
||||
vp.vitN = m.vit
|
||||
vp.tK = m.traverz
|
||||
vp.vitK = m.vit
|
||||
vp.pvN = if (m.pv == 0) RevolutionSign.ASC else RevolutionSign.DESC
|
||||
vp.pvK = if (m.pv == 0) RevolutionSign.ASC else RevolutionSign.DESC
|
||||
vp.krenMin = m.orientation.kren
|
||||
vp.krenMax = m.orientation.kren
|
||||
} else {
|
||||
vp.tK = m.traverz
|
||||
vp.vitK = m.vit
|
||||
|
||||
vp.pvK = if (m.pv == 0) RevolutionSign.ASC else RevolutionSign.DESC
|
||||
if (vp.krenMax < m.orientation.kren)
|
||||
vp.krenMax = m.orientation.kren
|
||||
|
||||
if (vp.krenMin > m.orientation.kren)
|
||||
vp.krenMin = m.orientation.kren
|
||||
}
|
||||
}
|
||||
if (vp.tN > 0)
|
||||
svp.add(vp.clone())
|
||||
return svp
|
||||
}
|
||||
|
||||
internal fun sortPointViews(mpl: Iterable<PointViewParams>): List<PointViewParams> =
|
||||
mpl.sortedWith(
|
||||
compareBy<PointViewParams>(
|
||||
{ it.traverz },
|
||||
{ it.vit },
|
||||
{ it.pointNumb },
|
||||
{ it.pv },
|
||||
{ it.orientation.kren }
|
||||
)
|
||||
)
|
||||
|
||||
private fun loadOrbitalPoints(satelliteId: Long, timeStart: LocalDateTime, timeStop: LocalDateTime) =
|
||||
pdcmRepository.findBySatelliteIdAndTimeBetween(satelliteId, timeStart, timeStop)
|
||||
.sortedWith(
|
||||
compareBy<PDCMEntity>(
|
||||
{ it.time },
|
||||
{ it.revolution },
|
||||
{ it.id ?: Long.MAX_VALUE }
|
||||
)
|
||||
)
|
||||
.map { it.toOrbitalPoint() }
|
||||
|
||||
private fun loadEarthCoverageSectors(
|
||||
satelliteId: Long,
|
||||
timeStart: LocalDateTime,
|
||||
timeStop: LocalDateTime,
|
||||
cellIndexes: List<Long>
|
||||
): Map<Int, MutableList<FleghtLineSector>> =
|
||||
earthCoverageRepository.findBySatelliteIdAndTStartGreaterThanAndTStopLessThanAndCellNumberIn(
|
||||
satelliteId,
|
||||
timeStart,
|
||||
timeStop,
|
||||
cellIndexes
|
||||
)
|
||||
.sortedWith(
|
||||
compareBy<EarthCoverageEntity>(
|
||||
{ it.cellNumber },
|
||||
{ it.tStart },
|
||||
{ it.tStop },
|
||||
{ it.id ?: Long.MAX_VALUE }
|
||||
)
|
||||
)
|
||||
.groupBy { it.cellNumber.toInt() }
|
||||
.entries
|
||||
.sortedBy { it.key }
|
||||
.associateTo(linkedMapOf()) { (cellNumber, views) ->
|
||||
cellNumber to views.map { view ->
|
||||
FleghtLineSector(
|
||||
fromDateTime(view.tStart),
|
||||
fromDateTime(view.tStop)
|
||||
)
|
||||
}.toMutableList()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
fun getBInd(b: Double): Long {
|
||||
var b2: Double = b * 180.0 / PI
|
||||
b2 = 90.0 - b2
|
||||
var k: Long = truncate(b2 / 2.0).toLong()
|
||||
if (k > 89) k = 89
|
||||
return k
|
||||
}
|
||||
|
||||
fun getLInd(l2: Double): Long {
|
||||
var l: Double = l2 * 180.0 / PI
|
||||
if (l < 0) l += 360.0
|
||||
var k: Long = truncate(l / 2.0).toLong()
|
||||
if (k > 179) k = 179
|
||||
return k
|
||||
}
|
||||
|
||||
fun getInd(
|
||||
b: Double,
|
||||
l: Double,
|
||||
): Long {
|
||||
return getBInd(b) * 180 + getLInd(l)
|
||||
}
|
||||
|
||||
|
||||
fun mkCovIndexes(contour : Iterable<PositionDTO>) : List<Long>{
|
||||
val indexes = mutableListOf<Long>()
|
||||
var bMin = 100.0
|
||||
var bMax = -100.0
|
||||
var lMin = 370.0
|
||||
var lMax = -370.0
|
||||
|
||||
contour.forEach { c ->
|
||||
val b = c.lat
|
||||
val l = c.long
|
||||
|
||||
if (b < bMin) bMin = b
|
||||
if (b > bMax) bMax = b
|
||||
if (l < lMin) lMin = l
|
||||
if (l > lMax) lMax = l
|
||||
}
|
||||
|
||||
while (bMin < bMax + 0.6){
|
||||
|
||||
var l = lMin
|
||||
while (l < lMax + 0.6){
|
||||
val ind = getInd(bMin * PI / 180, l * PI / 180)
|
||||
if (!indexes.contains(ind))
|
||||
indexes.add(ind)
|
||||
l += 0.5
|
||||
}
|
||||
|
||||
bMin += 0.5
|
||||
}
|
||||
return indexes
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
fun getBInd2(b: Double): Long {
|
||||
var b2: Double = b * 180.0 / PI
|
||||
b2 = 90.0 - b2
|
||||
var k: Long = truncate(b2).toLong()
|
||||
if (k > 180) k = 180
|
||||
return k
|
||||
}
|
||||
|
||||
fun getLInd2(l2: Double): Long {
|
||||
var l: Double = l2 * 180.0 / PI
|
||||
if (l < 0) l += 360.0
|
||||
var k: Long = truncate(l).toLong()
|
||||
if (k > 359) k = 359
|
||||
return k
|
||||
}
|
||||
|
||||
fun getInd2(
|
||||
b: Double,
|
||||
l: Double,
|
||||
): Long {
|
||||
return getBInd2(b) * 360 + getLInd2(l)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
fun prepareEarthCovering(bal : Ballistics, satId : Long){
|
||||
|
||||
logger.info("Начало расчета покрытия ячеек земной поверхности для КА $satId")
|
||||
|
||||
earthCellViewRepository.deleteBySatId(satId)
|
||||
logger.info("Функционал не используется. Старые результаты удалены.")
|
||||
return
|
||||
|
||||
// val contourService = ContourClipService()
|
||||
// val step = 1.0
|
||||
//
|
||||
// bal.sunAngleMin = -PI/2
|
||||
//
|
||||
// val num : Long = 1
|
||||
// var b = -90 + step
|
||||
// while (b <= 90){
|
||||
// var l = 0.0
|
||||
// val line = mutableListOf<EarthCellViewsEntity>()
|
||||
// while (l <= 360){
|
||||
// val cellInd = getInd2(b * PI / 180.0,l* PI / 180.0)
|
||||
// val t1 = bal.points.first().t
|
||||
// val t2 = bal.points.last().t
|
||||
// val cont : String = "POLYGON (($l $b, ${l-1} ${b}, ${l-1} ${b-1}, $l ${b-1}, $l $b))"
|
||||
// val contour = contourService.getObjContour(ObjDTO(contourWKT = cont))
|
||||
// if (contour == null || contour.count() < 3) {
|
||||
// logger.error("Контур площадного объекта пуст или содержит меньше 3-х точек")
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// val objs = contourService.splitPolySides(contour.map { p ->
|
||||
// PositionDTO(
|
||||
// p.lat / 180 * PI,
|
||||
// p.long / 180 * PI,
|
||||
// p.height
|
||||
// )
|
||||
// }
|
||||
// )
|
||||
// val r = bal.calculateMPL(
|
||||
// t1,
|
||||
// t2,
|
||||
// objs
|
||||
//
|
||||
// )
|
||||
// val views =
|
||||
// if (r != BallisticsError.OK) {
|
||||
// logger.error("Ошибка при расчете параметров наблюдения площадного объекта (${cellInd}) для КА $satId. Код ошибки : $r")
|
||||
// listOf()
|
||||
// } else
|
||||
// squareViewFromMPL(satId, bal.mpl, "")
|
||||
//
|
||||
// for (view in views) {
|
||||
// line.add(
|
||||
// EarthCellViewsEntity(
|
||||
// null,
|
||||
// satId,
|
||||
// cellInd,
|
||||
// view.vitN.toLong(),
|
||||
// toDateTime(view.tN),
|
||||
// if(view.pvN == RevolutionSign.ASC) 0 else 1,
|
||||
// view.vitK.toLong(),
|
||||
// toDateTime(view.tK),
|
||||
// if(view.pvK == RevolutionSign.ASC) 1 else 0,
|
||||
// view.krenMin * 180 / PI,
|
||||
// view.krenMax * 180 / PI,
|
||||
// cont
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// l += step
|
||||
// if (line.isEmpty()) {
|
||||
// logger.info("нет наблюдений ячейки $cont")
|
||||
// }
|
||||
// earthCellViewRepository.saveAll(line)
|
||||
// line.clear()
|
||||
// }
|
||||
// b += step
|
||||
// }
|
||||
// logger.info("Конец расчета покрытия ячеек земной поверхности для КА $satId")
|
||||
}
|
||||
|
||||
fun getOrbPoints(id : Long, tn : LocalDateTime?, tk : LocalDateTime?) =
|
||||
pdcmRepository.findBySatelliteIdAndTimeBetween(
|
||||
id,
|
||||
tn?: LocalDateTime.now(),
|
||||
tk?: LocalDateTime.now().plusDays(1))
|
||||
.map { it.toDTO()
|
||||
}
|
||||
|
||||
fun getOrbitAvailability(time: LocalDateTime?): List<SatelliteOrbitAvailabilityDTO> =
|
||||
if (time == null) {
|
||||
pdcmRepository.findSatelliteOrbitAvailability()
|
||||
} else {
|
||||
pdcmRepository.findSatelliteOrbitAvailabilityAtTime(time)
|
||||
}
|
||||
|
||||
fun getFL(id : Long, tn : LocalDateTime?, tk : LocalDateTime?, step : Double?) =
|
||||
flightLineRepository.findBySatelliteIdAndTimeBetween(id,
|
||||
tn?: LocalDateTime.now(),
|
||||
tk?: LocalDateTime.now().plusDays(1)
|
||||
)
|
||||
.map { it.toDTO() }
|
||||
|
||||
|
||||
|
||||
fun getRVA(id : Long, tn : LocalDateTime, tk : LocalDateTime) : List<RadioVisibilityAreaDTO> {
|
||||
|
||||
val bal = Ballistics()
|
||||
|
||||
val points = pdcmRepository.findBySatelliteIdAndTimeBetween(id,
|
||||
tn,
|
||||
tk).map { it.toOrbitalPoint() }
|
||||
|
||||
if (points.isEmpty())
|
||||
throw CustomErrorException("Нет ПДЦМ для КА $id на интервале времени $tn - $tk")
|
||||
|
||||
bal.setOrbitalPoints(points,
|
||||
EarthType.PZ90d02)
|
||||
|
||||
val tstart = points.first().t
|
||||
val tstop = points.last().t
|
||||
|
||||
bal.rollMin = 0.0
|
||||
bal.rollMax = 45.0 / 180 * PI
|
||||
|
||||
|
||||
val r = bal.calculateZRV(
|
||||
listOf(PPI(1,1,0.35, 0.45, 100.0, 0.0, PI/2)),
|
||||
tstart,
|
||||
tstop
|
||||
)
|
||||
if (r != BallisticsError.OK) {
|
||||
logger.error("Ошибка при расчете параметров ЗРВ : $r")
|
||||
return listOf()
|
||||
} else
|
||||
return bal.zrv.map { mkRVA(it, id)}
|
||||
|
||||
}
|
||||
|
||||
fun mkRVA(p : ZRV, id : Long) = RadioVisibilityAreaDTO(
|
||||
noradId = id,
|
||||
stationId = p.ppi.toLong(),
|
||||
revolution = p.vit.toLong(),
|
||||
onStart = mkTP(p.zoneIn),
|
||||
onMaximum = mkTP(p.zoneMax),
|
||||
onStop = mkTP(p.zoneOut)
|
||||
)
|
||||
fun mkTP(p : VisibilityParametersZRV) = TargetPositionDTO(
|
||||
toDateTime(p.t),
|
||||
p.elevation * 180 / PI,
|
||||
p.azimuth * 180 / PI,
|
||||
p.range
|
||||
)
|
||||
|
||||
|
||||
|
||||
fun calculateKeplerParams(point: OrbitalPoint): KeplerParams {
|
||||
val res = KeplerParams()
|
||||
|
||||
val aL00 = 6.25648106E+7
|
||||
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 getAscNodes(id : Long, tn : LocalDateTime?, tk : LocalDateTime?) : List<AscNodeDTO> {
|
||||
|
||||
return ascNodeRepository.findBySatelliteIdAndTimeBetween(
|
||||
id,
|
||||
tn ?: LocalDateTime.now(),
|
||||
tk ?: LocalDateTime.now().plusDays(1)
|
||||
)
|
||||
.map {
|
||||
val keps = calculateKeplerParams(
|
||||
OrbitalPoint(
|
||||
fromDateTime(it.time),
|
||||
1,
|
||||
Vector3D(it.x,it.y,it.z),
|
||||
Vector3D(it.vx,it.vy,it.vz)
|
||||
))
|
||||
it.toDTO(keps, astro.earth.ekvRadius)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class SatelliteBallisticsDeletionSummary(
|
||||
val satelliteId: Long,
|
||||
val pdcmDeleted: Int,
|
||||
val ascNodesDeleted: Int,
|
||||
val flightLinesDeleted: Int,
|
||||
val earthCoverageDeleted: Int,
|
||||
val earthCellViewsDeleted: Int
|
||||
)
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package space.nstart.pcp.pcp_request_service.service
|
||||
|
||||
import ballistics.Ballistics
|
||||
import ballistics.types.BallisticsError
|
||||
import ballistics.types.PPI
|
||||
import ballistics.types.TLE
|
||||
import ballistics.types.TLEParams
|
||||
import ballistics.types.VisibilityParametersZRV
|
||||
import ballistics.types.ZRV
|
||||
import ballistics.utils.fromDateTime
|
||||
import ballistics.utils.toDateTime
|
||||
import org.springframework.stereotype.Service
|
||||
import space.nstart.pcp.pcp_request_service.configuration.CustomErrorException
|
||||
import space.nstart.pcp.pcp_request_service.configuration.CustomValidationException
|
||||
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.TLEDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TargetPositionDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TleRvaRequestDTO
|
||||
import kotlin.math.PI
|
||||
|
||||
|
||||
@Service
|
||||
class TLEService() {
|
||||
|
||||
fun mkTP(p : VisibilityParametersZRV) = TargetPositionDTO(
|
||||
toDateTime(p.t),
|
||||
p.elevation * 180 / PI,
|
||||
p.azimuth * 180 / PI,
|
||||
p.range
|
||||
)
|
||||
|
||||
fun mkRVA(p : ZRV, id : Long) = RadioVisibilityAreaDTO(
|
||||
noradId = id,
|
||||
stationId = p.ppi.toLong(),
|
||||
revolution = p.vit.toLong(),
|
||||
onStart = mkTP(p.zoneIn),
|
||||
onMaximum = mkTP(p.zoneMax),
|
||||
onStop = mkTP(p.zoneOut)
|
||||
)
|
||||
|
||||
|
||||
fun parseTLE(tle: TLEDTO) : TLEParams{
|
||||
val bal = Ballistics()
|
||||
return bal.getTLEParams(TLE(tle.first, tle.second), tle.header?:"empty")
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun rva(req : TleRvaRequestDTO) : Iterable<RadioVisibilityAreaDTO> {
|
||||
|
||||
val bal = Ballistics()
|
||||
var tleParams : TLEParams
|
||||
try {
|
||||
tleParams = bal.getTLEParams(TLE(req.tle.first, req.tle.second))
|
||||
if (req.timeStart >= req.timeStop || req.timeStart < tleParams.time)
|
||||
throw CustomValidationException("Ошибка задания интервала расчета")
|
||||
}catch (ex : Exception){
|
||||
throw CustomValidationException("Ошибка формата TLE : ${ex.message}")
|
||||
}
|
||||
val duration = fromDateTime(req.timeStop) - fromDateTime(req.timeStart)
|
||||
val r = bal.calculateOrbPoints(TLE(req.tle.first, req.tle.second), duration)
|
||||
|
||||
if (r != BallisticsError.OK)
|
||||
throw CustomErrorException("Ошибка расчета ПДЦМ : $r")
|
||||
|
||||
val rz = bal.calculateZRV(
|
||||
listOf(PPI(
|
||||
req.station.number.toInt(),
|
||||
0,
|
||||
req.station.position.lat * PI / 180.0,
|
||||
req.station.position.long * PI / 180.0,
|
||||
req.station.position.height,
|
||||
req.station.elevationMin * PI / 180.0,
|
||||
req.station.elevationMax * PI / 180.0)
|
||||
),
|
||||
fromDateTime(req.timeStart),
|
||||
fromDateTime(req.timeStop)
|
||||
)
|
||||
|
||||
if (rz != BallisticsError.OK)
|
||||
throw CustomErrorException("Ошибка расчета зон радио-видимости : $rz")
|
||||
|
||||
return bal.zrv.map { mkRVA(it, tleParams.noradId) }
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
package space.nstart.pcp.pcp_request_service.utils
|
||||
|
||||
import ballistics.types.OPKatObj
|
||||
import clipper.IntPoint
|
||||
import clipper.Path
|
||||
import clipper.containsPoint
|
||||
import org.locationtech.jts.io.WKTReader
|
||||
import space.nstart.pcp.pcp_request_service.configuration.CustomValidationException
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.ObjDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.PositionDTO
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.sqrt
|
||||
|
||||
|
||||
class ContourClipService {
|
||||
|
||||
|
||||
|
||||
fun prceWKT(wkt : String) : Iterable<PositionDTO>?{
|
||||
try {
|
||||
val contour = mutableListOf<PositionDTO>()
|
||||
val reader = WKTReader()
|
||||
val geom = reader.read(wkt)
|
||||
for (c in geom.coordinates)
|
||||
contour.add(PositionDTO(c.y, c.x, c.z))
|
||||
if (contour.size < 3)
|
||||
return null
|
||||
return contour
|
||||
} catch (ex : Exception){
|
||||
throw CustomValidationException("Ошибка пазбора WKT-формата")
|
||||
}
|
||||
}
|
||||
|
||||
fun getObjContour(obj : ObjDTO) : Iterable<PositionDTO>?{
|
||||
obj.contourWKT?.let { return prceWKT(obj.contourWKT!!) }
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
fun splitPolySides(poly : Iterable<PositionDTO>, internal : Boolean = true) : Iterable<OPKatObj>{
|
||||
|
||||
// return poly.map { p1 ->
|
||||
// OPKatObj(
|
||||
// 1, 1, "", 1, p1.lat, p1.long,
|
||||
// 0.0, 0.0, PI / 2, -PI / 2
|
||||
// )
|
||||
// }
|
||||
val res = mutableListOf<OPKatObj>()
|
||||
for (i in 1 ..poly.count()-1){
|
||||
res.addAll(splitLine(poly.elementAt(i-1), poly.elementAt(i)))
|
||||
}
|
||||
// if (internal)
|
||||
res.addAll(splitPoly(poly, 2.0 * PI / 180.0))
|
||||
return res
|
||||
}
|
||||
|
||||
private fun splitLine(p1 : PositionDTO, p2 : PositionDTO) : Iterable<OPKatObj>{
|
||||
var x1 = p1.long
|
||||
var x2 = p2.long
|
||||
if (abs(x1 - x2) > PI){
|
||||
if (x1 < x2)
|
||||
x1 += 2 * PI
|
||||
else
|
||||
x2 = 2 * PI
|
||||
}
|
||||
if (x1 > x2){
|
||||
val buf = x1
|
||||
x1 = x2
|
||||
x2 = buf
|
||||
}
|
||||
var y1 = p1.lat
|
||||
var y2 = p2.lat
|
||||
if (y1 > y2){
|
||||
val buf = y1
|
||||
y1 = y2
|
||||
y2 = buf
|
||||
}
|
||||
val dx = x2 - x1
|
||||
val dy = y2 - y1
|
||||
val dl = sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1))
|
||||
val res = mutableListOf<OPKatObj>()
|
||||
res.add(OPKatObj(1, 1, "", 1, p1.lat, p1.long, 0.0, 0.0, PI / 2, -PI / 2))
|
||||
if (dl > 0.01) {
|
||||
res.add(OPKatObj(1, 1, "", 2, p2.lat, p2.long, 0.0, 0.0, PI / 2, -PI / 2))
|
||||
var pn = 3
|
||||
val step = 0.1 * PI / 180
|
||||
if (dx > dy) {
|
||||
var x = x1 + step
|
||||
val k = dy / dx
|
||||
val b = y2 - k * x2
|
||||
(0..(dx / step).toInt()).forEach { i ->
|
||||
if (x < x2) {
|
||||
val y = k * x + b
|
||||
res.add(OPKatObj(1, 1, "", pn, y, x, 0.0, 0.0, PI / 2, -PI / 2))
|
||||
++pn
|
||||
}
|
||||
x += step
|
||||
}
|
||||
} else {
|
||||
if (dx < 0.000001) {
|
||||
var y = y1 + step
|
||||
(0..(dy / step).toInt()).forEach { i ->
|
||||
if (y < y2) {
|
||||
res.add(OPKatObj(1, 1, "", pn, y, x1, 0.0, 0.0, PI / 2, -PI / 2))
|
||||
++pn
|
||||
}
|
||||
y += step
|
||||
}
|
||||
} else {
|
||||
var y = y1 + step
|
||||
val k = dy / dx
|
||||
val b = y2 - k * x2
|
||||
(0..(dy / step).toInt()).forEach { i ->
|
||||
if (y < y2) {
|
||||
val x = (y - b) / k
|
||||
res.add(OPKatObj(1, 1, "", pn, y, x, 0.0, 0.0, PI / 2, -PI / 2))
|
||||
++pn
|
||||
}
|
||||
y += step
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
private fun splitPoly(poly : Iterable<PositionDTO>, step : Double? = null) : Iterable<OPKatObj> {
|
||||
val res = mutableListOf<OPKatObj>()
|
||||
val path = Path()
|
||||
val clipVal = 1000000
|
||||
var bMin = poly.minOf { it.lat }
|
||||
val bMax = poly.maxOf { it.lat }
|
||||
var lMin = poly.minOf { it.long }
|
||||
var lMax = poly.maxOf { it.long }
|
||||
for (p in poly) {
|
||||
path.add(IntPoint((p.lat * clipVal).toLong(), (p.long * clipVal).toLong()))
|
||||
if (step != null)
|
||||
res.add(OPKatObj(1, 1, "", 1, p.lat, p.long, 0.0, 0.0, PI / 2, -PI / 2))
|
||||
}
|
||||
path.add(path.first())
|
||||
if ((lMin < 0 && lMax > 0) && (lMax - lMin > PI)) {
|
||||
val b = lMax
|
||||
lMax = lMin + PI * 2
|
||||
lMin = b
|
||||
}
|
||||
if (lMax - lMin > PI) {
|
||||
val b = lMax
|
||||
lMax = lMin
|
||||
lMin = b - 2 * PI
|
||||
}
|
||||
var stepL : Double
|
||||
var stepB : Double
|
||||
if (step != null){
|
||||
stepL = step
|
||||
stepB = step
|
||||
}else {
|
||||
stepL = (lMax - lMin) / 10
|
||||
if (stepL > 0.1 * PI / 180)
|
||||
stepL = 0.1 * PI / 180
|
||||
stepB = (bMax - bMin) / 10
|
||||
if (stepB > 0.1 * PI / 180)
|
||||
stepB = 0.1 * PI / 180
|
||||
}
|
||||
while (bMin <= bMax) {
|
||||
var l = lMin
|
||||
while (l <= lMax) {
|
||||
if (path.containsPoint(IntPoint( (bMin * clipVal).toLong(), (l * clipVal).toLong())) == 1)
|
||||
res.add(OPKatObj(1, 1, "", 1, bMin, l, 0.0, 0.0, PI / 2, -PI / 2))
|
||||
l += stepL
|
||||
}
|
||||
bMin += stepB
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
spring:
|
||||
application:
|
||||
name: pcp-ballistics-service
|
||||
profiles:
|
||||
default: local
|
||||
config:
|
||||
import: "configserver:"
|
||||
cloud:
|
||||
config:
|
||||
uri: ${CONFIG_SERVER_URI:http://localhost:8888}
|
||||
fail-fast: ${CONFIG_SERVER_FAIL_FAST:true}
|
||||
profile: ${SPRING_CLOUD_CONFIG_PROFILE:${SPRING_PROFILES_ACTIVE:${spring.profiles.default}}}
|
||||
label: ${SPRING_CLOUD_CONFIG_LABEL:dev}
|
||||
@@ -0,0 +1,16 @@
|
||||
CREATE TABLE IF NOT EXISTS pdcm (
|
||||
pdcm_id BIGSERIAL PRIMARY KEY,
|
||||
satellite_id BIGINT NOT NULL,
|
||||
t TIMESTAMP NOT NULL,
|
||||
revolution BIGINT NOT NULL,
|
||||
x DOUBLE PRECISION,
|
||||
y DOUBLE PRECISION,
|
||||
z DOUBLE PRECISION,
|
||||
vx DOUBLE PRECISION,
|
||||
vy DOUBLE PRECISION,
|
||||
vz DOUBLE PRECISION
|
||||
);
|
||||
|
||||
CREATE INDEX idx_pdcm_satellite_id ON pdcm(satellite_id);
|
||||
CREATE INDEX idx_pdcm_t ON pdcm(t);
|
||||
CREATE INDEX idx_pdcm_revolution ON pdcm(revolution);
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
CREATE TABLE IF NOT EXISTS asc_node (
|
||||
asc_node_id BIGSERIAL PRIMARY KEY,
|
||||
satellite_id BIGINT NOT NULL,
|
||||
t TIMESTAMP NOT NULL,
|
||||
revolution BIGINT NOT NULL,
|
||||
longitude DOUBLE PRECISION,
|
||||
height DOUBLE PRECISION,
|
||||
x DOUBLE PRECISION,
|
||||
y DOUBLE PRECISION,
|
||||
z DOUBLE PRECISION,
|
||||
vx DOUBLE PRECISION,
|
||||
vy DOUBLE PRECISION,
|
||||
vz DOUBLE PRECISION
|
||||
);
|
||||
CREATE INDEX idx_asc_node_satellite_id ON asc_node(satellite_id);
|
||||
CREATE INDEX idx_asc_node_t ON asc_node(t);
|
||||
CREATE INDEX idx_asc_node_revolution ON asc_node(revolution);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS flight_line (
|
||||
flight_line_id BIGSERIAL PRIMARY KEY,
|
||||
satellite_id BIGINT NOT NULL,
|
||||
t TIMESTAMP NOT NULL,
|
||||
revolution BIGINT NOT NULL,
|
||||
revolution_sign VARCHAR(4) NOT NULL CHECK (revolution_sign IN ('ASC', 'DESC')),
|
||||
longitude DOUBLE PRECISION,
|
||||
latitude DOUBLE PRECISION,
|
||||
longitude_outer_left DOUBLE PRECISION,
|
||||
latitude_outer_left DOUBLE PRECISION,
|
||||
longitude_inner_left DOUBLE PRECISION,
|
||||
latitude_inner_left DOUBLE PRECISION,
|
||||
longitude_inner_right DOUBLE PRECISION,
|
||||
latitude_inner_right DOUBLE PRECISION,
|
||||
longitude_outer_right DOUBLE PRECISION,
|
||||
latitude_outer_right DOUBLE PRECISION
|
||||
);
|
||||
CREATE INDEX idx_flight_line_satellite_id ON flight_line(satellite_id);
|
||||
CREATE INDEX idx_flight_line_t ON flight_line(t);
|
||||
CREATE INDEX idx_flight_line_revolution ON flight_line(revolution);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS earth_coverage (
|
||||
earth_coverage_id BIGSERIAL PRIMARY KEY,
|
||||
satellite_id BIGINT NOT NULL,
|
||||
cell_number BIGINT NOT NULL,
|
||||
t_start TIMESTAMP NOT NULL,
|
||||
t_stop TIMESTAMP NOT NULL
|
||||
);
|
||||
CREATE INDEX idx_earth_coverage_satellite_id ON earth_coverage(satellite_id);
|
||||
CREATE INDEX idx_earth_coverage_t ON earth_coverage(t_start);
|
||||
CREATE INDEX idx_earth_coverage_cell_number ON earth_coverage(cell_number);
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
CREATE EXTENSION IF NOT EXISTS postgis;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cell_covering (
|
||||
cell_covering_id BIGSERIAL PRIMARY KEY,
|
||||
satellite_id BIGINT NOT NULL,
|
||||
cell_num BIGINT NOT NULL,
|
||||
rev_begin BIGINT NOT NULL,
|
||||
time_begin TIMESTAMP NOT NULL,
|
||||
rev_sign_begin INT NOT NULL,
|
||||
rev_end BIGINT NOT NULL,
|
||||
time_end TIMESTAMP NOT NULL,
|
||||
rev_sign_end INT NOT NULL,
|
||||
roll_min DOUBLE PRECISION,
|
||||
roll_max DOUBLE PRECISION,
|
||||
contour_wkt TEXT NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE cell_covering ADD COLUMN IF NOT EXISTS contour_geom geometry(Polygon, 4326)
|
||||
GENERATED ALWAYS AS (ST_GeomFromText(contour_wkt)::geometry(Polygon, 4326)) STORED;
|
||||
|
||||
CREATE INDEX idx_cell_covering_cell_num ON cell_covering(cell_num);
|
||||
CREATE INDEX idx_cell_covering_satellite_id ON cell_covering(satellite_id);
|
||||
CREATE INDEX idx_cell_covering_contour_geom ON cell_covering USING GIST (contour_geom);
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package space.nstart.pcp.pcp_request_service
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
|
||||
@SpringBootTest
|
||||
class PcpBallisticsServiceApplicationTests {
|
||||
|
||||
@Test
|
||||
fun contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package space.nstart.pcp.pcp_request_service.message
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.readText
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class SatelliteDeletedKafkaListenerGroupIdTest {
|
||||
|
||||
private val listeners = mapOf(
|
||||
"slots-service" to ListenerExpectation(
|
||||
path = "services/slots-service/src/main/kotlin/space/nstart/pcp/slots_service/service/SatelliteDeletedKafkaListener.kt",
|
||||
groupId = "pcp-slots-service-satellite-deleted",
|
||||
hasEventFilter = true
|
||||
),
|
||||
"pcp-ballistics-service" to ListenerExpectation(
|
||||
path = "services/pcp-ballistics-service/src/main/kotlin/space/nstart/pcp/pcp_request_service/message/SatelliteDeletedKafkaListener.kt",
|
||||
groupId = "pcp-ballistics-service-satellite-deleted",
|
||||
hasEventFilter = true
|
||||
),
|
||||
"tle-monitoring-service" to ListenerExpectation(
|
||||
path = "services/tle-monitoring-service/src/main/kotlin/space/nstart/pcp/tle_monitoring_service/message/SatelliteDeletedKafkaListener.kt",
|
||||
groupId = "pcp-tle-monitoring-service-satellite-deleted",
|
||||
hasEventFilter = true
|
||||
),
|
||||
"pcp-complex-mission-service" to ListenerExpectation(
|
||||
path = "services/pcp-complex-mission-service/src/main/kotlin/space/nstart/pcp/pcp_satellites_service/service/SatelliteDeletedKafkaListener.kt",
|
||||
groupId = "pcp-complex-mission-service-satellite-deleted"
|
||||
),
|
||||
"pcp-mission-planing-service" to ListenerExpectation(
|
||||
path = "services/pcp-mission-planing-service/src/main/kotlin/space/nstart/pcp/pcp_mission_planing_service/service/SatelliteDeletedKafkaListener.kt",
|
||||
groupId = "pcp-mission-planing-service-satellite-deleted"
|
||||
),
|
||||
"pcp-dynamic-plan-service" to ListenerExpectation(
|
||||
path = "services/pcp-dynamic-plan-service/src/main/kotlin/space/nstart/pcp/complan/service/SatelliteDeletedKafkaListener.kt",
|
||||
groupId = "pcp-dynamic-plan-service-satellite-deleted"
|
||||
)
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `satellite deleted listeners use unique explicit consumer groups`() {
|
||||
val resolvedGroupIds = listeners.mapValues { (_, expectation) ->
|
||||
val source = projectRoot().resolve(expectation.path).readText()
|
||||
val annotationBody = kafkaListenerAnnotationBody(source)
|
||||
val groupReference = annotationParameter(annotationBody, "groupId")
|
||||
assertNotNull(groupReference, "SatelliteDeletedEvent listener must define explicit groupId: ${expectation.path}")
|
||||
assertEquals("SATELLITE_DELETED_CONSUMER_GROUP", groupReference)
|
||||
assertTrue(
|
||||
annotationBody.contains("""topics = ["\${'$'}{app.kafka.topics.satellites:pcp.satellites}"]"""),
|
||||
"SatelliteDeletedEvent listener must keep satellite topic: ${expectation.path}"
|
||||
)
|
||||
if (expectation.hasEventFilter) {
|
||||
assertTrue(
|
||||
annotationBody.contains("""filter = "satelliteDeletedFilter""""),
|
||||
"SatelliteDeletedEvent listener must keep event filter: ${expectation.path}"
|
||||
)
|
||||
}
|
||||
|
||||
val groupId = consumerGroupConstantValue(source)
|
||||
assertEquals(expectation.groupId, groupId)
|
||||
groupId
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
resolvedGroupIds.size,
|
||||
resolvedGroupIds.values.toSet().size,
|
||||
"SatelliteDeletedEvent consumer groups must be unique per service: $resolvedGroupIds"
|
||||
)
|
||||
}
|
||||
|
||||
private fun kafkaListenerAnnotationBody(source: String): String {
|
||||
val match = Regex("@KafkaListener\\((.*?)\\)", RegexOption.DOT_MATCHES_ALL).find(source)
|
||||
assertNotNull(match, "SatelliteDeletedEvent listener must have @KafkaListener")
|
||||
return match.groupValues[1]
|
||||
}
|
||||
|
||||
private fun annotationParameter(annotationBody: String, parameterName: String): String? =
|
||||
Regex("""$parameterName\s*=\s*([^,\n)]+)""")
|
||||
.find(annotationBody)
|
||||
?.groupValues
|
||||
?.get(1)
|
||||
?.trim()
|
||||
|
||||
private fun consumerGroupConstantValue(source: String): String {
|
||||
val match = Regex("""private const val SATELLITE_DELETED_CONSUMER_GROUP\s*=\s*"([^"]+)"""").find(source)
|
||||
assertNotNull(match, "SatelliteDeletedEvent listener must declare SATELLITE_DELETED_CONSUMER_GROUP")
|
||||
return match.groupValues[1]
|
||||
}
|
||||
|
||||
private fun projectRoot(): Path {
|
||||
generateSequence(Path.of(System.getProperty("user.dir")).toAbsolutePath()) { it.parent }
|
||||
.firstOrNull { Files.exists(it.resolve("settings.gradle.kts")) }
|
||||
?.let { return it }
|
||||
|
||||
error("Cannot locate project root from ${System.getProperty("user.dir")}")
|
||||
}
|
||||
|
||||
private data class ListenerExpectation(
|
||||
val path: String,
|
||||
val groupId: String,
|
||||
val hasEventFilter: Boolean = false
|
||||
)
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package space.nstart.pcp.pcp_request_service.message
|
||||
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.ObjectProvider
|
||||
import space.nstart.pcp.pcp_request_service.service.SatelliteIcEventService
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteICDTO
|
||||
import tools.jackson.databind.ObjectMapper
|
||||
|
||||
class SatelliteIcRvKafkaListenerTest {
|
||||
|
||||
private val objectMapper = ObjectMapper()
|
||||
private val satelliteIcEventService = RecordingSatelliteIcEventService()
|
||||
private val listener = SatelliteIcRvKafkaListener(
|
||||
objectMapperProvider = SingleObjectProvider(objectMapper),
|
||||
satelliteIcEventService = satelliteIcEventService
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `listener parses ICRV placed event payload and delegates to service`() {
|
||||
listener.consume(
|
||||
"""
|
||||
{
|
||||
"type": "ICRVPlacedEvent",
|
||||
"data": {
|
||||
"satelliteId": 56756,
|
||||
"ic": {
|
||||
"orbPoint": {
|
||||
"time": "2026-04-16T12:00:00",
|
||||
"revolution": 42,
|
||||
"vx": 1.0,
|
||||
"vy": 2.0,
|
||||
"vz": 3.0,
|
||||
"x": 4.0,
|
||||
"y": 5.0,
|
||||
"z": 6.0
|
||||
},
|
||||
"sBall": 0.7,
|
||||
"f81": 140.0
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
assertEquals(56756L, satelliteIcEventService.lastMessage?.satelliteId)
|
||||
assertEquals(42L, satelliteIcEventService.lastMessage?.ic?.orbPoint?.revolution)
|
||||
assertEquals(140.0, satelliteIcEventService.lastMessage?.ic?.f81)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `listener substitutes default initial condition coefficients when payload contains nulls`() {
|
||||
listener.consume(
|
||||
"""
|
||||
{
|
||||
"type": "ICRVPlacedEvent",
|
||||
"data": {
|
||||
"satelliteId": 56756,
|
||||
"ic": {
|
||||
"orbPoint": {
|
||||
"time": "2026-04-16T12:00:00",
|
||||
"revolution": 42,
|
||||
"vx": 1.0,
|
||||
"vy": 2.0,
|
||||
"vz": 3.0,
|
||||
"x": 4.0,
|
||||
"y": 5.0,
|
||||
"z": 6.0
|
||||
},
|
||||
"sBall": null,
|
||||
"f81": null
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
assertEquals(0.0, satelliteIcEventService.lastMessage?.ic?.sBall)
|
||||
assertEquals(147.8, satelliteIcEventService.lastMessage?.ic?.f81)
|
||||
}
|
||||
|
||||
private class RecordingSatelliteIcEventService : SatelliteIcEventService() {
|
||||
var lastMessage: SatelliteICDTO? = null
|
||||
|
||||
override fun handlePlacedIcRv(message: SatelliteICDTO) {
|
||||
lastMessage = message
|
||||
}
|
||||
}
|
||||
|
||||
private class SingleObjectProvider<T : Any>(private val value: T) : ObjectProvider<T> {
|
||||
override fun getObject(vararg args: Any?): T = value
|
||||
override fun getIfAvailable(): T = value
|
||||
override fun getIfUnique(): T = value
|
||||
override fun getObject(): T = value
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
package space.nstart.pcp.pcp_request_service.repository
|
||||
|
||||
import jakarta.persistence.EntityManager
|
||||
import org.apache.kafka.clients.consumer.ConsumerConfig
|
||||
import org.apache.kafka.common.serialization.StringDeserializer
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.boot.test.context.TestConfiguration
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory
|
||||
import org.springframework.kafka.core.ConsumerFactory
|
||||
import org.springframework.kafka.core.DefaultKafkaConsumerFactory
|
||||
import org.springframework.kafka.listener.adapter.RecordFilterStrategy
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import space.nstart.pcp.pcp_request_service.entity.PDCMEntity
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@SpringBootTest(
|
||||
properties = [
|
||||
"spring.cloud.config.enabled=false",
|
||||
"spring.cloud.config.import-check.enabled=false",
|
||||
"spring.kafka.bootstrap-servers=false",
|
||||
"spring.kafka.listener.auto-startup=false",
|
||||
"spring.datasource.driver-class-name=org.h2.Driver",
|
||||
"spring.datasource.url=jdbc:h2:mem:pdcm-repository-test;MODE=PostgreSQL;DB_CLOSE_DELAY=-1",
|
||||
"spring.datasource.username=sa",
|
||||
"spring.datasource.password=",
|
||||
"spring.flyway.enabled=false",
|
||||
"spring.jpa.hibernate.ddl-auto=create-drop",
|
||||
"spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect"
|
||||
]
|
||||
)
|
||||
@Transactional
|
||||
class PDCMRepositoryTest {
|
||||
|
||||
@Autowired
|
||||
private lateinit var entityManager: EntityManager
|
||||
|
||||
@Autowired
|
||||
private lateinit var repository: PDCMRepository
|
||||
|
||||
@Test
|
||||
fun `find satellite orbit availability returns all satellites with points when time is null`() {
|
||||
persistPoint(11L, LocalDateTime.of(2026, 4, 14, 10, 0))
|
||||
persistPoint(11L, LocalDateTime.of(2026, 4, 14, 12, 0))
|
||||
persistPoint(22L, LocalDateTime.of(2026, 4, 15, 9, 30))
|
||||
persistPoint(22L, LocalDateTime.of(2026, 4, 15, 18, 45))
|
||||
|
||||
val actual = repository.findSatelliteOrbitAvailability()
|
||||
|
||||
assertEquals(2, actual.size)
|
||||
assertEquals(11L, actual[0].satelliteId)
|
||||
assertEquals(LocalDateTime.of(2026, 4, 14, 10, 0), actual[0].timeStart)
|
||||
assertEquals(LocalDateTime.of(2026, 4, 14, 12, 0), actual[0].timeStop)
|
||||
assertEquals(22L, actual[1].satelliteId)
|
||||
assertEquals(LocalDateTime.of(2026, 4, 15, 9, 30), actual[1].timeStart)
|
||||
assertEquals(LocalDateTime.of(2026, 4, 15, 18, 45), actual[1].timeStop)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `find satellite orbit availability filters satellites whose interval contains requested time`() {
|
||||
persistPoint(11L, LocalDateTime.of(2026, 4, 14, 10, 0))
|
||||
persistPoint(11L, LocalDateTime.of(2026, 4, 14, 12, 0))
|
||||
persistPoint(22L, LocalDateTime.of(2026, 4, 15, 9, 30))
|
||||
persistPoint(22L, LocalDateTime.of(2026, 4, 15, 18, 45))
|
||||
|
||||
val actual = repository.findSatelliteOrbitAvailabilityAtTime(LocalDateTime.of(2026, 4, 14, 11, 0))
|
||||
|
||||
assertEquals(1, actual.size)
|
||||
assertEquals(11L, actual.single().satelliteId)
|
||||
assertEquals(LocalDateTime.of(2026, 4, 14, 10, 0), actual.single().timeStart)
|
||||
assertEquals(LocalDateTime.of(2026, 4, 14, 12, 0), actual.single().timeStop)
|
||||
}
|
||||
|
||||
private fun persistPoint(satelliteId: Long, time: LocalDateTime) {
|
||||
entityManager.persist(
|
||||
PDCMEntity(
|
||||
satelliteId = satelliteId,
|
||||
time = time,
|
||||
revolution = 1,
|
||||
x = 1.0,
|
||||
y = 2.0,
|
||||
z = 3.0,
|
||||
vx = 4.0,
|
||||
vy = 5.0,
|
||||
vz = 6.0
|
||||
)
|
||||
)
|
||||
entityManager.flush()
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
class KafkaFilterStubConfiguration {
|
||||
@Bean("kafkaListenerContainerFactory")
|
||||
fun kafkaListenerContainerFactory(): ConcurrentKafkaListenerContainerFactory<String, String> =
|
||||
ConcurrentKafkaListenerContainerFactory<String, String>().apply {
|
||||
setConsumerFactory(testConsumerFactory())
|
||||
setAutoStartup(false)
|
||||
}
|
||||
|
||||
private fun testConsumerFactory(): ConsumerFactory<String, String> =
|
||||
DefaultKafkaConsumerFactory(
|
||||
mapOf(
|
||||
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG to "localhost:9092",
|
||||
ConsumerConfig.GROUP_ID_CONFIG to "test",
|
||||
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.java,
|
||||
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.java
|
||||
)
|
||||
)
|
||||
|
||||
@Bean("placedIcFilter")
|
||||
fun placedIcFilter(): RecordFilterStrategy<String, String> = RecordFilterStrategy { false }
|
||||
|
||||
@Bean("placedIcRvFilter")
|
||||
fun placedIcRvFilter(): RecordFilterStrategy<String, String> = RecordFilterStrategy { false }
|
||||
|
||||
@Bean("updatedIcFilter")
|
||||
fun updatedIcFilter(): RecordFilterStrategy<String, String> = RecordFilterStrategy { false }
|
||||
|
||||
@Bean("satelliteDeletedFilter")
|
||||
fun satelliteDeletedFilter(): RecordFilterStrategy<String, String> = RecordFilterStrategy { false }
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package space.nstart.pcp.pcp_request_service.service
|
||||
|
||||
import ballistics.types.Orientation
|
||||
import ballistics.types.PointViewParams
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class SatelliteServiceDeterminismTest {
|
||||
|
||||
@Test
|
||||
fun `sortPointViews orders mpl points deterministically`() {
|
||||
val service = SatelliteService()
|
||||
|
||||
val sorted = service.sortPointViews(
|
||||
listOf(
|
||||
pointView(traverz = 120.0, vit = 3, pointNumb = 2, pv = 1, kren = 0.3),
|
||||
pointView(traverz = 100.0, vit = 4, pointNumb = 1, pv = 0, kren = 0.5),
|
||||
pointView(traverz = 100.0, vit = 3, pointNumb = 2, pv = 0, kren = 0.4),
|
||||
pointView(traverz = 100.0, vit = 3, pointNumb = 1, pv = 1, kren = 0.2)
|
||||
)
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
listOf(
|
||||
100.0 to 3,
|
||||
100.0 to 3,
|
||||
100.0 to 4,
|
||||
120.0 to 3
|
||||
),
|
||||
sorted.map { it.traverz to it.vit }
|
||||
)
|
||||
assertEquals(listOf(1, 2, 1, 2), sorted.map { it.pointNumb })
|
||||
}
|
||||
|
||||
private fun pointView(
|
||||
traverz: Double,
|
||||
vit: Int,
|
||||
pointNumb: Int,
|
||||
pv: Int,
|
||||
kren: Double
|
||||
) = PointViewParams(
|
||||
objON = 0,
|
||||
objN = 0,
|
||||
objUUID = "",
|
||||
pointNumb = pointNumb,
|
||||
vit = vit,
|
||||
traverz = traverz,
|
||||
latTraverz = 0.0,
|
||||
longTraverz = 0.0,
|
||||
orientation = Orientation(0.0, kren, 0.0),
|
||||
range = 0.0,
|
||||
sunAngle = 0.0,
|
||||
sightAngle = 0.0,
|
||||
pv = pv
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
spring:
|
||||
application:
|
||||
name: pcp-ballistics-service
|
||||
cloud:
|
||||
config:
|
||||
enabled: false
|
||||
lifecycle.timeout-per-shutdown-phase: 40s
|
||||
jackson:
|
||||
default-property-inclusion: non_null
|
||||
kafka:
|
||||
bootstrap-servers: 192.168.60.68:29092
|
||||
consumer:
|
||||
group-id: pcp-ballistics-service
|
||||
auto-offset-reset: earliest
|
||||
template:
|
||||
default-topic: pcp.tle
|
||||
datasource:
|
||||
driver-class-name: org.postgresql.Driver
|
||||
url: jdbc:postgresql://192.168.60.68:5432/pcp_ballistics
|
||||
username: postgres
|
||||
password: password
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: validate
|
||||
properties:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
jdbc:
|
||||
lob:
|
||||
non_contextual_creation: true
|
||||
flyway:
|
||||
enabled: true
|
||||
baseline-on-migrate: true
|
||||
locations: classpath:db/migration
|
||||
codec:
|
||||
max-in-memory-size: 20MB
|
||||
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
enabled: true
|
||||
layout: BaseLayout
|
||||
path: /swagger/ui
|
||||
api-docs:
|
||||
enabled: true
|
||||
path: /api-docs
|
||||
|
||||
logging:
|
||||
level:
|
||||
.: ERROR
|
||||
file:
|
||||
name: ./logs/application.log
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "*"
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
info:
|
||||
enabled: true
|
||||
|
||||
settings:
|
||||
calculation-interval: 28
|
||||
ic-service: http://192.168.60.68:9080
|
||||
|
||||
server:
|
||||
port: 7003
|
||||
Reference in New Issue
Block a user