CCSDS 503.0-B Tracking Data Message parser and serializer
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

irmin: revert ~src/~dst produce

+440
+440
test/interop/orekit/scripts/generate.java
··· 1 + ///usr/bin/env jbang "$0" "$@" ; exit $? 2 + //DEPS org.orekit:orekit:12.2 3 + 4 + // Generate CCSDS 503.0-B TDM interop traces for ocaml-tdm. 5 + // 6 + // Oracle: OREKIT 12.2 (Java) -- ESA's open-source flight dynamics library. 7 + // Run: jbang generate.java <TRACE_DIR> 8 + // 9 + // OREKIT needs orekit-data for time scale initialisation. This script 10 + // downloads the data zip automatically on first run and caches it next 11 + // to the script. 12 + 13 + import org.orekit.data.DataContext; 14 + import org.orekit.data.DataProvidersManager; 15 + import org.orekit.data.DirectoryCrawler; 16 + import org.orekit.data.ZipJarCrawler; 17 + import org.orekit.files.ccsds.definitions.TimeSystem; 18 + import org.orekit.files.ccsds.ndm.tdm.ObservationType; 19 + import org.orekit.files.ccsds.ndm.tdm.ObservationsBlock; 20 + import org.orekit.files.ccsds.ndm.tdm.RangeMode; 21 + import org.orekit.files.ccsds.ndm.tdm.RangeUnits; 22 + import org.orekit.files.ccsds.ndm.tdm.TdmFile; 23 + import org.orekit.files.ccsds.ndm.tdm.TdmHeader; 24 + import org.orekit.files.ccsds.ndm.tdm.TdmMetadata; 25 + import org.orekit.files.ccsds.ndm.tdm.TdmWriter; 26 + import org.orekit.files.ccsds.ndm.tdm.AngleType; 27 + import org.orekit.files.ccsds.ndm.tdm.IntegrationReference; 28 + import org.orekit.files.ccsds.ndm.tdm.TimetagReference; 29 + import org.orekit.files.ccsds.section.Segment; 30 + import org.orekit.files.ccsds.utils.generation.KvnGenerator; 31 + import org.orekit.time.AbsoluteDate; 32 + import org.orekit.time.TimeScale; 33 + import org.orekit.time.TimeScalesFactory; 34 + 35 + import java.io.*; 36 + import java.net.URI; 37 + import java.net.URL; 38 + import java.nio.channels.Channels; 39 + import java.nio.channels.ReadableByteChannel; 40 + import java.nio.file.*; 41 + import java.util.*; 42 + 43 + public class generate { 44 + 45 + static final String OREKIT_DATA_URL = 46 + "https://gitlab.orekit.org/orekit/orekit-data/-/archive/master/orekit-data-master.zip"; 47 + 48 + /** Bootstrap OREKIT data context. Downloads orekit-data if needed. */ 49 + static void bootstrap(Path scriptDir) throws Exception { 50 + Path dataZip = scriptDir.resolve("orekit-data-master.zip"); 51 + Path dataDir = scriptDir.resolve("orekit-data-master"); 52 + 53 + if (!Files.exists(dataDir)) { 54 + if (!Files.exists(dataZip)) { 55 + System.err.println("Downloading orekit-data ..."); 56 + try (InputStream in = URI.create(OREKIT_DATA_URL).toURL().openStream()) { 57 + Files.copy(in, dataZip, StandardCopyOption.REPLACE_EXISTING); 58 + } 59 + } 60 + // Unzip 61 + System.err.println("Extracting orekit-data ..."); 62 + ProcessBuilder pb = new ProcessBuilder("unzip", "-q", "-o", 63 + dataZip.toString(), "-d", scriptDir.toString()); 64 + pb.inheritIO(); 65 + int rc = pb.start().waitFor(); 66 + if (rc != 0) throw new RuntimeException("unzip failed with code " + rc); 67 + } 68 + 69 + DataProvidersManager manager = 70 + DataContext.getDefault().getDataProvidersManager(); 71 + manager.addProvider(new DirectoryCrawler(dataDir.toFile())); 72 + } 73 + 74 + // ------------------------------------------------------------------ 75 + // Scenario definitions 76 + // ------------------------------------------------------------------ 77 + 78 + /** A test scenario: one TDM file with known measurements. */ 79 + record Scenario( 80 + String name, 81 + String originator, 82 + String participant1, 83 + String participant2, 84 + String participant3, // null if absent 85 + String mode, // null if absent 86 + String path, // null if absent 87 + String transmitBand, // null if absent 88 + String receiveBand, // null if absent 89 + String timetagRef, // null if absent 90 + Double integrationInterval, // null if absent 91 + String integrationRef, // null if absent 92 + String rangeMode, // null if absent 93 + Double rangeModulus, // null if absent 94 + String rangeUnits, // null if absent 95 + String angleType, // null if absent 96 + String dataQuality, // null if absent 97 + AbsoluteDate startTime, 98 + AbsoluteDate stopTime, 99 + Observation[] observations 100 + ) {} 101 + 102 + record Observation(ObservationType type, AbsoluteDate epoch, double value) {} 103 + 104 + static Scenario[] scenarios(TimeScale utc) { 105 + 106 + // --- Scenario 1: Range & Doppler (DSN-style two-way) --- 107 + AbsoluteDate t0 = new AbsoluteDate(2024, 3, 15, 10, 0, 0.0, utc); 108 + Observation[] rangeDoppler = { 109 + new Observation(ObservationType.RANGE, 110 + t0, 285432167.890), 111 + new Observation(ObservationType.RANGE, 112 + t0.shiftedBy(600.0), 285432245.123), 113 + new Observation(ObservationType.RANGE, 114 + t0.shiftedBy(1200.0), 285432322.456), 115 + new Observation(ObservationType.DOPPLER_INSTANTANEOUS, 116 + t0, -4567.123456), 117 + new Observation(ObservationType.DOPPLER_INSTANTANEOUS, 118 + t0.shiftedBy(600.0), -4567.234567), 119 + new Observation(ObservationType.DOPPLER_INSTANTANEOUS, 120 + t0.shiftedBy(1200.0), -4567.345678), 121 + }; 122 + 123 + // --- Scenario 2: Angles (Az/El) --- 124 + AbsoluteDate t1 = new AbsoluteDate(2024, 6, 1, 0, 0, 0.0, utc); 125 + Observation[] angles = { 126 + new Observation(ObservationType.ANGLE_1, 127 + t1, 145.678901), 128 + new Observation(ObservationType.ANGLE_2, 129 + t1, 32.456789), 130 + new Observation(ObservationType.ANGLE_1, 131 + t1.shiftedBy(900.0), 156.789012), 132 + new Observation(ObservationType.ANGLE_2, 133 + t1.shiftedBy(900.0), 45.567890), 134 + new Observation(ObservationType.ANGLE_1, 135 + t1.shiftedBy(1800.0), 167.890123), 136 + new Observation(ObservationType.ANGLE_2, 137 + t1.shiftedBy(1800.0), 58.678901), 138 + }; 139 + 140 + // --- Scenario 3: Receive frequency --- 141 + AbsoluteDate t2 = new AbsoluteDate(2024, 9, 20, 8, 30, 0.0, utc); 142 + Observation[] freq = { 143 + new Observation(ObservationType.RECEIVE_FREQ, 144 + t2, 8420432156.789), 145 + new Observation(ObservationType.RECEIVE_FREQ, 146 + t2.shiftedBy(300.0), 8420432178.456), 147 + new Observation(ObservationType.RECEIVE_FREQ, 148 + t2.shiftedBy(600.0), 8420432200.123), 149 + }; 150 + 151 + // --- Scenario 4: Mixed (range + angles + Doppler) --- 152 + AbsoluteDate t3 = new AbsoluteDate(2025, 1, 1, 0, 0, 0.0, utc); 153 + Observation[] mixed = { 154 + new Observation(ObservationType.RANGE, 155 + t3, 50000.0), 156 + new Observation(ObservationType.ANGLE_1, 157 + t3, 90.0), 158 + new Observation(ObservationType.ANGLE_2, 159 + t3, 45.0), 160 + new Observation(ObservationType.DOPPLER_INSTANTANEOUS, 161 + t3, -100.5), 162 + new Observation(ObservationType.RANGE, 163 + t3.shiftedBy(60.0), 50001.234), 164 + new Observation(ObservationType.ANGLE_1, 165 + t3.shiftedBy(60.0), 90.123), 166 + new Observation(ObservationType.ANGLE_2, 167 + t3.shiftedBy(60.0), 45.456), 168 + new Observation(ObservationType.DOPPLER_INSTANTANEOUS, 169 + t3.shiftedBy(60.0), -100.678), 170 + }; 171 + 172 + // --- Scenario 5: Negative and zero values --- 173 + AbsoluteDate t4 = new AbsoluteDate(2024, 12, 31, 23, 59, 0.0, utc); 174 + Observation[] edgeValues = { 175 + new Observation(ObservationType.RANGE, 176 + t4, 0.0), 177 + new Observation(ObservationType.DOPPLER_INSTANTANEOUS, 178 + t4, -99999.999999), 179 + new Observation(ObservationType.DOPPLER_INSTANTANEOUS, 180 + t4.shiftedBy(1.0), 99999.999999), 181 + new Observation(ObservationType.RANGE, 182 + t4.shiftedBy(1.0), 1e12), 183 + }; 184 + 185 + // --- Scenario 6: Three participants (relay) --- 186 + AbsoluteDate t5 = new AbsoluteDate(2024, 7, 4, 12, 0, 0.0, utc); 187 + Observation[] relay = { 188 + new Observation(ObservationType.RANGE, 189 + t5, 384400.0), 190 + new Observation(ObservationType.RANGE, 191 + t5.shiftedBy(120.0), 384401.567), 192 + }; 193 + 194 + // --- Scenario 7: Small fractional seconds --- 195 + AbsoluteDate t6 = new AbsoluteDate(2024, 2, 29, 12, 0, 0.123, utc); 196 + Observation[] fractional = { 197 + new Observation(ObservationType.RANGE, 198 + t6, 12345.6789), 199 + new Observation(ObservationType.RANGE, 200 + t6.shiftedBy(0.5), 12345.789), 201 + new Observation(ObservationType.RANGE, 202 + t6.shiftedBy(1.0), 12345.8901), 203 + }; 204 + 205 + return new Scenario[] { 206 + new Scenario("range_doppler", "NASA/JPL", 207 + "DSS-25", "MARS RECONNAISSANCE ORBITER", 208 + null, "SEQUENTIAL", "1,2,1", 209 + "X", "X", "TRANSMIT", 210 + 1.0, "MIDDLE", 211 + "COHERENT", null, "km", 212 + null, "RAW", 213 + t0, t0.shiftedBy(1200.0), rangeDoppler), 214 + 215 + new Scenario("angles_azel", "ESOC", 216 + "MALINDI", "SENTINEL-1A", 217 + null, null, null, 218 + null, null, null, 219 + null, null, 220 + null, null, null, 221 + "AZEL", "VALIDATED", 222 + t1, t1.shiftedBy(1800.0), angles), 223 + 224 + new Scenario("receive_freq", "CNES", 225 + "TOULOUSE", "JASON-3", 226 + null, "SEQUENTIAL", "1,2,1", 227 + "X", "X", "RECEIVE", 228 + null, null, 229 + null, null, null, 230 + null, null, 231 + t2, t2.shiftedBy(600.0), freq), 232 + 233 + new Scenario("mixed", "DLR", 234 + "WEILHEIM", "TERRASAR-X", 235 + null, "SEQUENTIAL", "1,2,1", 236 + "X", "X", "TRANSMIT", 237 + 1.0, "START", 238 + "COHERENT", null, "km", 239 + "AZEL", "RAW", 240 + t3, t3.shiftedBy(60.0), mixed), 241 + 242 + new Scenario("edge_values", "NASA/GSFC", 243 + "WHITESANDS", "TDRS-M", 244 + null, "SEQUENTIAL", "1,2,1", 245 + "S", "S", "TRANSMIT", 246 + null, null, 247 + null, null, "km", 248 + null, null, 249 + t4, t4.shiftedBy(1.0), edgeValues), 250 + 251 + new Scenario("three_participants", "NASA/JPL", 252 + "DSS-14", "LUNAR-GATEWAY", "ORION", 253 + "SEQUENTIAL", "1,2,3", 254 + "Ka", "Ka", "TRANSMIT", 255 + null, null, 256 + "COHERENT", null, "km", 257 + null, null, 258 + t5, t5.shiftedBy(120.0), relay), 259 + 260 + new Scenario("fractional_seconds", "JAXA", 261 + "USUDA", "HAYABUSA2", 262 + null, null, "1,2,1", 263 + "X", "X", null, 264 + null, null, 265 + null, null, "km", 266 + null, null, 267 + t6, t6.shiftedBy(1.0), fractional), 268 + }; 269 + } 270 + 271 + // ------------------------------------------------------------------ 272 + // TDM file building via OREKIT public API 273 + // ------------------------------------------------------------------ 274 + 275 + static TdmFile buildTdm(Scenario sc, TimeScale utc) { 276 + TdmHeader header = new TdmHeader(); 277 + header.setFormatVersion(1.0); 278 + header.setCreationDate(new AbsoluteDate(2025, 1, 1, 0, 0, 0.0, utc)); 279 + header.setOriginator(sc.originator()); 280 + 281 + TdmMetadata meta = new TdmMetadata(); 282 + meta.setTimeSystem(TimeSystem.UTC); 283 + meta.setStartTime(sc.startTime()); 284 + meta.setStopTime(sc.stopTime()); 285 + meta.setParticipants(buildParticipants(sc)); 286 + 287 + if (sc.mode() != null) { 288 + meta.setMode(sc.mode()); 289 + } 290 + if (sc.path() != null) { 291 + // Parse "1,2,1" into int array 292 + String[] parts = sc.path().split(","); 293 + int[] pathArr = new int[parts.length]; 294 + for (int i = 0; i < parts.length; i++) { 295 + pathArr[i] = Integer.parseInt(parts[i].trim()); 296 + } 297 + meta.setPath(pathArr); 298 + } 299 + if (sc.transmitBand() != null) meta.setTransmitBand(sc.transmitBand()); 300 + if (sc.receiveBand() != null) meta.setReceiveBand(sc.receiveBand()); 301 + if (sc.timetagRef() != null) { 302 + meta.setTimetagRef(TimetagReference.valueOf(sc.timetagRef())); 303 + } 304 + if (sc.integrationInterval() != null) { 305 + meta.setIntegrationInterval(sc.integrationInterval()); 306 + } 307 + if (sc.integrationRef() != null) { 308 + meta.setIntegrationRef(IntegrationReference.valueOf(sc.integrationRef())); 309 + } 310 + if (sc.rangeMode() != null) { 311 + meta.setRangeMode(RangeMode.valueOf(sc.rangeMode())); 312 + } 313 + if (sc.rangeModulus() != null) { 314 + meta.setRangeModulus(sc.rangeModulus()); 315 + } 316 + if (sc.rangeUnits() != null) { 317 + meta.setRangeUnits(RangeUnits.valueOf(sc.rangeUnits().toLowerCase())); 318 + } 319 + if (sc.angleType() != null) { 320 + meta.setAngleType(AngleType.valueOf(sc.angleType())); 321 + } 322 + if (sc.dataQuality() != null) { 323 + meta.setDataQuality(sc.dataQuality()); 324 + } 325 + 326 + ObservationsBlock block = new ObservationsBlock(); 327 + block.setMetadata(meta); 328 + for (Observation obs : sc.observations()) { 329 + block.addObservation(obs.type(), obs.epoch(), obs.value()); 330 + } 331 + 332 + List<Segment<TdmMetadata, ObservationsBlock>> segments = new ArrayList<>(); 333 + segments.add(new Segment<>(meta, block)); 334 + 335 + return new TdmFile(header, segments, null, null); 336 + } 337 + 338 + static Map<Integer, String> buildParticipants(Scenario sc) { 339 + Map<Integer, String> p = new TreeMap<>(); 340 + p.put(1, sc.participant1()); 341 + p.put(2, sc.participant2()); 342 + if (sc.participant3() != null) { 343 + p.put(3, sc.participant3()); 344 + } 345 + return p; 346 + } 347 + 348 + /** Write a TdmFile to KVN format using TdmWriter + KvnGenerator. */ 349 + static String writeKvn(TdmFile tdm) throws Exception { 350 + StringWriter sw = new StringWriter(); 351 + // TdmWriter(writerId, conventions, dataContext) 352 + TdmWriter writer = new TdmWriter( 353 + null, null, DataContext.getDefault()); 354 + try (KvnGenerator gen = new KvnGenerator(sw, 0, "generate.java", 355 + Constants.MISSING, false)) { 356 + writer.writeMessage(gen, tdm); 357 + } 358 + return sw.toString(); 359 + } 360 + 361 + // OREKIT constants placeholder 362 + static final class Constants { 363 + static final double MISSING = Double.NaN; 364 + } 365 + 366 + // ------------------------------------------------------------------ 367 + // CSV index generation 368 + // ------------------------------------------------------------------ 369 + 370 + static void writeCsvIndex(Scenario[] scenarios, Path traceDir) throws Exception { 371 + try (PrintWriter pw = new PrintWriter(new FileWriter( 372 + traceDir.resolve("index.csv").toFile()))) { 373 + pw.println("name,file,participant_1,participant_2,participant_3," 374 + + "mode,path,transmit_band,receive_band," 375 + + "range_mode,range_units,angle_type,data_quality," 376 + + "num_observations,obs_keywords,obs_values"); 377 + 378 + for (Scenario sc : scenarios) { 379 + StringBuilder keywords = new StringBuilder(); 380 + StringBuilder values = new StringBuilder(); 381 + for (int i = 0; i < sc.observations().length; i++) { 382 + if (i > 0) { keywords.append(";"); values.append(";"); } 383 + keywords.append(sc.observations()[i].type().name()); 384 + values.append(String.format("%.14g", sc.observations()[i].value())); 385 + } 386 + 387 + pw.printf("%s,%s.kvn,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%d,\"%s\",\"%s\"%n", 388 + sc.name(), 389 + sc.name(), 390 + sc.participant1(), 391 + sc.participant2(), 392 + sc.participant3() != null ? sc.participant3() : "", 393 + sc.mode() != null ? sc.mode() : "", 394 + sc.path() != null ? sc.path() : "", 395 + sc.transmitBand() != null ? sc.transmitBand() : "", 396 + sc.receiveBand() != null ? sc.receiveBand() : "", 397 + sc.rangeMode() != null ? sc.rangeMode() : "", 398 + sc.rangeUnits() != null ? sc.rangeUnits() : "", 399 + sc.angleType() != null ? sc.angleType() : "", 400 + sc.dataQuality() != null ? sc.dataQuality() : "", 401 + sc.observations().length, 402 + keywords.toString(), 403 + values.toString()); 404 + } 405 + } 406 + } 407 + 408 + // ------------------------------------------------------------------ 409 + // Main 410 + // ------------------------------------------------------------------ 411 + 412 + public static void main(String[] args) throws Exception { 413 + if (args.length < 1) { 414 + System.err.println("Usage: jbang generate.java <TRACE_DIR>"); 415 + System.exit(1); 416 + } 417 + Path traceDir = Paths.get(args[0]); 418 + Files.createDirectories(traceDir); 419 + 420 + // Bootstrap OREKIT data 421 + Path scriptDir = Paths.get(System.getProperty("user.dir")); 422 + bootstrap(scriptDir); 423 + 424 + TimeScale utc = TimeScalesFactory.getUTC(); 425 + Scenario[] allScenarios = scenarios(utc); 426 + 427 + // Generate KVN files via OREKIT 428 + for (Scenario sc : allScenarios) { 429 + TdmFile tdm = buildTdm(sc, utc); 430 + String kvn = writeKvn(tdm); 431 + Path outPath = traceDir.resolve(sc.name() + ".kvn"); 432 + Files.writeString(outPath, kvn); 433 + System.out.println("Wrote " + outPath); 434 + } 435 + 436 + // Generate CSV index 437 + writeCsvIndex(allScenarios, traceDir); 438 + System.out.println("Wrote " + traceDir.resolve("index.csv")); 439 + } 440 + }