анализ TLE
This commit is contained in:
@@ -95,7 +95,7 @@ variables:
|
||||
|
||||
ballistics:test:
|
||||
stage: test
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
services:
|
||||
- name: repo.nstart.cloud/postgis/postgis:16-3.4
|
||||
alias: postgres
|
||||
@@ -124,7 +124,7 @@ ballistics:test:
|
||||
|
||||
ballistics:build:
|
||||
stage: build
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .ballistics:rules:build-and-image
|
||||
- .ballistics:gradle-cache
|
||||
|
||||
@@ -139,7 +139,7 @@ variables:
|
||||
|
||||
complex-mission-service:test:
|
||||
stage: test
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .complex-mission-service:rules:test
|
||||
- .pcp-gradle-cache
|
||||
@@ -158,7 +158,7 @@ complex-mission-service:test:
|
||||
|
||||
complex-mission-service:build:
|
||||
stage: build
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .complex-mission-service:rules:build-and-image
|
||||
- .pcp-gradle-cache
|
||||
@@ -300,7 +300,7 @@ complex-mission-service:deploy:master:
|
||||
|
||||
tle-monitoring:test:
|
||||
stage: test
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
services:
|
||||
- name: repo.nstart.cloud/postgis/postgis:16-3.4
|
||||
alias: postgres
|
||||
@@ -330,7 +330,7 @@ tle-monitoring:test:
|
||||
|
||||
tle-monitoring:build:
|
||||
stage: build
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .tle-monitoring:rules:build-and-image
|
||||
- .pcp-gradle-cache
|
||||
|
||||
@@ -86,7 +86,7 @@ variables:
|
||||
|
||||
coverage-scheme:test:
|
||||
stage: test
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .coverage_scheme:rules:test
|
||||
- .coverage_scheme:gradle-cache
|
||||
@@ -105,7 +105,7 @@ coverage-scheme:test:
|
||||
|
||||
coverage-scheme:build:
|
||||
stage: build
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .coverage_scheme:rules:build-and-image
|
||||
- .coverage_scheme:gradle-cache
|
||||
|
||||
@@ -87,7 +87,7 @@ variables:
|
||||
|
||||
dynamic-plan:test:
|
||||
stage: test
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .dynamic_plan:rules:test
|
||||
- .dynamic_plan:gradle-cache
|
||||
@@ -106,7 +106,7 @@ dynamic-plan:test:
|
||||
|
||||
dynamic-plan:build:
|
||||
stage: build
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .dynamic_plan:rules:build-and-image
|
||||
- .dynamic_plan:gradle-cache
|
||||
|
||||
@@ -86,7 +86,7 @@ variables:
|
||||
|
||||
mission-planing:test:
|
||||
stage: test
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .mission-planing:rules:test
|
||||
- .mission-planing:gradle-cache
|
||||
@@ -105,7 +105,7 @@ mission-planing:test:
|
||||
|
||||
mission-planing:build:
|
||||
stage: build
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .mission-planing:rules:build-and-image
|
||||
- .mission-planing:gradle-cache
|
||||
|
||||
@@ -85,7 +85,7 @@ variables:
|
||||
|
||||
request:test:
|
||||
stage: test
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .request:rules:test
|
||||
- .request:gradle-cache
|
||||
@@ -104,7 +104,7 @@ request:test:
|
||||
|
||||
request:build:
|
||||
stage: build
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .request:rules:build-and-image
|
||||
- .request:gradle-cache
|
||||
|
||||
@@ -85,7 +85,7 @@ variables:
|
||||
|
||||
satellite-catalog:test:
|
||||
stage: test
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .satellite-catalog:rules:test
|
||||
- .satellite-catalog:gradle-cache
|
||||
@@ -104,7 +104,7 @@ satellite-catalog:test:
|
||||
|
||||
satellite-catalog:build:
|
||||
stage: build
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .satellite-catalog:rules:build-and-image
|
||||
- .satellite-catalog:gradle-cache
|
||||
|
||||
@@ -76,7 +76,7 @@ variables:
|
||||
|
||||
spring-boot-admin:test:
|
||||
stage: test
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .spring-boot-admin:rules:test
|
||||
- .spring-boot-admin:gradle-cache
|
||||
@@ -95,7 +95,7 @@ spring-boot-admin:test:
|
||||
|
||||
spring-boot-admin:build:
|
||||
stage: build
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .spring-boot-admin:rules:build-and-image
|
||||
- .spring-boot-admin:gradle-cache
|
||||
|
||||
@@ -85,7 +85,7 @@ variables:
|
||||
|
||||
stations:test:
|
||||
stage: test
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .stations:rules:test
|
||||
- .stations:gradle-cache
|
||||
@@ -104,7 +104,7 @@ stations:test:
|
||||
|
||||
stations:build:
|
||||
stage: build
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .stations:rules:build-and-image
|
||||
- .stations:gradle-cache
|
||||
|
||||
@@ -86,7 +86,7 @@ variables:
|
||||
|
||||
ui:test:
|
||||
stage: test
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .ui:rules:test
|
||||
- .ui:gradle-cache
|
||||
@@ -105,7 +105,7 @@ ui:test:
|
||||
|
||||
ui:build:
|
||||
stage: build
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .ui:rules:build-and-image
|
||||
- .ui:gradle-cache
|
||||
|
||||
@@ -86,7 +86,7 @@ variables:
|
||||
|
||||
slots:test:
|
||||
stage: test
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
services:
|
||||
- name: repo.nstart.cloud/postgis/postgis:16-3.4
|
||||
alias: postgres
|
||||
@@ -115,7 +115,7 @@ slots:test:
|
||||
|
||||
slots:build:
|
||||
stage: build
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .slots:rules:build-and-image
|
||||
- .slots:gradle-cache
|
||||
|
||||
@@ -82,7 +82,7 @@ variables:
|
||||
|
||||
config-server:test:
|
||||
stage: test
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .config-server:rules:test
|
||||
- .config-server:gradle-cache
|
||||
@@ -101,7 +101,7 @@ config-server:test:
|
||||
|
||||
config-server:build:
|
||||
stage: build
|
||||
image: repo.nstart.cloud/library/gradle:jdk17-ubi10
|
||||
image: repo.nstart.cloud/cloud/gradle:jdk17-ubi10
|
||||
extends:
|
||||
- .config-server:rules:build-and-image
|
||||
- .config-server:gradle-cache
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
pcp:
|
||||
infra:
|
||||
postgres:
|
||||
host: ${PCP_POSTGRES_HOST:localhost}
|
||||
port: ${PCP_POSTGRES_PORT:5432}
|
||||
# host: ${PCP_POSTGRES_HOST:localhost}
|
||||
# port: ${PCP_POSTGRES_PORT:5432}
|
||||
host: ${PCP_POSTGRES_HOST:192.168.100.160}
|
||||
port: ${PCP_POSTGRES_PORT:35400}
|
||||
kafka:
|
||||
host: ${PCP_KAFKA_HOST:localhost}
|
||||
port: ${PCP_KAFKA_PORT:29092}
|
||||
|
||||
@@ -136,7 +136,7 @@ spring:
|
||||
camunda:
|
||||
client:
|
||||
mode: self-managed
|
||||
grpc-address: http://camunda.k8s.265.nstart.local:30901
|
||||
grpc-address: http://camunda.k8s.265.nstart.cloud:30901
|
||||
auth:
|
||||
method: none
|
||||
prefer-rest-over-grpc: false
|
||||
|
||||
+27
-1
@@ -1,8 +1,10 @@
|
||||
package space.nstart.pcp.slots_service.controller
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
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.slots_service.service.TleAnalysisService
|
||||
|
||||
@@ -11,6 +13,30 @@ import space.nstart.pcp.slots_service.service.TleAnalysisService
|
||||
class TleAnalysisController(
|
||||
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}")
|
||||
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
|
||||
|
||||
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(
|
||||
val satelliteId: Long,
|
||||
val rows: List<TleAnalysisRowDTO>
|
||||
@@ -26,6 +38,7 @@ data class RadiusVectorDTO(
|
||||
|
||||
data class RadiusVectorDiscrepancyDTO(
|
||||
val previousEpoch: LocalDateTime,
|
||||
val epochDifferenceHours: Double,
|
||||
val dx: Double,
|
||||
val dy: Double,
|
||||
val dz: Double,
|
||||
|
||||
+190
-31
@@ -5,60 +5,183 @@ import ballistics.orbitalPoints.timeStepper.TLEStepper
|
||||
import ballistics.types.EarthType
|
||||
import ballistics.types.TLE
|
||||
import ballistics.utils.fromDateTime
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
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 space.nstart.pcp.slots_service.configuration.CustomErrorException
|
||||
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.TleAnalysisSatelliteDTO
|
||||
import space.nstart.pcp.slots_service.dto.tleanalysis.TleAnalysisResponseDTO
|
||||
import space.nstart.pcp.slots_service.dto.tleanalysis.TleAnalysisRowDTO
|
||||
import java.time.Duration
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.math.sqrt
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
@Service
|
||||
class TleAnalysisService(
|
||||
private val tleMonitoringService: TleMonitoringService
|
||||
private val tleMonitoringService: TleMonitoringService,
|
||||
private val satelliteCatalogService: SatelliteCatalogService
|
||||
) {
|
||||
fun analyzeSatellite(satelliteId: Long): TleAnalysisResponseDTO {
|
||||
val records = tleMonitoringService.satelliteTles(satelliteId)
|
||||
.mapNotNull { parseRecord(it) }
|
||||
.sortedBy { it.epoch }
|
||||
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||
|
||||
val rows = records.mapIndexed { index, record ->
|
||||
val currentPoint = calculatePoint(record.tle, record.epoch)
|
||||
val radiusVector = RadiusVectorDTO(
|
||||
x = currentPoint.r.x,
|
||||
y = currentPoint.r.y,
|
||||
z = currentPoint.r.z
|
||||
fun satellites(): List<TleAnalysisSatelliteDTO> {
|
||||
logger.info("TLE analysis satellites list stage started")
|
||||
var catalogSatellites = emptyList<SatelliteSummaryDTO>()
|
||||
val catalogLoadMs = measureTimeMillis {
|
||||
catalogSatellites = runCatching { satelliteCatalogService.allSatellites() }
|
||||
.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 previousPointAtCurrentEpoch = calculatePoint(previous.tle, record.epoch)
|
||||
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,
|
||||
dx = dx,
|
||||
dy = dy,
|
||||
dz = dz,
|
||||
norm = norm(dx, dy, dz)
|
||||
val catalogByNoradId = catalogSatellites
|
||||
.mapNotNull { satellite -> satellite.noradId?.let { noradId -> noradId to satellite } }
|
||||
.toMap()
|
||||
val catalogById = catalogSatellites.associateBy { it.id }
|
||||
|
||||
val satellites = tleRecords
|
||||
.groupingBy { record -> record.satelliteId }
|
||||
.eachCount()
|
||||
.entries
|
||||
.sortedBy { (satelliteId, _) -> satelliteId }
|
||||
.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(
|
||||
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
|
||||
fun analyzeSatellite(satelliteId: Long, tleCount: Int = DEFAULT_TLE_ANALYSIS_COUNT): TleAnalysisResponseDTO {
|
||||
val normalizedTleCount = tleCount.coerceAtLeast(1)
|
||||
logger.info(
|
||||
"TLE analysis report stage started: satelliteId={}, requestedTleCount={}, normalizedTleCount={}",
|
||||
satelliteId,
|
||||
tleCount,
|
||||
normalizedTleCount
|
||||
)
|
||||
|
||||
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(
|
||||
satelliteId = satelliteId,
|
||||
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 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(
|
||||
val tle: TLEDTO,
|
||||
val epoch: LocalDateTime,
|
||||
val revolution: 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
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.ObjectProvider
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
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.slots_service.configuration.CustomErrorException
|
||||
import tools.jackson.databind.ObjectMapper
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
@Service
|
||||
class TleMonitoringService(webClientBuilderProvider: ObjectProvider<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}")
|
||||
private val tleMonitoringUrl: String = ""
|
||||
|
||||
fun satelliteTles(satelliteId: Long): List<TLEExtensionDTO> =
|
||||
webClientBuilder.build()
|
||||
.get()
|
||||
.uri("$tleMonitoringUrl/v1/api/tle/satellite/$satelliteId")
|
||||
.retrieve()
|
||||
.onStatus({ status -> status.isError }, ::mapError)
|
||||
.bodyToFlux(TLEExtensionDTO::class.java)
|
||||
.collectList()
|
||||
.block()
|
||||
?: emptyList()
|
||||
fun allTles(): List<TLEExtensionDTO> {
|
||||
logger.info("TLE monitoring client all TLE request started")
|
||||
return runCatching {
|
||||
var records = emptyList<TLEExtensionDTO>()
|
||||
val durationMs = measureTimeMillis {
|
||||
records = webClientBuilder.build()
|
||||
.get()
|
||||
.uri("$tleMonitoringUrl/v1/api/tle")
|
||||
.retrieve()
|
||||
.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> =
|
||||
response.bodyToMono(String::class.java)
|
||||
|
||||
@@ -1,25 +1,40 @@
|
||||
.tle-analysis-page {
|
||||
height: calc(100vh - 4.75rem);
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tle-analysis-layout {
|
||||
flex: 1 1 auto;
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tle-analysis-layout > [class*="col-"] {
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tle-analysis-sidebar,
|
||||
.tle-analysis-results-card {
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
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,
|
||||
@@ -29,11 +44,28 @@
|
||||
}
|
||||
|
||||
.tle-analysis-satellites-wrap {
|
||||
flex: 1 1 auto;
|
||||
flex: 1 1 0;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.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,
|
||||
|
||||
@@ -15,23 +15,30 @@
|
||||
|
||||
function bindEvents() {
|
||||
document.getElementById('tle-analysis-refresh')?.addEventListener('click', async () => {
|
||||
state.selectedSatelliteId = null;
|
||||
state.analysis = null;
|
||||
state.loading = false;
|
||||
renderResults();
|
||||
await loadSatellites();
|
||||
});
|
||||
document.getElementById('tle-analysis-satellite-filter')?.addEventListener('input', renderSatellites);
|
||||
document.getElementById('tle-analysis-count')?.addEventListener('change', async () => {
|
||||
if (state.selectedSatelliteId) {
|
||||
await loadAnalysis(state.selectedSatelliteId);
|
||||
}
|
||||
});
|
||||
document.getElementById('tle-analysis-satellite-filter')?.addEventListener('input', renderSatellites);
|
||||
}
|
||||
|
||||
async function loadSatellites() {
|
||||
showAlert('Загрузка списка спутников...', 'info');
|
||||
try {
|
||||
const response = await fetch('/api/catalog/satellites');
|
||||
const response = await fetch('/api/tle-analysis/satellites');
|
||||
if (!response.ok) {
|
||||
throw new Error(await errorText(response));
|
||||
}
|
||||
state.satellites = await response.json();
|
||||
state.satellites = normalizeSatelliteResponse(await response.json());
|
||||
renderSatellites();
|
||||
renderResults();
|
||||
clearAlert();
|
||||
} catch (error) {
|
||||
showAlert(`Не удалось загрузить спутники: ${error.message}`, 'danger');
|
||||
@@ -44,7 +51,8 @@
|
||||
renderResults();
|
||||
showAlert('Формирование анализа TLE...', 'info');
|
||||
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) {
|
||||
throw new Error(await errorText(response));
|
||||
}
|
||||
@@ -53,10 +61,10 @@
|
||||
clearAlert();
|
||||
} catch (error) {
|
||||
state.analysis = null;
|
||||
renderResults();
|
||||
showAlert(`Не удалось сформировать анализ TLE: ${error.message}`, 'danger');
|
||||
} finally {
|
||||
state.loading = false;
|
||||
renderResults();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,10 +77,12 @@
|
||||
if (!filter) return true;
|
||||
return normalize([
|
||||
satellite.id,
|
||||
satellite.catalogId,
|
||||
satellite.noradId,
|
||||
satellite.code,
|
||||
satellite.name,
|
||||
satellite.typeCode
|
||||
satellite.typeCode,
|
||||
satellite.tleRecordsCount
|
||||
].join(' ')).includes(filter);
|
||||
});
|
||||
|
||||
@@ -84,12 +94,15 @@
|
||||
tbody.innerHTML = satellites.map((satellite) => {
|
||||
const active = Number(satellite.id) === Number(state.selectedSatelliteId) ? 'active' : '';
|
||||
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 `
|
||||
<tr class="${active}" data-satellite-id="${escapeHtml(satellite.id)}">
|
||||
<td>${escapeHtml(satellite.id)}</td>
|
||||
<td>${escapeHtml(catalogId)}</td>
|
||||
<td>
|
||||
<div class="catalog-main-text">${title}</div>
|
||||
<div class="catalog-helper">${escapeHtml(satellite.code || '')} ${satellite.typeCode ? ' / ' + escapeHtml(satellite.typeCode) : ''}</div>
|
||||
<div class="catalog-helper">TLE-записей: ${escapeHtml(tleCount)}</div>
|
||||
</td>
|
||||
<td>${satellite.noradId == null ? '—' : escapeHtml(satellite.noradId)}</td>
|
||||
</tr>`;
|
||||
@@ -116,7 +129,8 @@
|
||||
return;
|
||||
}
|
||||
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() {
|
||||
@@ -184,10 +198,38 @@
|
||||
z=${formatNumber(r.z, 3)}
|
||||
</td>
|
||||
<td>${formatNumber(row.radiusNorm, 3)}</td>
|
||||
<td>${discrepancy ? formatNumber(discrepancy.epochDifferenceHours, 3) : '—'}</td>
|
||||
<td>${diffText}</td>
|
||||
</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) {
|
||||
return String(value || '').trim().toLowerCase();
|
||||
}
|
||||
|
||||
@@ -15,7 +15,16 @@
|
||||
<h2 class="mb-1">Анализ TLE</h2>
|
||||
<div class="catalog-helper">Анализ изменения TLE выбранного спутника по разнице рассчитанных радиус-векторов.</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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,8 +38,8 @@
|
||||
<div class="catalog-section-title">Спутники</div>
|
||||
<div class="catalog-helper mt-1">Выберите КА для формирования аналитической таблицы TLE.</div>
|
||||
</div>
|
||||
<div class="card-body p-3">
|
||||
<label for="tle-analysis-satellite-filter" class="form-label">Фильтр</label>
|
||||
<div class="card-body p-2 tle-analysis-filter-body">
|
||||
<label for="tle-analysis-satellite-filter" class="form-label mb-1 small text-muted">Фильтр</label>
|
||||
<input id="tle-analysis-satellite-filter"
|
||||
type="search"
|
||||
class="form-control form-control-sm"
|
||||
@@ -73,6 +82,7 @@
|
||||
<th>NORAD</th>
|
||||
<th>Радиус-вектор, м</th>
|
||||
<th>|r|, м</th>
|
||||
<th>Разница Времени, ч</th>
|
||||
<th>Расхождение, м</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -84,8 +94,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
<th:block layout:fragment="scripts">
|
||||
<script src="/tle_analysis_scripts.js"></script>
|
||||
</th:block>
|
||||
</body>
|
||||
|
||||
+17
@@ -140,6 +140,23 @@ class CatalogControllerTest {
|
||||
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
|
||||
fun `stations page is rendered`() {
|
||||
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
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
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.tle_monitoring_service.repository.TLERepository
|
||||
import space.nstart.pcp.tle_monitoring_service.service.CelesTrakService
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("v1/api/tle")
|
||||
class TLEController {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||
|
||||
@Autowired
|
||||
private lateinit var tleRepository: TLERepository
|
||||
@@ -25,11 +28,35 @@ class TLEController {
|
||||
private lateinit var celestrakService: CelesTrakService
|
||||
|
||||
@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}")
|
||||
fun bySatellite(@PathVariable satelliteId: Long) =
|
||||
tleRepository.findAllBySatelliteIdOrderByTimeTleAsc(satelliteId).map { tle -> tle.toDto() }
|
||||
fun bySatellite(@PathVariable satelliteId: Long): List<TLEExtensionDTO> {
|
||||
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 {
|
||||
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) =
|
||||
celestrakService.getTleByNoradIdRaw(noradIn, false, sendMessage)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -31,8 +31,8 @@ pluginManagement {
|
||||
maven {
|
||||
url = uri("https://repo.nstart.cloud/repository/maven-proxy/")
|
||||
}
|
||||
// mavenCentral()
|
||||
// gradlePluginPortal()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
// maven {
|
||||
// url = uri("https://repo.nstart.local/repository/maven-proxy/")
|
||||
// }
|
||||
|
||||
Reference in New Issue
Block a user