Задание НУ из ui

This commit is contained in:
emelianov
2026-05-29 10:52:36 +03:00
parent 7dfa1163e9
commit d693bf799d
13 changed files with 393 additions and 8 deletions
@@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.server.ResponseStatusException
import space.nstart.pcp.pcp_types_lib.dto.ballistics.InitialConditionsDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteICDTO
import space.nstart.pcp.pcp_types_lib.dto.requests.slots.SlotCalculationSummaryDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteCreateDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteGroupCreateDTO
@@ -22,6 +23,7 @@ import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteObservationProfileD
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteSlotProfileDTO
import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteUpdateDTO
import space.nstart.pcp.slots_service.service.GroupStateService
import space.nstart.pcp.slots_service.service.BallisticsService
import space.nstart.pcp.slots_service.service.SatelliteCatalogService
import space.nstart.pcp.slots_service.service.SatellitePdcmService
import space.nstart.pcp.slots_service.service.SlotService
@@ -36,7 +38,8 @@ class CatalogRestController(
private val satelliteCatalogService: SatelliteCatalogService,
private val slotService: SlotService,
private val groupStateService: GroupStateService,
private val satellitePdcmService: SatellitePdcmService
private val satellitePdcmService: SatellitePdcmService,
private val ballisticsService: BallisticsService
) {
private val sendInitialConditionsTimeFormatter =
DateTimeFormatter.ofPattern("dd.MM.uuuu HH:mm:ss.SSS").withResolverStyle(ResolverStyle.STRICT)
@@ -73,6 +76,21 @@ class CatalogRestController(
return ResponseEntity.accepted().build()
}
@PostMapping("/satellites/pdcm/{satelliteId}/initial-conditions")
fun setSatelliteInitialConditions(
@PathVariable satelliteId: Long,
@RequestBody request: SatelliteICDTO
): ResponseEntity<Void> {
ballisticsService.receiveRv(
SatelliteICDTO(
satelliteId = satelliteId,
ic = request.ic,
movementModel = request.movementModel
)
)
return ResponseEntity.accepted().build()
}
private fun parseSendInitialConditionsTime(time: String?): LocalDateTime {
val normalizedTime = time?.trim()?.takeIf { it.isNotEmpty() }
?: return LocalDateTime.now()
@@ -9,6 +9,7 @@ import space.nstart.pcp.pcp_types_lib.dto.ballistics.ExactTimePositionRequestDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.AscNodeDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.FlightLineDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteICDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteOrbitAvailabilityDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TlePointRequestDTO
@@ -71,6 +72,16 @@ class BallisticsService (webClientBuilderProvider: ObjectProvider<WebClient.Buil
?.firstOrNull()
?: throw CustomErrorException("Не удалось получить положение спутника $id на время $time")
fun receiveRv(request: SatelliteICDTO) {
webClientBuilder.build()
.post()
.uri("$url/api/satellites/receive-rv")
.bodyValue(request)
.retrieve()
.toBodilessEntity()
.block()
}
fun parseTle(tle: TLEDTO): TleParsedDTO =
webClientBuilder.build()
.post()
@@ -15,6 +15,12 @@
width: 15rem;
}
.satellite-pdcm-ic-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
gap: 1rem;
}
.satellite-pdcm-status {
display: inline-flex;
width: 0.75rem;
@@ -110,6 +110,55 @@
};
}
function parseDateTimeForPayload(value, fieldName) {
const text = value?.trim() || '';
const match = text.match(sendInitialConditionsDateTimePattern);
if (!match) {
throw new Error(`${fieldName}: укажите время в формате dd.mm.yyyy hh:MM:ss.zzz`);
}
const [, day, month, year, hour, minute, second, millisecond] = match;
const parsed = new Date(
Number(year),
Number(month) - 1,
Number(day),
Number(hour),
Number(minute),
Number(second),
Number(millisecond)
);
const validDate = parsed.getFullYear() === Number(year)
&& parsed.getMonth() === Number(month) - 1
&& parsed.getDate() === Number(day)
&& parsed.getHours() === Number(hour)
&& parsed.getMinutes() === Number(minute)
&& parsed.getSeconds() === Number(second)
&& parsed.getMilliseconds() === Number(millisecond);
if (!validDate) {
throw new Error(`${fieldName}: некорректная дата или время`);
}
return `${year}-${month}-${day}T${hour}:${minute}:${second}.${millisecond}`;
}
function parseRequiredNumber(inputId, fieldName) {
const value = document.getElementById(inputId)?.value;
const number = Number(value);
if (value === '' || !Number.isFinite(number)) {
throw new Error(`${fieldName}: укажите число`);
}
return number;
}
function parseRequiredInteger(inputId, fieldName) {
const number = parseRequiredNumber(inputId, fieldName);
if (!Number.isInteger(number)) {
throw new Error(`${fieldName}: укажите целое число`);
}
return number;
}
function formatNumber(value) {
return Number(value ?? 0).toFixed(3);
}
@@ -142,6 +191,19 @@
}
}
function renderSetInitialConditionsState(details, loading = false) {
const openButton = document.getElementById('satellite-pdcm-open-set-ic');
const submitButton = document.getElementById('satellite-pdcm-set-ic-submit');
if (openButton) {
openButton.disabled = !details || loading;
openButton.title = details ? '' : 'Выберите спутник';
}
if (submitButton) {
submitButton.disabled = loading;
submitButton.textContent = loading ? 'Запуск...' : 'Запустить расчет';
}
}
function renderList() {
const tableBody = document.getElementById('satellite-pdcm-list');
tableBody.innerHTML = '';
@@ -182,6 +244,7 @@
summary.innerHTML = '<div class="catalog-helper">Выберите спутник слева.</div>';
renderExportState(null);
renderSendInitialConditionsState(null);
renderSetInitialConditionsState(null);
return;
}
@@ -213,6 +276,7 @@
`;
renderExportState(details);
renderSendInitialConditionsState(details);
renderSetInitialConditionsState(details);
}
function renderTable(details) {
@@ -365,10 +429,89 @@
}
}
function openSetInitialConditionsModal() {
if (!state.selectedSatelliteId || !state.details) {
showAlert('Выберите спутник для задания начальных условий');
return;
}
document.getElementById('satellite-pdcm-set-ic-target').textContent =
`${state.details.name} · ID ${state.details.satelliteId}`;
document.getElementById('satellite-pdcm-ic-time').value = currentDateTimeInputValue();
document.getElementById('satellite-pdcm-ic-revolution').value = '';
document.getElementById('satellite-pdcm-ic-movement-model').value = 'FOTO';
document.getElementById('satellite-pdcm-ic-s-ball').value = '0';
document.getElementById('satellite-pdcm-ic-f81').value = '147.8';
['x', 'y', 'z', 'vx', 'vy', 'vz'].forEach(field => {
document.getElementById(`satellite-pdcm-ic-${field}`).value = '';
});
if (window.bootstrap) {
window.bootstrap.Modal.getOrCreateInstance(document.getElementById('satellite-pdcm-set-ic-modal')).show();
}
}
function buildInitialConditionsPayload() {
const movementModel = document.getElementById('satellite-pdcm-ic-movement-model').value || 'FOTO';
return {
satelliteId: state.selectedSatelliteId,
movementModel,
ic: {
orbPoint: {
time: parseDateTimeForPayload(document.getElementById('satellite-pdcm-ic-time').value, 'Time'),
revolution: parseRequiredInteger('satellite-pdcm-ic-revolution', 'Revolution'),
vx: parseRequiredNumber('satellite-pdcm-ic-vx', 'Vx'),
vy: parseRequiredNumber('satellite-pdcm-ic-vy', 'Vy'),
vz: parseRequiredNumber('satellite-pdcm-ic-vz', 'Vz'),
x: parseRequiredNumber('satellite-pdcm-ic-x', 'X'),
y: parseRequiredNumber('satellite-pdcm-ic-y', 'Y'),
z: parseRequiredNumber('satellite-pdcm-ic-z', 'Z')
},
sBall: parseRequiredNumber('satellite-pdcm-ic-s-ball', 'S-ball'),
f81: parseRequiredNumber('satellite-pdcm-ic-f81', 'F81')
}
};
}
async function submitInitialConditions(event) {
event.preventDefault();
if (!state.selectedSatelliteId || !state.details) {
showAlert('Выберите спутник для задания начальных условий');
return;
}
let payload;
try {
payload = buildInitialConditionsPayload();
} catch (error) {
showAlert(error.message || 'Проверьте параметры начальных условий');
return;
}
renderSetInitialConditionsState(state.details, true);
showAlert('');
try {
await requestJson(`${apiBase}/${state.selectedSatelliteId}/initial-conditions`, {
method: 'POST',
body: JSON.stringify(payload)
});
const modalElement = document.getElementById('satellite-pdcm-set-ic-modal');
if (window.bootstrap && modalElement) {
window.bootstrap.Modal.getOrCreateInstance(modalElement).hide();
}
showAlert('Расчет ПДЦМ по начальным условиям запущен', 'success');
} catch (error) {
showAlert(error.message || 'Не удалось запустить расчет ПДЦМ по начальным условиям');
} finally {
renderSetInitialConditionsState(state.details);
}
}
async function selectSatellite(satelliteId) {
state.selectedSatelliteId = satelliteId;
renderList();
renderSendInitialConditionsState(null);
renderSetInitialConditionsState(null);
showAlert('');
try {
@@ -414,6 +557,8 @@
});
document.getElementById('satellite-pdcm-export').addEventListener('click', exportCurrentCsv);
document.getElementById('satellite-pdcm-send-ic').addEventListener('click', sendInitialConditions);
document.getElementById('satellite-pdcm-open-set-ic').addEventListener('click', openSetInitialConditionsModal);
document.getElementById('satellite-pdcm-set-ic-form').addEventListener('submit', submitInitialConditions);
const sendInitialConditionsTimeInput = document.getElementById('satellite-pdcm-send-ic-time');
sendInitialConditionsTimeInput.value = currentDateTimeInputValue();
sendInitialConditionsTimeInput.addEventListener('input', () => renderSendInitialConditionsState(state.details));
@@ -28,6 +28,7 @@
/>
</div>
<button id="satellite-pdcm-send-ic" type="button" class="btn btn-outline-primary" disabled>Передать НУ в slot-service</button>
<button id="satellite-pdcm-open-set-ic" type="button" class="btn btn-primary" disabled>Задать НУ</button>
<button id="satellite-pdcm-refresh" type="button" class="btn btn-outline-secondary">Обновить</button>
</div>
</div>
@@ -80,6 +81,84 @@
</div>
</div>
</div>
<div class="modal fade" id="satellite-pdcm-set-ic-modal" tabindex="-1" aria-labelledby="satellite-pdcm-set-ic-title" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<form id="satellite-pdcm-set-ic-form" class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="satellite-pdcm-set-ic-title">Задать начальные условия</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<div class="modal-body">
<div id="satellite-pdcm-set-ic-target" class="catalog-helper mb-3">Выберите спутник слева.</div>
<div class="satellite-pdcm-ic-grid">
<div>
<label for="satellite-pdcm-ic-time" class="form-label">Time</label>
<input id="satellite-pdcm-ic-time" type="text" class="form-control" placeholder="dd.mm.yyyy hh:mm:ss.zzz" required/>
</div>
<div>
<label for="satellite-pdcm-ic-revolution" class="form-label">Revolution</label>
<input id="satellite-pdcm-ic-revolution" type="number" class="form-control" required/>
</div>
<div>
<label for="satellite-pdcm-ic-movement-model" class="form-label">Модель движения</label>
<select id="satellite-pdcm-ic-movement-model" class="form-select" required>
<option value="FOTO" selected>FOTO</option>
<option value="KONDOR">KONDOR</option>
<option value="METEORM1">METEORM1</option>
<option value="METEORM2">METEORM2</option>
<option value="BARS">BARS</option>
<option value="KONDOR_PROGNOZ">KONDOR_PROGNOZ</option>
</select>
</div>
<div>
<label for="satellite-pdcm-ic-s-ball" class="form-label">S-ball</label>
<input id="satellite-pdcm-ic-s-ball" type="number" step="any" class="form-control" value="0" required/>
</div>
<div>
<label for="satellite-pdcm-ic-f81" class="form-label">F81</label>
<input id="satellite-pdcm-ic-f81" type="number" step="any" class="form-control" value="147.8" required/>
</div>
</div>
<div class="satellite-pdcm-ic-grid mt-3">
<div>
<label for="satellite-pdcm-ic-x" class="form-label">X</label>
<input id="satellite-pdcm-ic-x" type="number" step="any" class="form-control" required/>
</div>
<div>
<label for="satellite-pdcm-ic-y" class="form-label">Y</label>
<input id="satellite-pdcm-ic-y" type="number" step="any" class="form-control" required/>
</div>
<div>
<label for="satellite-pdcm-ic-z" class="form-label">Z</label>
<input id="satellite-pdcm-ic-z" type="number" step="any" class="form-control" required/>
</div>
</div>
<div class="satellite-pdcm-ic-grid mt-3">
<div>
<label for="satellite-pdcm-ic-vx" class="form-label">Vx</label>
<input id="satellite-pdcm-ic-vx" type="number" step="any" class="form-control" required/>
</div>
<div>
<label for="satellite-pdcm-ic-vy" class="form-label">Vy</label>
<input id="satellite-pdcm-ic-vy" type="number" step="any" class="form-control" required/>
</div>
<div>
<label for="satellite-pdcm-ic-vz" class="form-label">Vz</label>
<input id="satellite-pdcm-ic-vz" type="number" step="any" class="form-control" required/>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Отмена</button>
<button id="satellite-pdcm-set-ic-submit" type="submit" class="btn btn-primary">Запустить расчет</button>
</div>
</form>
</div>
</div>
</div>
<script src="/satellite_pdcm_scripts.js"></script>
</th:block>