анализ TLE
This commit is contained in:
@@ -95,7 +95,7 @@ variables:
|
|||||||
|
|
||||||
ballistics:test:
|
ballistics:test:
|
||||||
stage: test
|
stage: test
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
services:
|
services:
|
||||||
- name: repo.nstart.cloud/postgis/postgis:16-3.4
|
- name: repo.nstart.cloud/postgis/postgis:16-3.4
|
||||||
alias: postgres
|
alias: postgres
|
||||||
@@ -124,7 +124,7 @@ ballistics:test:
|
|||||||
|
|
||||||
ballistics:build:
|
ballistics:build:
|
||||||
stage: build
|
stage: build
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .ballistics:rules:build-and-image
|
- .ballistics:rules:build-and-image
|
||||||
- .ballistics:gradle-cache
|
- .ballistics:gradle-cache
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ variables:
|
|||||||
|
|
||||||
complex-mission-service:test:
|
complex-mission-service:test:
|
||||||
stage: test
|
stage: test
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .complex-mission-service:rules:test
|
- .complex-mission-service:rules:test
|
||||||
- .pcp-gradle-cache
|
- .pcp-gradle-cache
|
||||||
@@ -158,7 +158,7 @@ complex-mission-service:test:
|
|||||||
|
|
||||||
complex-mission-service:build:
|
complex-mission-service:build:
|
||||||
stage: build
|
stage: build
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .complex-mission-service:rules:build-and-image
|
- .complex-mission-service:rules:build-and-image
|
||||||
- .pcp-gradle-cache
|
- .pcp-gradle-cache
|
||||||
@@ -300,7 +300,7 @@ complex-mission-service:deploy:master:
|
|||||||
|
|
||||||
tle-monitoring:test:
|
tle-monitoring:test:
|
||||||
stage: test
|
stage: test
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
services:
|
services:
|
||||||
- name: repo.nstart.cloud/postgis/postgis:16-3.4
|
- name: repo.nstart.cloud/postgis/postgis:16-3.4
|
||||||
alias: postgres
|
alias: postgres
|
||||||
@@ -330,7 +330,7 @@ tle-monitoring:test:
|
|||||||
|
|
||||||
tle-monitoring:build:
|
tle-monitoring:build:
|
||||||
stage: build
|
stage: build
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .tle-monitoring:rules:build-and-image
|
- .tle-monitoring:rules:build-and-image
|
||||||
- .pcp-gradle-cache
|
- .pcp-gradle-cache
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ variables:
|
|||||||
|
|
||||||
coverage-scheme:test:
|
coverage-scheme:test:
|
||||||
stage: test
|
stage: test
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .coverage_scheme:rules:test
|
- .coverage_scheme:rules:test
|
||||||
- .coverage_scheme:gradle-cache
|
- .coverage_scheme:gradle-cache
|
||||||
@@ -105,7 +105,7 @@ coverage-scheme:test:
|
|||||||
|
|
||||||
coverage-scheme:build:
|
coverage-scheme:build:
|
||||||
stage: build
|
stage: build
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .coverage_scheme:rules:build-and-image
|
- .coverage_scheme:rules:build-and-image
|
||||||
- .coverage_scheme:gradle-cache
|
- .coverage_scheme:gradle-cache
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ variables:
|
|||||||
|
|
||||||
dynamic-plan:test:
|
dynamic-plan:test:
|
||||||
stage: test
|
stage: test
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .dynamic_plan:rules:test
|
- .dynamic_plan:rules:test
|
||||||
- .dynamic_plan:gradle-cache
|
- .dynamic_plan:gradle-cache
|
||||||
@@ -106,7 +106,7 @@ dynamic-plan:test:
|
|||||||
|
|
||||||
dynamic-plan:build:
|
dynamic-plan:build:
|
||||||
stage: build
|
stage: build
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .dynamic_plan:rules:build-and-image
|
- .dynamic_plan:rules:build-and-image
|
||||||
- .dynamic_plan:gradle-cache
|
- .dynamic_plan:gradle-cache
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ variables:
|
|||||||
|
|
||||||
mission-planing:test:
|
mission-planing:test:
|
||||||
stage: test
|
stage: test
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .mission-planing:rules:test
|
- .mission-planing:rules:test
|
||||||
- .mission-planing:gradle-cache
|
- .mission-planing:gradle-cache
|
||||||
@@ -105,7 +105,7 @@ mission-planing:test:
|
|||||||
|
|
||||||
mission-planing:build:
|
mission-planing:build:
|
||||||
stage: build
|
stage: build
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .mission-planing:rules:build-and-image
|
- .mission-planing:rules:build-and-image
|
||||||
- .mission-planing:gradle-cache
|
- .mission-planing:gradle-cache
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ variables:
|
|||||||
|
|
||||||
request:test:
|
request:test:
|
||||||
stage: test
|
stage: test
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .request:rules:test
|
- .request:rules:test
|
||||||
- .request:gradle-cache
|
- .request:gradle-cache
|
||||||
@@ -104,7 +104,7 @@ request:test:
|
|||||||
|
|
||||||
request:build:
|
request:build:
|
||||||
stage: build
|
stage: build
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .request:rules:build-and-image
|
- .request:rules:build-and-image
|
||||||
- .request:gradle-cache
|
- .request:gradle-cache
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ variables:
|
|||||||
|
|
||||||
satellite-catalog:test:
|
satellite-catalog:test:
|
||||||
stage: test
|
stage: test
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .satellite-catalog:rules:test
|
- .satellite-catalog:rules:test
|
||||||
- .satellite-catalog:gradle-cache
|
- .satellite-catalog:gradle-cache
|
||||||
@@ -104,7 +104,7 @@ satellite-catalog:test:
|
|||||||
|
|
||||||
satellite-catalog:build:
|
satellite-catalog:build:
|
||||||
stage: build
|
stage: build
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .satellite-catalog:rules:build-and-image
|
- .satellite-catalog:rules:build-and-image
|
||||||
- .satellite-catalog:gradle-cache
|
- .satellite-catalog:gradle-cache
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ variables:
|
|||||||
|
|
||||||
spring-boot-admin:test:
|
spring-boot-admin:test:
|
||||||
stage: test
|
stage: test
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .spring-boot-admin:rules:test
|
- .spring-boot-admin:rules:test
|
||||||
- .spring-boot-admin:gradle-cache
|
- .spring-boot-admin:gradle-cache
|
||||||
@@ -95,7 +95,7 @@ spring-boot-admin:test:
|
|||||||
|
|
||||||
spring-boot-admin:build:
|
spring-boot-admin:build:
|
||||||
stage: build
|
stage: build
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .spring-boot-admin:rules:build-and-image
|
- .spring-boot-admin:rules:build-and-image
|
||||||
- .spring-boot-admin:gradle-cache
|
- .spring-boot-admin:gradle-cache
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ variables:
|
|||||||
|
|
||||||
stations:test:
|
stations:test:
|
||||||
stage: test
|
stage: test
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .stations:rules:test
|
- .stations:rules:test
|
||||||
- .stations:gradle-cache
|
- .stations:gradle-cache
|
||||||
@@ -104,7 +104,7 @@ stations:test:
|
|||||||
|
|
||||||
stations:build:
|
stations:build:
|
||||||
stage: build
|
stage: build
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .stations:rules:build-and-image
|
- .stations:rules:build-and-image
|
||||||
- .stations:gradle-cache
|
- .stations:gradle-cache
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ variables:
|
|||||||
|
|
||||||
ui:test:
|
ui:test:
|
||||||
stage: test
|
stage: test
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .ui:rules:test
|
- .ui:rules:test
|
||||||
- .ui:gradle-cache
|
- .ui:gradle-cache
|
||||||
@@ -105,7 +105,7 @@ ui:test:
|
|||||||
|
|
||||||
ui:build:
|
ui:build:
|
||||||
stage: build
|
stage: build
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .ui:rules:build-and-image
|
- .ui:rules:build-and-image
|
||||||
- .ui:gradle-cache
|
- .ui:gradle-cache
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ variables:
|
|||||||
|
|
||||||
slots:test:
|
slots:test:
|
||||||
stage: test
|
stage: test
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
services:
|
services:
|
||||||
- name: repo.nstart.cloud/postgis/postgis:16-3.4
|
- name: repo.nstart.cloud/postgis/postgis:16-3.4
|
||||||
alias: postgres
|
alias: postgres
|
||||||
@@ -115,7 +115,7 @@ slots:test:
|
|||||||
|
|
||||||
slots:build:
|
slots:build:
|
||||||
stage: build
|
stage: build
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .slots:rules:build-and-image
|
- .slots:rules:build-and-image
|
||||||
- .slots:gradle-cache
|
- .slots:gradle-cache
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ variables:
|
|||||||
|
|
||||||
config-server:test:
|
config-server:test:
|
||||||
stage: test
|
stage: test
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .config-server:rules:test
|
- .config-server:rules:test
|
||||||
- .config-server:gradle-cache
|
- .config-server:gradle-cache
|
||||||
@@ -101,7 +101,7 @@ config-server:test:
|
|||||||
|
|
||||||
config-server:build:
|
config-server:build:
|
||||||
stage: build
|
stage: build
|
||||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||||
extends:
|
extends:
|
||||||
- .config-server:rules:build-and-image
|
- .config-server:rules:build-and-image
|
||||||
- .config-server:gradle-cache
|
- .config-server:gradle-cache
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
pcp:
|
pcp:
|
||||||
infra:
|
infra:
|
||||||
postgres:
|
postgres:
|
||||||
host: ${PCP_POSTGRES_HOST:localhost}
|
# host: ${PCP_POSTGRES_HOST:localhost}
|
||||||
port: ${PCP_POSTGRES_PORT:5432}
|
# port: ${PCP_POSTGRES_PORT:5432}
|
||||||
|
host: ${PCP_POSTGRES_HOST:192.168.100.160}
|
||||||
|
port: ${PCP_POSTGRES_PORT:35400}
|
||||||
kafka:
|
kafka:
|
||||||
host: ${PCP_KAFKA_HOST:localhost}
|
host: ${PCP_KAFKA_HOST:localhost}
|
||||||
port: ${PCP_KAFKA_PORT:29092}
|
port: ${PCP_KAFKA_PORT:29092}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ spring:
|
|||||||
camunda:
|
camunda:
|
||||||
client:
|
client:
|
||||||
mode: self-managed
|
mode: self-managed
|
||||||
grpc-address: http://camunda.k8s.265.nstart.local:30901
|
grpc-address: http://camunda.k8s.265.nstart.cloud:30901
|
||||||
auth:
|
auth:
|
||||||
method: none
|
method: none
|
||||||
prefer-rest-over-grpc: false
|
prefer-rest-over-grpc: false
|
||||||
|
|||||||
+27
-1
@@ -1,8 +1,10 @@
|
|||||||
package space.nstart.pcp.slots_service.controller
|
package space.nstart.pcp.slots_service.controller
|
||||||
|
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.PathVariable
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
import space.nstart.pcp.slots_service.service.TleAnalysisService
|
import space.nstart.pcp.slots_service.service.TleAnalysisService
|
||||||
|
|
||||||
@@ -11,6 +13,30 @@ import space.nstart.pcp.slots_service.service.TleAnalysisService
|
|||||||
class TleAnalysisController(
|
class TleAnalysisController(
|
||||||
private val tleAnalysisService: TleAnalysisService
|
private val tleAnalysisService: TleAnalysisService
|
||||||
) {
|
) {
|
||||||
|
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||||
|
|
||||||
|
@GetMapping("/satellites")
|
||||||
|
fun satellites() =
|
||||||
|
tleAnalysisService.satellites().also { satellites ->
|
||||||
|
logger.info("TLE analysis satellites request completed: satellites={}", satellites.size)
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/satellites/{satelliteId}")
|
@GetMapping("/satellites/{satelliteId}")
|
||||||
fun analyzeSatellite(@PathVariable satelliteId: Long) = tleAnalysisService.analyzeSatellite(satelliteId)
|
fun analyzeSatellite(
|
||||||
|
@PathVariable satelliteId: Long,
|
||||||
|
@RequestParam(defaultValue = "5") tleCount: Int
|
||||||
|
) = tleAnalysisService.analyzeSatellite(satelliteId, tleCount).also { response ->
|
||||||
|
logger.info(
|
||||||
|
"TLE analysis report request completed: satelliteId={}, requestedTleCount={}, rows={}",
|
||||||
|
satelliteId,
|
||||||
|
tleCount,
|
||||||
|
response.rows.size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/satellites/{satelliteId}/analysis")
|
||||||
|
fun analyzeSatelliteDetails(
|
||||||
|
@PathVariable satelliteId: Long,
|
||||||
|
@RequestParam(defaultValue = "5") tleCount: Int
|
||||||
|
) = analyzeSatellite(satelliteId, tleCount)
|
||||||
}
|
}
|
||||||
|
|||||||
+13
@@ -2,6 +2,18 @@ package space.nstart.pcp.slots_service.dto.tleanalysis
|
|||||||
|
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
data class TleAnalysisSatelliteDTO(
|
||||||
|
val id: Long,
|
||||||
|
val catalogId: Long? = null,
|
||||||
|
val noradId: Long? = null,
|
||||||
|
val code: String = "",
|
||||||
|
val name: String = "",
|
||||||
|
val typeCode: String = "",
|
||||||
|
val active: Boolean = true,
|
||||||
|
val scanTle: Boolean = false,
|
||||||
|
val tleRecordsCount: Int = 0
|
||||||
|
)
|
||||||
|
|
||||||
data class TleAnalysisResponseDTO(
|
data class TleAnalysisResponseDTO(
|
||||||
val satelliteId: Long,
|
val satelliteId: Long,
|
||||||
val rows: List<TleAnalysisRowDTO>
|
val rows: List<TleAnalysisRowDTO>
|
||||||
@@ -26,6 +38,7 @@ data class RadiusVectorDTO(
|
|||||||
|
|
||||||
data class RadiusVectorDiscrepancyDTO(
|
data class RadiusVectorDiscrepancyDTO(
|
||||||
val previousEpoch: LocalDateTime,
|
val previousEpoch: LocalDateTime,
|
||||||
|
val epochDifferenceHours: Double,
|
||||||
val dx: Double,
|
val dx: Double,
|
||||||
val dy: Double,
|
val dy: Double,
|
||||||
val dz: Double,
|
val dz: Double,
|
||||||
|
|||||||
+190
-31
@@ -5,60 +5,183 @@ import ballistics.orbitalPoints.timeStepper.TLEStepper
|
|||||||
import ballistics.types.EarthType
|
import ballistics.types.EarthType
|
||||||
import ballistics.types.TLE
|
import ballistics.types.TLE
|
||||||
import ballistics.utils.fromDateTime
|
import ballistics.utils.fromDateTime
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
|
||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEExtensionDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEExtensionDTO
|
||||||
|
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteSummaryDTO
|
||||||
import space.nstart.pcp.slots_service.configuration.CustomErrorException
|
import space.nstart.pcp.slots_service.configuration.CustomErrorException
|
||||||
import space.nstart.pcp.slots_service.dto.tleanalysis.RadiusVectorDTO
|
import space.nstart.pcp.slots_service.dto.tleanalysis.RadiusVectorDTO
|
||||||
import space.nstart.pcp.slots_service.dto.tleanalysis.RadiusVectorDiscrepancyDTO
|
import space.nstart.pcp.slots_service.dto.tleanalysis.RadiusVectorDiscrepancyDTO
|
||||||
|
import space.nstart.pcp.slots_service.dto.tleanalysis.TleAnalysisSatelliteDTO
|
||||||
import space.nstart.pcp.slots_service.dto.tleanalysis.TleAnalysisResponseDTO
|
import space.nstart.pcp.slots_service.dto.tleanalysis.TleAnalysisResponseDTO
|
||||||
import space.nstart.pcp.slots_service.dto.tleanalysis.TleAnalysisRowDTO
|
import space.nstart.pcp.slots_service.dto.tleanalysis.TleAnalysisRowDTO
|
||||||
|
import java.time.Duration
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class TleAnalysisService(
|
class TleAnalysisService(
|
||||||
private val tleMonitoringService: TleMonitoringService
|
private val tleMonitoringService: TleMonitoringService,
|
||||||
|
private val satelliteCatalogService: SatelliteCatalogService
|
||||||
) {
|
) {
|
||||||
fun analyzeSatellite(satelliteId: Long): TleAnalysisResponseDTO {
|
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||||
val records = tleMonitoringService.satelliteTles(satelliteId)
|
|
||||||
.mapNotNull { parseRecord(it) }
|
|
||||||
.sortedBy { it.epoch }
|
|
||||||
|
|
||||||
val rows = records.mapIndexed { index, record ->
|
fun satellites(): List<TleAnalysisSatelliteDTO> {
|
||||||
val currentPoint = calculatePoint(record.tle, record.epoch)
|
logger.info("TLE analysis satellites list stage started")
|
||||||
val radiusVector = RadiusVectorDTO(
|
var catalogSatellites = emptyList<SatelliteSummaryDTO>()
|
||||||
x = currentPoint.r.x,
|
val catalogLoadMs = measureTimeMillis {
|
||||||
y = currentPoint.r.y,
|
catalogSatellites = runCatching { satelliteCatalogService.allSatellites() }
|
||||||
z = currentPoint.r.z
|
.onFailure { exception ->
|
||||||
|
logger.warn("Не удалось загрузить спутники из satellite-catalog-service для анализа TLE: {}", exception.message)
|
||||||
|
}
|
||||||
|
.getOrDefault(emptyList())
|
||||||
|
}
|
||||||
|
var tleRecords = emptyList<TLEExtensionDTO>()
|
||||||
|
val tleLoadMs = measureTimeMillis {
|
||||||
|
tleRecords = runCatching { tleMonitoringService.allTles() }
|
||||||
|
.onFailure { exception ->
|
||||||
|
logger.warn("Не удалось загрузить TLE из tle-monitoring-service для списка анализа TLE: {}", exception.message)
|
||||||
|
}
|
||||||
|
.getOrDefault(emptyList())
|
||||||
|
}
|
||||||
|
logger.info(
|
||||||
|
"TLE analysis satellites list sources loaded: catalogSatellites={}, tleRecords={}, catalogLoadMs={}, tleLoadMs={}",
|
||||||
|
catalogSatellites.size,
|
||||||
|
tleRecords.size,
|
||||||
|
catalogLoadMs,
|
||||||
|
tleLoadMs
|
||||||
|
)
|
||||||
|
|
||||||
|
if (tleRecords.isEmpty()) {
|
||||||
|
val fallbackSatellites = catalogSatellites.mapNotNull { satellite -> satellite.toAnalysisSatelliteFallback() }
|
||||||
|
logger.info(
|
||||||
|
"TLE analysis satellites list built from catalog fallback: satellites={}",
|
||||||
|
fallbackSatellites.size
|
||||||
)
|
)
|
||||||
|
return fallbackSatellites
|
||||||
|
}
|
||||||
|
|
||||||
val discrepancy = records.getOrNull(index - 1)?.let { previous ->
|
val catalogByNoradId = catalogSatellites
|
||||||
val previousPointAtCurrentEpoch = calculatePoint(previous.tle, record.epoch)
|
.mapNotNull { satellite -> satellite.noradId?.let { noradId -> noradId to satellite } }
|
||||||
val dx = currentPoint.r.x - previousPointAtCurrentEpoch.r.x
|
.toMap()
|
||||||
val dy = currentPoint.r.y - previousPointAtCurrentEpoch.r.y
|
val catalogById = catalogSatellites.associateBy { it.id }
|
||||||
val dz = currentPoint.r.z - previousPointAtCurrentEpoch.r.z
|
|
||||||
RadiusVectorDiscrepancyDTO(
|
val satellites = tleRecords
|
||||||
previousEpoch = previous.epoch,
|
.groupingBy { record -> record.satelliteId }
|
||||||
dx = dx,
|
.eachCount()
|
||||||
dy = dy,
|
.entries
|
||||||
dz = dz,
|
.sortedBy { (satelliteId, _) -> satelliteId }
|
||||||
norm = norm(dx, dy, dz)
|
.map { (tleSatelliteId, recordsCount) ->
|
||||||
|
val catalogSatellite = catalogByNoradId[tleSatelliteId] ?: catalogById[tleSatelliteId]
|
||||||
|
catalogSatellite?.toAnalysisSatellite(
|
||||||
|
tleSatelliteId = tleSatelliteId,
|
||||||
|
recordsCount = recordsCount
|
||||||
|
) ?: TleAnalysisSatelliteDTO(
|
||||||
|
id = tleSatelliteId,
|
||||||
|
noradId = tleSatelliteId,
|
||||||
|
name = "КА $tleSatelliteId",
|
||||||
|
tleRecordsCount = recordsCount
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
logger.info(
|
||||||
|
"TLE analysis satellites list built from TLE records: satellites={}, matchedCatalogSatellites={}, unmatchedTleSatellites={}",
|
||||||
|
satellites.size,
|
||||||
|
satellites.count { it.catalogId != null },
|
||||||
|
satellites.count { it.catalogId == null }
|
||||||
|
)
|
||||||
|
return satellites
|
||||||
|
}
|
||||||
|
|
||||||
TleAnalysisRowDTO(
|
fun analyzeSatellite(satelliteId: Long, tleCount: Int = DEFAULT_TLE_ANALYSIS_COUNT): TleAnalysisResponseDTO {
|
||||||
index = index + 1,
|
val normalizedTleCount = tleCount.coerceAtLeast(1)
|
||||||
epoch = record.epoch,
|
logger.info(
|
||||||
revolution = record.revolution,
|
"TLE analysis report stage started: satelliteId={}, requestedTleCount={}, normalizedTleCount={}",
|
||||||
noradId = record.noradId,
|
satelliteId,
|
||||||
tleHeader = record.tle.header,
|
tleCount,
|
||||||
radiusVector = radiusVector,
|
normalizedTleCount
|
||||||
radiusNorm = norm(radiusVector.x, radiusVector.y, radiusVector.z),
|
)
|
||||||
discrepancy = discrepancy
|
|
||||||
|
var sourceRecords = emptyList<TLEExtensionDTO>()
|
||||||
|
val sourceLoadMs = measureTimeMillis {
|
||||||
|
sourceRecords = tleMonitoringService.satelliteTles(satelliteId)
|
||||||
|
}
|
||||||
|
logger.info(
|
||||||
|
"TLE analysis report source TLE loaded: satelliteId={}, records={}, durationMs={}",
|
||||||
|
satelliteId,
|
||||||
|
sourceRecords.size,
|
||||||
|
sourceLoadMs
|
||||||
|
)
|
||||||
|
|
||||||
|
val parsedRecords = sourceRecords.mapNotNull { parseRecord(it) }.sortedBy { it.epoch }
|
||||||
|
val skippedRecords = sourceRecords.size - parsedRecords.size
|
||||||
|
if (skippedRecords > 0) {
|
||||||
|
logger.warn(
|
||||||
|
"TLE analysis report skipped invalid TLE records: satelliteId={}, skipped={}, sourceRecords={}",
|
||||||
|
satelliteId,
|
||||||
|
skippedRecords,
|
||||||
|
sourceRecords.size
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val records = parsedRecords.takeLast(normalizedTleCount)
|
||||||
|
logger.info(
|
||||||
|
"TLE analysis report records selected: satelliteId={}, parsedRecords={}, selectedRecords={}, firstEpoch={}, lastEpoch={}",
|
||||||
|
satelliteId,
|
||||||
|
parsedRecords.size,
|
||||||
|
records.size,
|
||||||
|
records.firstOrNull()?.epoch,
|
||||||
|
records.lastOrNull()?.epoch
|
||||||
|
)
|
||||||
|
|
||||||
|
var rows = emptyList<TleAnalysisRowDTO>()
|
||||||
|
val rowsBuildMs = measureTimeMillis {
|
||||||
|
rows = records.mapIndexed { index, record ->
|
||||||
|
// Сравнение пары TLE выполняется на эпоху более поздней записи.
|
||||||
|
val comparisonEpoch = record.epoch
|
||||||
|
val currentPoint = calculatePoint(record.tle, comparisonEpoch)
|
||||||
|
val radiusVector = RadiusVectorDTO(
|
||||||
|
x = currentPoint.r.x,
|
||||||
|
y = currentPoint.r.y,
|
||||||
|
z = currentPoint.r.z
|
||||||
|
)
|
||||||
|
|
||||||
|
val discrepancy = records.getOrNull(index - 1)?.let { previous ->
|
||||||
|
val epochDifferenceHours = Duration.between(previous.epoch, comparisonEpoch).toMillis() / MILLIS_IN_HOUR
|
||||||
|
val previousPointAtCurrentEpoch = calculatePoint(previous.tle, comparisonEpoch)
|
||||||
|
val dx = currentPoint.r.x - previousPointAtCurrentEpoch.r.x
|
||||||
|
val dy = currentPoint.r.y - previousPointAtCurrentEpoch.r.y
|
||||||
|
val dz = currentPoint.r.z - previousPointAtCurrentEpoch.r.z
|
||||||
|
RadiusVectorDiscrepancyDTO(
|
||||||
|
previousEpoch = previous.epoch,
|
||||||
|
epochDifferenceHours = epochDifferenceHours,
|
||||||
|
dx = dx,
|
||||||
|
dy = dy,
|
||||||
|
dz = dz,
|
||||||
|
norm = norm(dx, dy, dz)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
TleAnalysisRowDTO(
|
||||||
|
index = index + 1,
|
||||||
|
epoch = record.epoch,
|
||||||
|
revolution = record.revolution,
|
||||||
|
noradId = record.noradId,
|
||||||
|
tleHeader = record.tle.header,
|
||||||
|
radiusVector = radiusVector,
|
||||||
|
radiusNorm = norm(radiusVector.x, radiusVector.y, radiusVector.z),
|
||||||
|
discrepancy = discrepancy
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info(
|
||||||
|
"TLE analysis report built: satelliteId={}, rows={}, durationMs={}",
|
||||||
|
satelliteId,
|
||||||
|
rows.size,
|
||||||
|
rowsBuildMs
|
||||||
|
)
|
||||||
|
|
||||||
return TleAnalysisResponseDTO(
|
return TleAnalysisResponseDTO(
|
||||||
satelliteId = satelliteId,
|
satelliteId = satelliteId,
|
||||||
rows = rows
|
rows = rows
|
||||||
@@ -85,10 +208,46 @@ class TleAnalysisService(
|
|||||||
|
|
||||||
private fun norm(x: Double, y: Double, z: Double): Double = sqrt(x * x + y * y + z * z)
|
private fun norm(x: Double, y: Double, z: Double): Double = sqrt(x * x + y * y + z * z)
|
||||||
|
|
||||||
|
private fun SatelliteSummaryDTO.toAnalysisSatellite(
|
||||||
|
tleSatelliteId: Long,
|
||||||
|
recordsCount: Int
|
||||||
|
): TleAnalysisSatelliteDTO =
|
||||||
|
TleAnalysisSatelliteDTO(
|
||||||
|
id = tleSatelliteId,
|
||||||
|
catalogId = id,
|
||||||
|
noradId = noradId ?: tleSatelliteId,
|
||||||
|
code = code,
|
||||||
|
name = name,
|
||||||
|
typeCode = typeCode,
|
||||||
|
active = active,
|
||||||
|
scanTle = scanTle,
|
||||||
|
tleRecordsCount = recordsCount
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun SatelliteSummaryDTO.toAnalysisSatelliteFallback(): TleAnalysisSatelliteDTO? {
|
||||||
|
val analysisId = noradId ?: id.takeIf { it > 0 } ?: return null
|
||||||
|
return TleAnalysisSatelliteDTO(
|
||||||
|
id = analysisId,
|
||||||
|
catalogId = id,
|
||||||
|
noradId = noradId,
|
||||||
|
code = code,
|
||||||
|
name = name,
|
||||||
|
typeCode = typeCode,
|
||||||
|
active = active,
|
||||||
|
scanTle = scanTle,
|
||||||
|
tleRecordsCount = 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private data class ParsedTleRecord(
|
private data class ParsedTleRecord(
|
||||||
val tle: TLEDTO,
|
val tle: TLEDTO,
|
||||||
val epoch: LocalDateTime,
|
val epoch: LocalDateTime,
|
||||||
val revolution: Long,
|
val revolution: Long,
|
||||||
val noradId: Long
|
val noradId: Long
|
||||||
)
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val DEFAULT_TLE_ANALYSIS_COUNT = 5
|
||||||
|
private const val MILLIS_IN_HOUR = 3_600_000.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+61
-10
@@ -1,5 +1,6 @@
|
|||||||
package space.nstart.pcp.slots_service.service
|
package space.nstart.pcp.slots_service.service
|
||||||
|
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.beans.factory.ObjectProvider
|
import org.springframework.beans.factory.ObjectProvider
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
@@ -8,24 +9,74 @@ import reactor.core.publisher.Mono
|
|||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEExtensionDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEExtensionDTO
|
||||||
import space.nstart.pcp.slots_service.configuration.CustomErrorException
|
import space.nstart.pcp.slots_service.configuration.CustomErrorException
|
||||||
import tools.jackson.databind.ObjectMapper
|
import tools.jackson.databind.ObjectMapper
|
||||||
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class TleMonitoringService(webClientBuilderProvider: ObjectProvider<WebClient.Builder>) {
|
class TleMonitoringService(webClientBuilderProvider: ObjectProvider<WebClient.Builder>) {
|
||||||
private val webClientBuilder: WebClient.Builder = webClientBuilderProvider.ifAvailable ?: WebClient.builder()
|
private val webClientBuilder: WebClient.Builder = webClientBuilderProvider.ifAvailable ?: WebClient.builder()
|
||||||
|
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||||
|
|
||||||
@Value("\${settings.tle-monitoring-service:tle-monitoring-service}")
|
@Value("\${settings.tle-monitoring-service:tle-monitoring-service}")
|
||||||
private val tleMonitoringUrl: String = ""
|
private val tleMonitoringUrl: String = ""
|
||||||
|
|
||||||
fun satelliteTles(satelliteId: Long): List<TLEExtensionDTO> =
|
fun allTles(): List<TLEExtensionDTO> {
|
||||||
webClientBuilder.build()
|
logger.info("TLE monitoring client all TLE request started")
|
||||||
.get()
|
return runCatching {
|
||||||
.uri("$tleMonitoringUrl/v1/api/tle/satellite/$satelliteId")
|
var records = emptyList<TLEExtensionDTO>()
|
||||||
.retrieve()
|
val durationMs = measureTimeMillis {
|
||||||
.onStatus({ status -> status.isError }, ::mapError)
|
records = webClientBuilder.build()
|
||||||
.bodyToFlux(TLEExtensionDTO::class.java)
|
.get()
|
||||||
.collectList()
|
.uri("$tleMonitoringUrl/v1/api/tle")
|
||||||
.block()
|
.retrieve()
|
||||||
?: emptyList()
|
.onStatus({ status -> status.isError }, ::mapError)
|
||||||
|
.bodyToFlux(TLEExtensionDTO::class.java)
|
||||||
|
.collectList()
|
||||||
|
.block()
|
||||||
|
?: emptyList()
|
||||||
|
}
|
||||||
|
logger.info(
|
||||||
|
"TLE monitoring client all TLE request completed: records={}, durationMs={}",
|
||||||
|
records.size,
|
||||||
|
durationMs
|
||||||
|
)
|
||||||
|
records
|
||||||
|
}.getOrElse { exception ->
|
||||||
|
logger.warn("TLE monitoring client all TLE request failed: {}", exception.message)
|
||||||
|
throw exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun satelliteTles(satelliteId: Long): List<TLEExtensionDTO> {
|
||||||
|
logger.info("TLE monitoring client satellite TLE request started: satelliteId={}", satelliteId)
|
||||||
|
return runCatching {
|
||||||
|
var records = emptyList<TLEExtensionDTO>()
|
||||||
|
val durationMs = measureTimeMillis {
|
||||||
|
records = webClientBuilder.build()
|
||||||
|
.get()
|
||||||
|
.uri("$tleMonitoringUrl/v1/api/tle/satellite/$satelliteId")
|
||||||
|
.retrieve()
|
||||||
|
.onStatus({ status -> status.isError }, ::mapError)
|
||||||
|
.bodyToFlux(TLEExtensionDTO::class.java)
|
||||||
|
.collectList()
|
||||||
|
.block()
|
||||||
|
?: emptyList()
|
||||||
|
}
|
||||||
|
logger.info(
|
||||||
|
"TLE monitoring client satellite TLE request completed: satelliteId={}, records={}, durationMs={}",
|
||||||
|
satelliteId,
|
||||||
|
records.size,
|
||||||
|
durationMs
|
||||||
|
)
|
||||||
|
records
|
||||||
|
}.getOrElse { exception ->
|
||||||
|
logger.warn(
|
||||||
|
"TLE monitoring client satellite TLE request failed: satelliteId={}, error={}",
|
||||||
|
satelliteId,
|
||||||
|
exception.message
|
||||||
|
)
|
||||||
|
throw exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun mapError(response: org.springframework.web.reactive.function.client.ClientResponse): Mono<Throwable> =
|
private fun mapError(response: org.springframework.web.reactive.function.client.ClientResponse): Mono<Throwable> =
|
||||||
response.bodyToMono(String::class.java)
|
response.bodyToMono(String::class.java)
|
||||||
|
|||||||
@@ -1,25 +1,40 @@
|
|||||||
.tle-analysis-page {
|
.tle-analysis-page {
|
||||||
height: calc(100vh - 4.75rem);
|
height: calc(100vh - 4.75rem);
|
||||||
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tle-analysis-layout {
|
.tle-analysis-layout {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tle-analysis-layout > [class*="col-"] {
|
.tle-analysis-layout > [class*="col-"] {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tle-analysis-sidebar,
|
.tle-analysis-sidebar,
|
||||||
.tle-analysis-results-card {
|
.tle-analysis-results-card {
|
||||||
|
flex: 1 1 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tle-analysis-filter-body {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tle-analysis-filter-body .form-control {
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tle-analysis-satellites-wrap,
|
.tle-analysis-satellites-wrap,
|
||||||
@@ -29,11 +44,28 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tle-analysis-satellites-wrap {
|
.tle-analysis-satellites-wrap {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 0;
|
||||||
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tle-analysis-results-wrap {
|
.tle-analysis-results-wrap {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tle-analysis-table-wrap {
|
||||||
|
flex: 1 1 0;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
max-height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tle-analysis-results-table {
|
||||||
|
min-width: 980px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tle-analysis-satellites-table tbody tr,
|
.tle-analysis-satellites-table tbody tr,
|
||||||
|
|||||||
@@ -15,23 +15,30 @@
|
|||||||
|
|
||||||
function bindEvents() {
|
function bindEvents() {
|
||||||
document.getElementById('tle-analysis-refresh')?.addEventListener('click', async () => {
|
document.getElementById('tle-analysis-refresh')?.addEventListener('click', async () => {
|
||||||
|
state.selectedSatelliteId = null;
|
||||||
|
state.analysis = null;
|
||||||
|
state.loading = false;
|
||||||
|
renderResults();
|
||||||
await loadSatellites();
|
await loadSatellites();
|
||||||
|
});
|
||||||
|
document.getElementById('tle-analysis-satellite-filter')?.addEventListener('input', renderSatellites);
|
||||||
|
document.getElementById('tle-analysis-count')?.addEventListener('change', async () => {
|
||||||
if (state.selectedSatelliteId) {
|
if (state.selectedSatelliteId) {
|
||||||
await loadAnalysis(state.selectedSatelliteId);
|
await loadAnalysis(state.selectedSatelliteId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
document.getElementById('tle-analysis-satellite-filter')?.addEventListener('input', renderSatellites);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadSatellites() {
|
async function loadSatellites() {
|
||||||
showAlert('Загрузка списка спутников...', 'info');
|
showAlert('Загрузка списка спутников...', 'info');
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/catalog/satellites');
|
const response = await fetch('/api/tle-analysis/satellites');
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(await errorText(response));
|
throw new Error(await errorText(response));
|
||||||
}
|
}
|
||||||
state.satellites = await response.json();
|
state.satellites = normalizeSatelliteResponse(await response.json());
|
||||||
renderSatellites();
|
renderSatellites();
|
||||||
|
renderResults();
|
||||||
clearAlert();
|
clearAlert();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showAlert(`Не удалось загрузить спутники: ${error.message}`, 'danger');
|
showAlert(`Не удалось загрузить спутники: ${error.message}`, 'danger');
|
||||||
@@ -44,7 +51,8 @@
|
|||||||
renderResults();
|
renderResults();
|
||||||
showAlert('Формирование анализа TLE...', 'info');
|
showAlert('Формирование анализа TLE...', 'info');
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/tle-analysis/satellites/${encodeURIComponent(satelliteId)}`);
|
const params = new URLSearchParams({ tleCount: String(selectedTleCount()) });
|
||||||
|
const response = await fetch(`/api/tle-analysis/satellites/${encodeURIComponent(satelliteId)}/analysis?${params}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(await errorText(response));
|
throw new Error(await errorText(response));
|
||||||
}
|
}
|
||||||
@@ -53,10 +61,10 @@
|
|||||||
clearAlert();
|
clearAlert();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
state.analysis = null;
|
state.analysis = null;
|
||||||
renderResults();
|
|
||||||
showAlert(`Не удалось сформировать анализ TLE: ${error.message}`, 'danger');
|
showAlert(`Не удалось сформировать анализ TLE: ${error.message}`, 'danger');
|
||||||
} finally {
|
} finally {
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
|
renderResults();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,10 +77,12 @@
|
|||||||
if (!filter) return true;
|
if (!filter) return true;
|
||||||
return normalize([
|
return normalize([
|
||||||
satellite.id,
|
satellite.id,
|
||||||
|
satellite.catalogId,
|
||||||
satellite.noradId,
|
satellite.noradId,
|
||||||
satellite.code,
|
satellite.code,
|
||||||
satellite.name,
|
satellite.name,
|
||||||
satellite.typeCode
|
satellite.typeCode,
|
||||||
|
satellite.tleRecordsCount
|
||||||
].join(' ')).includes(filter);
|
].join(' ')).includes(filter);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -84,12 +94,15 @@
|
|||||||
tbody.innerHTML = satellites.map((satellite) => {
|
tbody.innerHTML = satellites.map((satellite) => {
|
||||||
const active = Number(satellite.id) === Number(state.selectedSatelliteId) ? 'active' : '';
|
const active = Number(satellite.id) === Number(state.selectedSatelliteId) ? 'active' : '';
|
||||||
const title = escapeHtml(satellite.name || satellite.code || `КА ${satellite.id}`);
|
const title = escapeHtml(satellite.name || satellite.code || `КА ${satellite.id}`);
|
||||||
|
const catalogId = satellite.catalogId == null ? satellite.id : satellite.catalogId;
|
||||||
|
const tleCount = Number(satellite.tleRecordsCount || 0);
|
||||||
return `
|
return `
|
||||||
<tr class="${active}" data-satellite-id="${escapeHtml(satellite.id)}">
|
<tr class="${active}" data-satellite-id="${escapeHtml(satellite.id)}">
|
||||||
<td>${escapeHtml(satellite.id)}</td>
|
<td>${escapeHtml(catalogId)}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="catalog-main-text">${title}</div>
|
<div class="catalog-main-text">${title}</div>
|
||||||
<div class="catalog-helper">${escapeHtml(satellite.code || '')} ${satellite.typeCode ? ' / ' + escapeHtml(satellite.typeCode) : ''}</div>
|
<div class="catalog-helper">${escapeHtml(satellite.code || '')} ${satellite.typeCode ? ' / ' + escapeHtml(satellite.typeCode) : ''}</div>
|
||||||
|
<div class="catalog-helper">TLE-записей: ${escapeHtml(tleCount)}</div>
|
||||||
</td>
|
</td>
|
||||||
<td>${satellite.noradId == null ? '—' : escapeHtml(satellite.noradId)}</td>
|
<td>${satellite.noradId == null ? '—' : escapeHtml(satellite.noradId)}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
@@ -116,7 +129,8 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const name = satellite.name || satellite.code || `КА ${satellite.id}`;
|
const name = satellite.name || satellite.code || `КА ${satellite.id}`;
|
||||||
label.textContent = `КА ${satellite.id}: ${name}${satellite.noradId == null ? '' : `, NORAD ${satellite.noradId}`}`;
|
const catalogId = satellite.catalogId == null ? satellite.id : satellite.catalogId;
|
||||||
|
label.textContent = `КА ${catalogId}: ${name}${satellite.noradId == null ? '' : `, NORAD ${satellite.noradId}`}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderResults() {
|
function renderResults() {
|
||||||
@@ -184,10 +198,38 @@
|
|||||||
z=${formatNumber(r.z, 3)}
|
z=${formatNumber(r.z, 3)}
|
||||||
</td>
|
</td>
|
||||||
<td>${formatNumber(row.radiusNorm, 3)}</td>
|
<td>${formatNumber(row.radiusNorm, 3)}</td>
|
||||||
|
<td>${discrepancy ? formatNumber(discrepancy.epochDifferenceHours, 3) : '—'}</td>
|
||||||
<td>${diffText}</td>
|
<td>${diffText}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeSatelliteResponse(payload) {
|
||||||
|
if (Array.isArray(payload)) {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.items)) {
|
||||||
|
return payload.items;
|
||||||
|
}
|
||||||
|
if (Array.isArray(payload?.content)) {
|
||||||
|
return payload.content;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectedTleCount() {
|
||||||
|
const input = document.getElementById('tle-analysis-count');
|
||||||
|
const value = Number(input?.value || 5);
|
||||||
|
if (!Number.isFinite(value) || value < 1) {
|
||||||
|
if (input) input.value = '5';
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
const count = Math.floor(value);
|
||||||
|
if (input && String(count) !== input.value) {
|
||||||
|
input.value = String(count);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
function normalize(value) {
|
function normalize(value) {
|
||||||
return String(value || '').trim().toLowerCase();
|
return String(value || '').trim().toLowerCase();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,16 @@
|
|||||||
<h2 class="mb-1">Анализ TLE</h2>
|
<h2 class="mb-1">Анализ TLE</h2>
|
||||||
<div class="catalog-helper">Анализ изменения TLE выбранного спутника по разнице рассчитанных радиус-векторов.</div>
|
<div class="catalog-helper">Анализ изменения TLE выбранного спутника по разнице рассчитанных радиус-векторов.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-2 flex-wrap">
|
<div class="d-flex align-items-end gap-2 flex-wrap">
|
||||||
|
<div>
|
||||||
|
<label for="tle-analysis-count" class="form-label mb-1 small text-muted">Количество TLE</label>
|
||||||
|
<input id="tle-analysis-count"
|
||||||
|
type="number"
|
||||||
|
class="form-control form-control-sm"
|
||||||
|
min="1"
|
||||||
|
step="1"
|
||||||
|
value="5">
|
||||||
|
</div>
|
||||||
<button id="tle-analysis-refresh" type="button" class="btn btn-outline-secondary">Обновить</button>
|
<button id="tle-analysis-refresh" type="button" class="btn btn-outline-secondary">Обновить</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -29,8 +38,8 @@
|
|||||||
<div class="catalog-section-title">Спутники</div>
|
<div class="catalog-section-title">Спутники</div>
|
||||||
<div class="catalog-helper mt-1">Выберите КА для формирования аналитической таблицы TLE.</div>
|
<div class="catalog-helper mt-1">Выберите КА для формирования аналитической таблицы TLE.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-3">
|
<div class="card-body p-2 tle-analysis-filter-body">
|
||||||
<label for="tle-analysis-satellite-filter" class="form-label">Фильтр</label>
|
<label for="tle-analysis-satellite-filter" class="form-label mb-1 small text-muted">Фильтр</label>
|
||||||
<input id="tle-analysis-satellite-filter"
|
<input id="tle-analysis-satellite-filter"
|
||||||
type="search"
|
type="search"
|
||||||
class="form-control form-control-sm"
|
class="form-control form-control-sm"
|
||||||
@@ -73,6 +82,7 @@
|
|||||||
<th>NORAD</th>
|
<th>NORAD</th>
|
||||||
<th>Радиус-вектор, м</th>
|
<th>Радиус-вектор, м</th>
|
||||||
<th>|r|, м</th>
|
<th>|r|, м</th>
|
||||||
|
<th>Разница Времени, ч</th>
|
||||||
<th>Расхождение, м</th>
|
<th>Расхождение, м</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -84,8 +94,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</th:block>
|
|
||||||
<th:block layout:fragment="scripts">
|
|
||||||
<script src="/tle_analysis_scripts.js"></script>
|
<script src="/tle_analysis_scripts.js"></script>
|
||||||
</th:block>
|
</th:block>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+17
@@ -140,6 +140,23 @@ class CatalogControllerTest {
|
|||||||
assertTrue(body.contains("/webjars/bootstrap/5.3.0/css/bootstrap.min.css"))
|
assertTrue(body.contains("/webjars/bootstrap/5.3.0/css/bootstrap.min.css"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `tle analysis page is rendered with page script`() {
|
||||||
|
val body = WebClient.create("http://localhost:$port")
|
||||||
|
.get()
|
||||||
|
.uri("/tle-analysis")
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(String::class.java)
|
||||||
|
.block()!!
|
||||||
|
|
||||||
|
assertTrue(body.contains("Анализ TLE"))
|
||||||
|
assertTrue(body.contains("tle-analysis-satellites-body"))
|
||||||
|
assertTrue(body.contains("tle-analysis-count"))
|
||||||
|
assertTrue(body.contains("Разница Времени, ч"))
|
||||||
|
assertTrue(body.contains("value=\"5\""))
|
||||||
|
assertTrue(body.contains("/tle_analysis_scripts.js"))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `stations page is rendered`() {
|
fun `stations page is rendered`() {
|
||||||
val body = WebClient.create("http://localhost:$port")
|
val body = WebClient.create("http://localhost:$port")
|
||||||
|
|||||||
+121
@@ -0,0 +1,121 @@
|
|||||||
|
package space.nstart.pcp.slots_service.service
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.Mockito.doReturn
|
||||||
|
import org.mockito.Mockito.mock
|
||||||
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
|
||||||
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEExtensionDTO
|
||||||
|
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteSummaryDTO
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class TleAnalysisServiceTest {
|
||||||
|
|
||||||
|
private val tleMonitoringService: TleMonitoringService = mock()
|
||||||
|
private val satelliteCatalogService: SatelliteCatalogService = mock()
|
||||||
|
private val service = TleAnalysisService(tleMonitoringService, satelliteCatalogService)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `satellites uses TLE satellite id and enriches rows from catalog by norad id`() {
|
||||||
|
doReturn(
|
||||||
|
listOf(
|
||||||
|
SatelliteSummaryDTO(
|
||||||
|
id = 101,
|
||||||
|
noradId = 62138,
|
||||||
|
code = "SAT-62138",
|
||||||
|
name = "Test satellite",
|
||||||
|
typeCode = "EO",
|
||||||
|
scanTle = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).`when`(satelliteCatalogService).allSatellites()
|
||||||
|
doReturn(
|
||||||
|
listOf(
|
||||||
|
TLEExtensionDTO(satelliteId = 62138, revolution = 10, tle = TLEDTO()),
|
||||||
|
TLEExtensionDTO(satelliteId = 62138, revolution = 11, tle = TLEDTO())
|
||||||
|
)
|
||||||
|
).`when`(tleMonitoringService).allTles()
|
||||||
|
|
||||||
|
val satellites = service.satellites()
|
||||||
|
|
||||||
|
assertEquals(1, satellites.size)
|
||||||
|
assertEquals(62138, satellites.single().id)
|
||||||
|
assertEquals(101, satellites.single().catalogId)
|
||||||
|
assertEquals(62138, satellites.single().noradId)
|
||||||
|
assertEquals("SAT-62138", satellites.single().code)
|
||||||
|
assertEquals("Test satellite", satellites.single().name)
|
||||||
|
assertEquals("EO", satellites.single().typeCode)
|
||||||
|
assertEquals(true, satellites.single().scanTle)
|
||||||
|
assertEquals(2, satellites.single().tleRecordsCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `satellites falls back to catalog when TLE records are empty`() {
|
||||||
|
doReturn(
|
||||||
|
listOf(
|
||||||
|
SatelliteSummaryDTO(
|
||||||
|
id = 101,
|
||||||
|
noradId = 62138,
|
||||||
|
code = "SAT-62138",
|
||||||
|
name = "Test satellite"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).`when`(satelliteCatalogService).allSatellites()
|
||||||
|
doReturn(emptyList<TLEExtensionDTO>()).`when`(tleMonitoringService).allTles()
|
||||||
|
|
||||||
|
val satellites = service.satellites()
|
||||||
|
|
||||||
|
assertEquals(1, satellites.size)
|
||||||
|
assertEquals(62138, satellites.single().id)
|
||||||
|
assertEquals(101, satellites.single().catalogId)
|
||||||
|
assertEquals(62138, satellites.single().noradId)
|
||||||
|
assertEquals(0, satellites.single().tleRecordsCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `analyze satellite uses only requested number of latest TLE records`() {
|
||||||
|
doReturn(
|
||||||
|
listOf(
|
||||||
|
tleRecord(revolution = 1, epoch = "21274.51041667"),
|
||||||
|
tleRecord(revolution = 2, epoch = "21275.51041667"),
|
||||||
|
tleRecord(revolution = 3, epoch = "21276.51041667")
|
||||||
|
)
|
||||||
|
).`when`(tleMonitoringService).satelliteTles(25544)
|
||||||
|
|
||||||
|
val response = service.analyzeSatellite(25544, tleCount = 2)
|
||||||
|
|
||||||
|
assertEquals(2, response.rows.size)
|
||||||
|
assertEquals(listOf(2L, 3L), response.rows.map { it.revolution })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `analyze satellite reports epoch difference between compared TLE records`() {
|
||||||
|
doReturn(
|
||||||
|
listOf(
|
||||||
|
tleRecord(revolution = 1, epoch = "21275.51041667"),
|
||||||
|
tleRecord(revolution = 2, epoch = "21276.01041667")
|
||||||
|
)
|
||||||
|
).`when`(tleMonitoringService).satelliteTles(25544)
|
||||||
|
|
||||||
|
val response = service.analyzeSatellite(25544, tleCount = 2)
|
||||||
|
|
||||||
|
assertEquals(2, response.rows.size)
|
||||||
|
val currentRow = response.rows[1]
|
||||||
|
val discrepancy = assertNotNull(currentRow.discrepancy)
|
||||||
|
assertEquals(response.rows[0].epoch, discrepancy.previousEpoch)
|
||||||
|
assertTrue(discrepancy.previousEpoch.isBefore(currentRow.epoch))
|
||||||
|
assertEquals(12.0, discrepancy.epochDifferenceHours, 0.001)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tleRecord(revolution: Long, epoch: String = "21275.51041667") =
|
||||||
|
TLEExtensionDTO(
|
||||||
|
satelliteId = 25544,
|
||||||
|
revolution = revolution,
|
||||||
|
tle = TLEDTO(
|
||||||
|
header = "ISS (ZARYA)",
|
||||||
|
first = "1 25544U 98067A $epoch .00000282 00000-0 12558-4 0 9993",
|
||||||
|
second = "2 25544 51.6435 177.7258 0003783 65.7820 55.4027 15.48915330299929"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
+31
-4
@@ -1,5 +1,6 @@
|
|||||||
package space.nstart.pcp.tle_monitoring_service.controller
|
package space.nstart.pcp.tle_monitoring_service.controller
|
||||||
|
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.PathVariable
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
@@ -12,12 +13,14 @@ import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
|
|||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEExtensionDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEExtensionDTO
|
||||||
import space.nstart.pcp.tle_monitoring_service.repository.TLERepository
|
import space.nstart.pcp.tle_monitoring_service.repository.TLERepository
|
||||||
import space.nstart.pcp.tle_monitoring_service.service.CelesTrakService
|
import space.nstart.pcp.tle_monitoring_service.service.CelesTrakService
|
||||||
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("v1/api/tle")
|
@RequestMapping("v1/api/tle")
|
||||||
class TLEController {
|
class TLEController {
|
||||||
|
|
||||||
|
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private lateinit var tleRepository: TLERepository
|
private lateinit var tleRepository: TLERepository
|
||||||
@@ -25,11 +28,35 @@ class TLEController {
|
|||||||
private lateinit var celestrakService: CelesTrakService
|
private lateinit var celestrakService: CelesTrakService
|
||||||
|
|
||||||
@GetMapping()
|
@GetMapping()
|
||||||
fun all() = tleRepository.findAll().map { tle -> tle.toDto() }
|
fun all(): List<TLEExtensionDTO> {
|
||||||
|
logger.info("TLE API all records request started")
|
||||||
|
var records = emptyList<TLEExtensionDTO>()
|
||||||
|
val durationMs = measureTimeMillis {
|
||||||
|
records = tleRepository.findAll().map { tle -> tle.toDto() }
|
||||||
|
}
|
||||||
|
logger.info(
|
||||||
|
"TLE API all records request completed: records={}, durationMs={}",
|
||||||
|
records.size,
|
||||||
|
durationMs
|
||||||
|
)
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/satellite/{satelliteId}")
|
@GetMapping("/satellite/{satelliteId}")
|
||||||
fun bySatellite(@PathVariable satelliteId: Long) =
|
fun bySatellite(@PathVariable satelliteId: Long): List<TLEExtensionDTO> {
|
||||||
tleRepository.findAllBySatelliteIdOrderByTimeTleAsc(satelliteId).map { tle -> tle.toDto() }
|
logger.info("TLE API satellite records request started: satelliteId={}", satelliteId)
|
||||||
|
var records = emptyList<TLEExtensionDTO>()
|
||||||
|
val durationMs = measureTimeMillis {
|
||||||
|
records = tleRepository.findAllBySatelliteIdOrderByTimeTleAsc(satelliteId).map { tle -> tle.toDto() }
|
||||||
|
}
|
||||||
|
logger.info(
|
||||||
|
"TLE API satellite records request completed: satelliteId={}, records={}, durationMs={}",
|
||||||
|
satelliteId,
|
||||||
|
records.size,
|
||||||
|
durationMs
|
||||||
|
)
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
private fun space.nstart.pcp.tle_monitoring_service.entity.TLEEntity.toDto(): TLEExtensionDTO {
|
private fun space.nstart.pcp.tle_monitoring_service.entity.TLEEntity.toDto(): TLEExtensionDTO {
|
||||||
val lines = tle.lines().map { it.trimEnd() }.filter { it.isNotBlank() }
|
val lines = tle.lines().map { it.trimEnd() }.filter { it.isNotBlank() }
|
||||||
@@ -72,4 +99,4 @@ class TLEController {
|
|||||||
fun tleFromSource(@RequestParam noradIn : Long, @RequestParam("message") sendMessage : Boolean) =
|
fun tleFromSource(@RequestParam noradIn : Long, @RequestParam("message") sendMessage : Boolean) =
|
||||||
celestrakService.getTleByNoradIdRaw(noradIn, false, sendMessage)
|
celestrakService.getTleByNoradIdRaw(noradIn, false, sendMessage)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -31,8 +31,8 @@ pluginManagement {
|
|||||||
maven {
|
maven {
|
||||||
url = uri("https://repo.nstart.cloud/repository/maven-proxy/")
|
url = uri("https://repo.nstart.cloud/repository/maven-proxy/")
|
||||||
}
|
}
|
||||||
// mavenCentral()
|
mavenCentral()
|
||||||
// gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
// maven {
|
// maven {
|
||||||
// url = uri("https://repo.nstart.local/repository/maven-proxy/")
|
// url = uri("https://repo.nstart.local/repository/maven-proxy/")
|
||||||
// }
|
// }
|
||||||
|
|||||||
Reference in New Issue
Block a user