feat(cuod-bundle): seed records auto-import on startup
CuodBundleImporter теперь после создания DictionaryDefinition подгружает records/<name>.records.json (relative to bundle resourceRoot) и создаёт DictionaryRecord для каждой записи. Идемпотентно: пропускает business_key, который уже существует. Bundle: - 5 satellite_type (SAR_X_HIRES, OPT_HIRES_VIS, OPT_MIDRES_MS, HYPER, METEO_GEO) - 4 spacecraft (Terra, Sentinel-1A, Sentinel-2A, Кондор-ФКА №2) - 3 ground_station (Отрадное, SvalSat, Байконур) Все с локализованными name/description (ru-RU + en-US), валидируемые JSON Schema на pre-save. Outbox events RecordCreated отправляются для projection-writer.
This commit is contained in:
+2
-1
@@ -24,5 +24,6 @@ public record BundleManifest(
|
|||||||
String schemaResource,
|
String schemaResource,
|
||||||
String schemaVersion,
|
String schemaVersion,
|
||||||
List<String> supportedLocales,
|
List<String> supportedLocales,
|
||||||
String defaultLocale) {}
|
String defaultLocale,
|
||||||
|
String recordsResource) {}
|
||||||
}
|
}
|
||||||
|
|||||||
+73
@@ -3,9 +3,12 @@ package cloud.nstart.terravault.ordinis.cuod;
|
|||||||
import cloud.nstart.terravault.ordinis.domain.DataScope;
|
import cloud.nstart.terravault.ordinis.domain.DataScope;
|
||||||
import cloud.nstart.terravault.ordinis.domain.definition.DictionaryDefinition;
|
import cloud.nstart.terravault.ordinis.domain.definition.DictionaryDefinition;
|
||||||
import cloud.nstart.terravault.ordinis.domain.definition.DictionaryDefinitionRepository;
|
import cloud.nstart.terravault.ordinis.domain.definition.DictionaryDefinitionRepository;
|
||||||
|
import cloud.nstart.terravault.ordinis.domain.record.DictionaryRecord;
|
||||||
|
import cloud.nstart.terravault.ordinis.domain.record.DictionaryRecordRepository;
|
||||||
import cloud.nstart.terravault.ordinis.events.EventEnvelope;
|
import cloud.nstart.terravault.ordinis.events.EventEnvelope;
|
||||||
import cloud.nstart.terravault.ordinis.events.definition.DictionaryDefinitionCreated;
|
import cloud.nstart.terravault.ordinis.events.definition.DictionaryDefinitionCreated;
|
||||||
import cloud.nstart.terravault.ordinis.events.definition.DictionaryDefinitionUpdated;
|
import cloud.nstart.terravault.ordinis.events.definition.DictionaryDefinitionUpdated;
|
||||||
|
import cloud.nstart.terravault.ordinis.events.record.DictionaryRecordCreated;
|
||||||
import cloud.nstart.terravault.ordinis.outbox.OutboxRecorder;
|
import cloud.nstart.terravault.ordinis.outbox.OutboxRecorder;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
@@ -19,6 +22,7 @@ import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -40,6 +44,7 @@ public class CuodBundleImporter {
|
|||||||
|
|
||||||
private final CuodBundleProperties props;
|
private final CuodBundleProperties props;
|
||||||
private final DictionaryDefinitionRepository repository;
|
private final DictionaryDefinitionRepository repository;
|
||||||
|
private final DictionaryRecordRepository recordRepository;
|
||||||
private final OutboxRecorder outboxRecorder;
|
private final OutboxRecorder outboxRecorder;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||||
@@ -47,10 +52,12 @@ public class CuodBundleImporter {
|
|||||||
public CuodBundleImporter(
|
public CuodBundleImporter(
|
||||||
CuodBundleProperties props,
|
CuodBundleProperties props,
|
||||||
DictionaryDefinitionRepository repository,
|
DictionaryDefinitionRepository repository,
|
||||||
|
DictionaryRecordRepository recordRepository,
|
||||||
OutboxRecorder outboxRecorder,
|
OutboxRecorder outboxRecorder,
|
||||||
ObjectMapper objectMapper) {
|
ObjectMapper objectMapper) {
|
||||||
this.props = props;
|
this.props = props;
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
|
this.recordRepository = recordRepository;
|
||||||
this.outboxRecorder = outboxRecorder;
|
this.outboxRecorder = outboxRecorder;
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
}
|
}
|
||||||
@@ -151,6 +158,72 @@ public class CuodBundleImporter {
|
|||||||
EventEnvelope.of(saved.getName(), toEventScope(saved.getScope()), manifest.bundle(), event));
|
EventEnvelope.of(saved.getName(), toEventScope(saved.getScope()), manifest.bundle(), event));
|
||||||
log.info("Справочник '{}' создан (scope={}, locales={}, default={})",
|
log.info("Справочник '{}' создан (scope={}, locales={}, default={})",
|
||||||
saved.getName(), saved.getScope(), entryLocales, entryDefault);
|
saved.getName(), saved.getScope(), entryLocales, entryDefault);
|
||||||
|
|
||||||
|
importRecords(manifest, entry, saved);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Подгружает seed records из {@code records/<name>.records.json} (если файл задан в
|
||||||
|
* manifest через {@code recordsResource} или существует по конвенции). Идемпотентно:
|
||||||
|
* при повторном вызове не создаёт дубликатов — пропускает существующие businessKey.
|
||||||
|
*/
|
||||||
|
private void importRecords(BundleManifest manifest, BundleManifest.DictionaryEntry entry,
|
||||||
|
DictionaryDefinition definition) {
|
||||||
|
String recordsPath = entry.recordsResource() != null
|
||||||
|
? entry.recordsResource()
|
||||||
|
: "records/" + entry.name() + ".records.json";
|
||||||
|
Resource recordsResource = resolver.getResource(props.resourceRoot() + recordsPath);
|
||||||
|
if (!recordsResource.exists()) {
|
||||||
|
log.debug("Records file для '{}' не найден ({}), пропускаем seed", entry.name(), recordsPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonNode recordsArray;
|
||||||
|
try (var in = recordsResource.getInputStream()) {
|
||||||
|
recordsArray = objectMapper.readTree(in);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Не удалось прочитать records '{}': {}", recordsPath, e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!recordsArray.isArray()) {
|
||||||
|
log.warn("Records '{}' не массив, пропускаем", recordsPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int created = 0;
|
||||||
|
int skipped = 0;
|
||||||
|
OffsetDateTime now = OffsetDateTime.now();
|
||||||
|
for (JsonNode rec : recordsArray) {
|
||||||
|
String businessKey = rec.path("businessKey").asText(null);
|
||||||
|
JsonNode data = rec.path("data");
|
||||||
|
if (businessKey == null || businessKey.isBlank() || data.isMissingNode()) {
|
||||||
|
log.warn("Пропуск некорректной записи в '{}': missing businessKey/data", recordsPath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<DictionaryRecord> existing = recordRepository
|
||||||
|
.findByDictionaryIdAndBusinessKeyOrderByValidFromDesc(definition.getId(), businessKey);
|
||||||
|
if (!existing.isEmpty()) {
|
||||||
|
skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
DictionaryRecord record = new DictionaryRecord(
|
||||||
|
UUID.randomUUID(), definition.getId(), businessKey, data, definition.getScope(), now);
|
||||||
|
DictionaryRecord savedRecord = recordRepository.save(record);
|
||||||
|
|
||||||
|
var recEvent = new DictionaryRecordCreated(
|
||||||
|
savedRecord.getId(), savedRecord.getBusinessKey(), savedRecord.getData(), null,
|
||||||
|
savedRecord.getValidFrom(), savedRecord.getValidTo(),
|
||||||
|
savedRecord.getCreatedAt(), savedRecord.getCreatedBy());
|
||||||
|
outboxRecorder.record(
|
||||||
|
"RecordCreated", "DictionaryRecord", savedRecord.getId().toString(),
|
||||||
|
definition.getName(), savedRecord.getDataScope(), manifest.bundle(),
|
||||||
|
"record:" + definition.getName() + ":" + savedRecord.getBusinessKey(),
|
||||||
|
EventEnvelope.of(definition.getName(), toEventScope(savedRecord.getDataScope()),
|
||||||
|
manifest.bundle(), recEvent));
|
||||||
|
created++;
|
||||||
|
}
|
||||||
|
log.info("Records для '{}': создано {}, пропущено (уже есть) {}",
|
||||||
|
entry.name(), created, skipped);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static cloud.nstart.terravault.ordinis.events.DataScope toEventScope(DataScope d) {
|
private static cloud.nstart.terravault.ordinis.events.DataScope toEventScope(DataScope d) {
|
||||||
|
|||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"businessKey": "MOSCOW_OTRADNOE",
|
||||||
|
"data": {
|
||||||
|
"code": "MOSCOW_OTRADNOE",
|
||||||
|
"name": {
|
||||||
|
"ru-RU": "Отрадное",
|
||||||
|
"en-US": "Otradnoye"
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"ru-RU": "Московская область, Россия",
|
||||||
|
"en-US": "Moscow Region, Russia"
|
||||||
|
},
|
||||||
|
"country": "RU",
|
||||||
|
"lat": 56.099,
|
||||||
|
"lon": 37.566,
|
||||||
|
"elevation_m": 200,
|
||||||
|
"elevation_min_deg": 5,
|
||||||
|
"antennas": [
|
||||||
|
{
|
||||||
|
"id": "A1",
|
||||||
|
"diameter_m": 12,
|
||||||
|
"bands": ["X_BAND", "S_BAND"],
|
||||||
|
"polarizations": ["LHCP", "RHCP"],
|
||||||
|
"tx_capable": true,
|
||||||
|
"rx_capable": true,
|
||||||
|
"g_t_db_per_k": 32
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operator": {
|
||||||
|
"ru-RU": "Роскосмос",
|
||||||
|
"en-US": "Roscosmos"
|
||||||
|
},
|
||||||
|
"status": "OPERATIONAL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"businessKey": "SVALBARD_SVALSAT",
|
||||||
|
"data": {
|
||||||
|
"code": "SVALBARD_SVALSAT",
|
||||||
|
"name": {
|
||||||
|
"ru-RU": "SvalSat (Шпицберген)",
|
||||||
|
"en-US": "SvalSat (Svalbard)"
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"ru-RU": "Шпицберген, Норвегия",
|
||||||
|
"en-US": "Svalbard, Norway"
|
||||||
|
},
|
||||||
|
"country": "NO",
|
||||||
|
"lat": 78.23,
|
||||||
|
"lon": 15.39,
|
||||||
|
"elevation_m": 458,
|
||||||
|
"elevation_min_deg": 3,
|
||||||
|
"antennas": [
|
||||||
|
{
|
||||||
|
"id": "S11",
|
||||||
|
"diameter_m": 13,
|
||||||
|
"bands": ["X_BAND", "S_BAND"],
|
||||||
|
"polarizations": ["LHCP", "RHCP"],
|
||||||
|
"tx_capable": true,
|
||||||
|
"rx_capable": true,
|
||||||
|
"g_t_db_per_k": 33
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operator": {
|
||||||
|
"ru-RU": "KSAT",
|
||||||
|
"en-US": "KSAT"
|
||||||
|
},
|
||||||
|
"status": "OPERATIONAL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"businessKey": "BAIKONUR_TM",
|
||||||
|
"data": {
|
||||||
|
"code": "BAIKONUR_TM",
|
||||||
|
"name": {
|
||||||
|
"ru-RU": "Байконур (телеметрия)",
|
||||||
|
"en-US": "Baikonur (telemetry)"
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"ru-RU": "Байконур, Казахстан",
|
||||||
|
"en-US": "Baikonur, Kazakhstan"
|
||||||
|
},
|
||||||
|
"country": "KZ",
|
||||||
|
"lat": 45.965,
|
||||||
|
"lon": 63.305,
|
||||||
|
"elevation_m": 90,
|
||||||
|
"elevation_min_deg": 5,
|
||||||
|
"antennas": [
|
||||||
|
{
|
||||||
|
"id": "BK-1",
|
||||||
|
"diameter_m": 9,
|
||||||
|
"bands": ["S_BAND", "UHF"],
|
||||||
|
"rx_capable": true,
|
||||||
|
"tx_capable": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operator": {
|
||||||
|
"ru-RU": "Роскосмос",
|
||||||
|
"en-US": "Roscosmos"
|
||||||
|
},
|
||||||
|
"status": "OPERATIONAL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"businessKey": "SAR_X_HIRES",
|
||||||
|
"data": {
|
||||||
|
"code": "SAR_X_HIRES",
|
||||||
|
"name": {
|
||||||
|
"ru-RU": "Радиолокатор синтезированной апертуры, X-диапазон, высокое разрешение",
|
||||||
|
"en-US": "Synthetic Aperture Radar, X-band, high resolution"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"ru-RU": "SAR X-band КА с разрешением до 1 м",
|
||||||
|
"en-US": "SAR X-band satellite with up to 1 m resolution"
|
||||||
|
},
|
||||||
|
"mission_category": "RADAR_SAR",
|
||||||
|
"mass_class": "MEDIUM",
|
||||||
|
"typical_orbit_classes": ["LEO", "SSO"],
|
||||||
|
"typical_resolution_m": 1,
|
||||||
|
"typical_revisit_hours": 24
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"businessKey": "OPT_HIRES_VIS",
|
||||||
|
"data": {
|
||||||
|
"code": "OPT_HIRES_VIS",
|
||||||
|
"name": {
|
||||||
|
"ru-RU": "Оптический КА высокого разрешения, видимый диапазон",
|
||||||
|
"en-US": "Optical high-resolution satellite, visible band"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"ru-RU": "Оптика 0.5-1 м VIS, узкая полоса",
|
||||||
|
"en-US": "Optical 0.5-1 m VIS, narrow swath"
|
||||||
|
},
|
||||||
|
"mission_category": "OPTICAL_HIRES",
|
||||||
|
"mass_class": "MEDIUM",
|
||||||
|
"typical_orbit_classes": ["SSO"],
|
||||||
|
"typical_resolution_m": 0.5,
|
||||||
|
"typical_revisit_hours": 48
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"businessKey": "OPT_MIDRES_MS",
|
||||||
|
"data": {
|
||||||
|
"code": "OPT_MIDRES_MS",
|
||||||
|
"name": {
|
||||||
|
"ru-RU": "Оптический мультиспектральный, среднее разрешение",
|
||||||
|
"en-US": "Optical multispectral, medium resolution"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"ru-RU": "Multispectral 5-30 м, широкая полоса (типа Landsat/Sentinel-2)",
|
||||||
|
"en-US": "Multispectral 5-30 m, wide swath (Landsat/Sentinel-2 class)"
|
||||||
|
},
|
||||||
|
"mission_category": "OPTICAL_MIDRES",
|
||||||
|
"mass_class": "LARGE",
|
||||||
|
"typical_orbit_classes": ["SSO"],
|
||||||
|
"typical_resolution_m": 10,
|
||||||
|
"typical_revisit_hours": 120
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"businessKey": "HYPER",
|
||||||
|
"data": {
|
||||||
|
"code": "HYPER",
|
||||||
|
"name": {
|
||||||
|
"ru-RU": "Гиперспектральный сканер",
|
||||||
|
"en-US": "Hyperspectral imager"
|
||||||
|
},
|
||||||
|
"mission_category": "HYPERSPECTRAL",
|
||||||
|
"mass_class": "MEDIUM",
|
||||||
|
"typical_orbit_classes": ["SSO"],
|
||||||
|
"typical_resolution_m": 30,
|
||||||
|
"typical_revisit_hours": 168
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"businessKey": "METEO_GEO",
|
||||||
|
"data": {
|
||||||
|
"code": "METEO_GEO",
|
||||||
|
"name": {
|
||||||
|
"ru-RU": "Метеоспутник на геостационарной орбите",
|
||||||
|
"en-US": "Geostationary meteorological satellite"
|
||||||
|
},
|
||||||
|
"mission_category": "METEO",
|
||||||
|
"mass_class": "LARGE",
|
||||||
|
"typical_orbit_classes": ["GEO"],
|
||||||
|
"typical_resolution_m": 1000,
|
||||||
|
"typical_revisit_hours": 0.25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"businessKey": "1999-068A",
|
||||||
|
"data": {
|
||||||
|
"designator": "1999-068A",
|
||||||
|
"norad_id": 25994,
|
||||||
|
"name": {
|
||||||
|
"ru-RU": "Terra (EOS AM-1)",
|
||||||
|
"en-US": "Terra (EOS AM-1)"
|
||||||
|
},
|
||||||
|
"operator": {
|
||||||
|
"ru-RU": "НАСА",
|
||||||
|
"en-US": "NASA"
|
||||||
|
},
|
||||||
|
"country": "US",
|
||||||
|
"satellite_type_code": "OPT_MIDRES_MS",
|
||||||
|
"mass_kg": 4864,
|
||||||
|
"launch_date": "1999-12-18",
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"orbit": {
|
||||||
|
"type": "SSO",
|
||||||
|
"semimajor_axis_km": 7083,
|
||||||
|
"eccentricity": 0.0001,
|
||||||
|
"inclination_deg": 98.2
|
||||||
|
},
|
||||||
|
"spectrum_bands": ["VIS", "NIR", "SWIR", "TIR"],
|
||||||
|
"mission": {
|
||||||
|
"ru-RU": "Глобальный мониторинг суши и атмосферы",
|
||||||
|
"en-US": "Global land and atmosphere monitoring"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"businessKey": "2014-016A",
|
||||||
|
"data": {
|
||||||
|
"designator": "2014-016A",
|
||||||
|
"norad_id": 39634,
|
||||||
|
"name": {
|
||||||
|
"ru-RU": "Sentinel-1A",
|
||||||
|
"en-US": "Sentinel-1A"
|
||||||
|
},
|
||||||
|
"operator": {
|
||||||
|
"ru-RU": "ESA / Copernicus",
|
||||||
|
"en-US": "ESA / Copernicus"
|
||||||
|
},
|
||||||
|
"country": "FR",
|
||||||
|
"satellite_type_code": "SAR_X_HIRES",
|
||||||
|
"mass_kg": 2300,
|
||||||
|
"launch_date": "2014-04-03",
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"orbit": {
|
||||||
|
"type": "SSO",
|
||||||
|
"semimajor_axis_km": 7064,
|
||||||
|
"eccentricity": 0.0001,
|
||||||
|
"inclination_deg": 98.18
|
||||||
|
},
|
||||||
|
"spectrum_bands": ["C_BAND"],
|
||||||
|
"mission": {
|
||||||
|
"ru-RU": "C-SAR радиолокация для Copernicus",
|
||||||
|
"en-US": "C-SAR radar for Copernicus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"businessKey": "2015-028A",
|
||||||
|
"data": {
|
||||||
|
"designator": "2015-028A",
|
||||||
|
"norad_id": 40697,
|
||||||
|
"name": {
|
||||||
|
"ru-RU": "Sentinel-2A",
|
||||||
|
"en-US": "Sentinel-2A"
|
||||||
|
},
|
||||||
|
"operator": {
|
||||||
|
"ru-RU": "ESA / Copernicus",
|
||||||
|
"en-US": "ESA / Copernicus"
|
||||||
|
},
|
||||||
|
"country": "FR",
|
||||||
|
"satellite_type_code": "OPT_MIDRES_MS",
|
||||||
|
"mass_kg": 1140,
|
||||||
|
"launch_date": "2015-06-23",
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"orbit": {
|
||||||
|
"type": "SSO",
|
||||||
|
"semimajor_axis_km": 7167,
|
||||||
|
"inclination_deg": 98.62
|
||||||
|
},
|
||||||
|
"spectrum_bands": ["VIS", "NIR", "SWIR"],
|
||||||
|
"mission": {
|
||||||
|
"ru-RU": "Multispectral imaging для Copernicus",
|
||||||
|
"en-US": "Multispectral imaging for Copernicus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"businessKey": "2024-149E",
|
||||||
|
"data": {
|
||||||
|
"designator": "2024-149E",
|
||||||
|
"name": {
|
||||||
|
"ru-RU": "Кондор-ФКА №2",
|
||||||
|
"en-US": "Kondor-FKA No.2"
|
||||||
|
},
|
||||||
|
"operator": {
|
||||||
|
"ru-RU": "Роскосмос",
|
||||||
|
"en-US": "Roscosmos"
|
||||||
|
},
|
||||||
|
"country": "RU",
|
||||||
|
"satellite_type_code": "SAR_X_HIRES",
|
||||||
|
"mass_kg": 1050,
|
||||||
|
"launch_date": "2024-08-13",
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"orbit": {
|
||||||
|
"type": "SSO",
|
||||||
|
"inclination_deg": 97.5
|
||||||
|
},
|
||||||
|
"spectrum_bands": ["S_BAND"],
|
||||||
|
"mission": {
|
||||||
|
"ru-RU": "Радиолокационное наблюдение Земли",
|
||||||
|
"en-US": "Radar Earth observation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user