FERS 1.0.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
json_serializer.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2// Copyright (c) 2025-present FERS Contributors (see AUTHORS.md).
3
4/**
5 * @file json_serializer.cpp
6 * @brief Implements JSON serialization and deserialization for FERS objects.
7 *
8 * This file leverages the `nlohmann/json` library's support for automatic
9 * serialization via `to_json` and `from_json` free functions. By placing these
10 * functions within the namespaces of the objects they serialize, we enable
11 * Argument-Dependent Lookup (ADL). This design choice allows the library to
12 * automatically find the correct conversion functions, keeping the serialization
13 * logic decoupled from the core object definitions and improving modularity.
14 */
15
17
18#include <cmath>
19#include <format>
20#include <nlohmann/json.hpp>
21#include <random>
22
24#include "core/parameters.h"
25#include "core/sim_id.h"
26#include "core/world.h"
27#include "math/coord.h"
28#include "math/path.h"
29#include "math/rotation_path.h"
30#include "radar/platform.h"
31#include "radar/receiver.h"
32#include "radar/target.h"
33#include "radar/transmitter.h"
36#include "signal/radar_signal.h"
38#include "timing/timing.h"
39#include "waveform_factory.h"
40
41// TODO: Add file path validation and error handling as needed.
42
43namespace
44{
45 nlohmann::json sim_id_to_json(const SimId id) { return std::to_string(id); }
46
47 SimId parse_json_id(const nlohmann::json& j, const std::string& key, const std::string& owner)
48 {
49 if (!j.contains(key))
50 {
51 throw std::runtime_error("Missing required '" + key + "' for " + owner + ".");
52 }
53 try
54 {
55 if (j.at(key).is_number_unsigned())
56 {
57 return j.at(key).get<SimId>();
58 }
59 if (j.at(key).is_number_integer())
60 {
61 const auto value = j.at(key).get<long long>();
62 if (value < 0)
63 {
64 throw std::runtime_error("negative id");
65 }
66 return static_cast<SimId>(value);
67 }
68 if (j.at(key).is_string())
69 {
70 const auto str = j.at(key).get<std::string>();
71 size_t idx = 0;
72 const unsigned long long parsed = std::stoull(str, &idx, 10);
73 if (idx != str.size())
74 {
75 throw std::runtime_error("trailing characters");
76 }
77 return static_cast<SimId>(parsed);
78 }
79 }
80 catch (const std::exception& e)
81 {
82 throw std::runtime_error("Invalid '" + key + "' for " + owner + ": " + e.what());
83 }
84 throw std::runtime_error("Invalid '" + key + "' type for " + owner + ".");
85 }
86}
87
88namespace math
89{
90 void to_json(nlohmann::json& j, const Vec3& v)
91 {
92 j = {{"x", v.x}, {"y", v.y}, {"z", v.z}};
93 } // NOLINT(*-use-internal-linkage)
94
95 void from_json(const nlohmann::json& j, Vec3& v) // NOLINT(*-use-internal-linkage)
96 {
97 j.at("x").get_to(v.x);
98 j.at("y").get_to(v.y);
99 j.at("z").get_to(v.z);
100 }
101
102 void to_json(nlohmann::json& j, const Coord& c) // NOLINT(*-use-internal-linkage)
103 {
104 j = {{"time", c.t}, {"x", c.pos.x}, {"y", c.pos.y}, {"altitude", c.pos.z}};
105 }
106
107 void from_json(const nlohmann::json& j, Coord& c) // NOLINT(*-use-internal-linkage)
108 {
109 j.at("time").get_to(c.t);
110 j.at("x").get_to(c.pos.x);
111 j.at("y").get_to(c.pos.y);
112 j.at("altitude").get_to(c.pos.z);
113 }
114
115 void to_json(nlohmann::json& j, const RotationCoord& rc) // NOLINT(*-use-internal-linkage)
116 {
117 const auto unit = params::rotationAngleUnit();
118 j = {{"time", rc.t},
121 }
122
123 void from_json(const nlohmann::json& j, RotationCoord& rc) // NOLINT(*-use-internal-linkage)
124 {
125 j.at("time").get_to(rc.t);
127 j.at("azimuth").get<RealType>(), j.at("elevation").get<RealType>(), rc.t, params::rotationAngleUnit());
128 rc.azimuth = external.azimuth;
129 rc.elevation = external.elevation;
130 }
131
136
137 void to_json(nlohmann::json& j, const Path& p) // NOLINT(*-use-internal-linkage)
138 {
139 j = {{"interpolation", p.getType()}, {"positionwaypoints", p.getCoords()}};
140 }
141
142 void from_json(const nlohmann::json& j, Path& p) // NOLINT(*-use-internal-linkage)
143 {
144 p.setInterp(j.at("interpolation").get<Path::InterpType>());
145 for (const auto waypoints = j.at("positionwaypoints").get<std::vector<Coord>>(); const auto& wp : waypoints)
146 {
147 p.addCoord(wp);
148 }
149 p.finalize();
150 }
151
155 "constant"}, // Not used in xml_parser or UI yet, but for completeness
158
159 void to_json(nlohmann::json& j, const RotationPath& p) // NOLINT(*-use-internal-linkage)
160 {
161 j["interpolation"] = p.getType();
162 // This logic exists to map the two different rotation definitions from the
163 // XML schema (<fixedrotation> and <rotationpath>) into a unified JSON
164 // structure that the frontend can more easily handle.
166 {
167 // A constant-rate rotation path corresponds to the <fixedrotation> XML element.
168 // The start and rate values are converted to compass degrees per second.
169 // No normalization is applied to preserve negative start angles.
170 const auto unit = params::rotationAngleUnit();
171 j["startazimuth"] = serial::rotation_angle_utils::internal_azimuth_to_external(p.getStart().azimuth, unit);
172 j["startelevation"] =
174 j["azimuthrate"] =
176 j["elevationrate"] =
178 }
179 else
180 {
181 j["rotationwaypoints"] = p.getCoords();
182 }
183 }
184
185 void from_json(const nlohmann::json& j, RotationPath& p) // NOLINT(*-use-internal-linkage)
186 {
187 p.setInterp(j.at("interpolation").get<RotationPath::InterpType>());
188 for (const auto waypoints = j.at("rotationwaypoints").get<std::vector<RotationCoord>>();
189 const auto& wp : waypoints)
190 {
191 p.addCoord(wp);
192 }
193 p.finalize();
194 }
195
196}
197
198namespace timing
199{
200 void to_json(nlohmann::json& j, const PrototypeTiming& pt) // NOLINT(*-use-internal-linkage)
201 {
202 j = nlohmann::json{{"id", sim_id_to_json(pt.getId())},
203 {"name", pt.getName()},
204 {"frequency", pt.getFrequency()},
205 {"synconpulse", pt.getSyncOnPulse()}};
206
207 if (pt.getFreqOffset().has_value())
208 {
209 j["freq_offset"] = pt.getFreqOffset().value();
210 }
211 if (pt.getRandomFreqOffsetStdev().has_value())
212 {
213 j["random_freq_offset_stdev"] = pt.getRandomFreqOffsetStdev().value();
214 }
215 if (pt.getPhaseOffset().has_value())
216 {
217 j["phase_offset"] = pt.getPhaseOffset().value();
218 }
219 if (pt.getRandomPhaseOffsetStdev().has_value())
220 {
221 j["random_phase_offset_stdev"] = pt.getRandomPhaseOffsetStdev().value();
222 }
223
224 std::vector<RealType> alphas;
225 std::vector<RealType> weights;
226 pt.copyAlphas(alphas, weights);
227 if (!alphas.empty())
228 {
229 nlohmann::json noise_entries = nlohmann::json::array();
230 for (size_t i = 0; i < alphas.size(); ++i)
231 {
232 noise_entries.push_back({{"alpha", alphas[i]}, {"weight", weights[i]}});
233 }
234 j["noise_entries"] = noise_entries;
235 }
236 }
237
238 void from_json(const nlohmann::json& j, PrototypeTiming& pt) // NOLINT(*-use-internal-linkage)
239 {
240 pt.setFrequency(j.at("frequency").get<RealType>());
241 if (j.value("synconpulse", false))
242 {
243 pt.setSyncOnPulse();
244 }
245 else
246 {
247 pt.clearSyncOnPulse();
248 }
249
250 if (j.contains("freq_offset"))
251 {
252 pt.setFreqOffset(j.at("freq_offset").get<RealType>());
253 }
254 else
255 pt.clearFreqOffset();
256 if (j.contains("random_freq_offset_stdev"))
257 {
258 pt.setRandomFreqOffsetStdev(j.at("random_freq_offset_stdev").get<RealType>());
259 }
260 else
262 if (j.contains("phase_offset"))
263 {
264 pt.setPhaseOffset(j.at("phase_offset").get<RealType>());
265 }
266 else
267 pt.clearPhaseOffset();
268 if (j.contains("random_phase_offset_stdev"))
269 {
270 pt.setRandomPhaseOffsetStdev(j.at("random_phase_offset_stdev").get<RealType>());
271 }
272 else
274
276 if (j.contains("noise_entries"))
277 {
278 for (const auto& entry : j.at("noise_entries"))
279 {
280 pt.setAlpha(entry.at("alpha").get<RealType>(), entry.at("weight").get<RealType>());
281 }
282 }
283 }
284}
285
286namespace fers_signal
287{
288 void to_json(nlohmann::json& j, const RadarSignal& rs) // NOLINT(*-use-internal-linkage)
289 {
290 j = nlohmann::json{{"id", sim_id_to_json(rs.getId())},
291 {"name", rs.getName()},
292 {"power", rs.getPower()},
293 {"carrier_frequency", rs.getCarrier()}};
294 if (dynamic_cast<const CwSignal*>(rs.getSignal()) != nullptr)
295 {
296 j["cw"] = nlohmann::json::object();
297 }
298 else
299 {
300 if (const auto& filename = rs.getFilename(); filename.has_value())
301 {
302 j["pulsed_from_file"] = {{"filename", *filename}};
303 }
304 else
305 {
306 throw std::logic_error("Attempted to serialize a file-based waveform named '" + rs.getName() +
307 "' without a source filename.");
308 }
309 }
310 }
311
312 void from_json(const nlohmann::json& j, std::unique_ptr<RadarSignal>& rs) // NOLINT(*-use-internal-linkage)
313 {
314 const auto name = j.at("name").get<std::string>();
315 const auto id = parse_json_id(j, "id", "waveform");
316 const auto power = j.at("power").get<RealType>();
317 const auto carrier = j.at("carrier_frequency").get<RealType>();
318
319 if (j.contains("cw"))
320 {
321 auto cw_signal = std::make_unique<CwSignal>();
322 rs = std::make_unique<RadarSignal>(name, power, carrier, params::endTime() - params::startTime(),
323 std::move(cw_signal), id);
324 }
325 else if (j.contains("pulsed_from_file"))
326 {
327 const auto& pulsed_file = j.at("pulsed_from_file");
328 const auto filename = pulsed_file.value("filename", "");
329 if (filename.empty())
330 {
331 LOG(logging::Level::WARNING, "Skipping load of file-based waveform '{}': filename is empty.", name);
332 return; // rs remains nullptr
333 }
334 rs = serial::loadWaveformFromFile(name, filename, power, carrier, id);
335 }
336 else
337 {
338 throw std::runtime_error("Unsupported waveform type in from_json for '" + name + "'");
339 }
340 }
341}
342
343namespace antenna
344{
345 void to_json(nlohmann::json& j, const Antenna& a) // NOLINT(*-use-internal-linkage)
346 {
347 j = {{"id", sim_id_to_json(a.getId())}, {"name", a.getName()}, {"efficiency", a.getEfficiencyFactor()}};
348
349 if (const auto* sinc = dynamic_cast<const Sinc*>(&a))
350 {
351 j["pattern"] = "sinc";
352 j["alpha"] = sinc->getAlpha();
353 j["beta"] = sinc->getBeta();
354 j["gamma"] = sinc->getGamma();
355 }
356 else if (const auto* gaussian = dynamic_cast<const Gaussian*>(&a))
357 {
358 j["pattern"] = "gaussian";
359 j["azscale"] = gaussian->getAzimuthScale();
360 j["elscale"] = gaussian->getElevationScale();
361 }
362 else if (const auto* sh = dynamic_cast<const SquareHorn*>(&a))
363 {
364 j["pattern"] = "squarehorn";
365 j["diameter"] = sh->getDimension();
366 }
367 else if (const auto* parabolic = dynamic_cast<const Parabolic*>(&a))
368 {
369 j["pattern"] = "parabolic";
370 j["diameter"] = parabolic->getDiameter();
371 }
372 else if (const auto* xml = dynamic_cast<const XmlAntenna*>(&a))
373 {
374 j["pattern"] = "xml";
375 j["filename"] = xml->getFilename();
376 }
377 else if (const auto* h5 = dynamic_cast<const H5Antenna*>(&a))
378 {
379 j["pattern"] = "file";
380 j["filename"] = h5->getFilename();
381 }
382 else
383 {
384 j["pattern"] = "isotropic";
385 }
386 }
387
388 void from_json(const nlohmann::json& j, std::unique_ptr<Antenna>& ant) // NOLINT(*-use-internal-linkage)
389 {
390 const auto name = j.at("name").get<std::string>();
391 const auto id = parse_json_id(j, "id", "Antenna");
392 const auto pattern = j.value("pattern", "isotropic");
393
394 if (pattern == "isotropic")
395 {
396 ant = std::make_unique<Isotropic>(name, id);
397 }
398 else if (pattern == "sinc")
399 {
400 ant = std::make_unique<Sinc>(name, j.at("alpha").get<RealType>(), j.at("beta").get<RealType>(),
401 j.at("gamma").get<RealType>(), id);
402 }
403 else if (pattern == "gaussian")
404 {
405 ant =
406 std::make_unique<Gaussian>(name, j.at("azscale").get<RealType>(), j.at("elscale").get<RealType>(), id);
407 }
408 else if (pattern == "squarehorn")
409 {
410 ant = std::make_unique<SquareHorn>(name, j.at("diameter").get<RealType>(), id);
411 }
412 else if (pattern == "parabolic")
413 {
414 ant = std::make_unique<Parabolic>(name, j.at("diameter").get<RealType>(), id);
415 }
416 else if (pattern == "xml")
417 {
418 const auto filename = j.value("filename", "");
419 if (filename.empty())
420 {
421 LOG(logging::Level::WARNING, "Skipping load of XML antenna '{}': filename is empty.", name);
422 return; // ant remains nullptr
423 }
424 ant = std::make_unique<XmlAntenna>(name, filename, id);
425 }
426 else if (pattern == "file")
427 {
428 const auto filename = j.value("filename", "");
429 if (filename.empty())
430 {
431 LOG(logging::Level::WARNING, "Skipping load of H5 antenna '{}': filename is empty.", name);
432 return; // ant remains nullptr
433 }
434 ant = std::make_unique<H5Antenna>(name, filename, id);
435 }
436 else
437 {
438 throw std::runtime_error("Unsupported antenna pattern in from_json: " + pattern);
439 }
440
441 ant->setEfficiencyFactor(j.value("efficiency", 1.0));
442 }
443}
444
445namespace radar
446{
447 void to_json(nlohmann::json& j, const SchedulePeriod& p)
448 {
449 j = {{"start", p.start}, {"end", p.end}};
450 } // NOLINT(*-use-internal-linkage)
451
452 void from_json(const nlohmann::json& j, SchedulePeriod& p) // NOLINT(*-use-internal-linkage)
453 {
454 j.at("start").get_to(p.start);
455 j.at("end").get_to(p.end);
456 }
457
458 void to_json(nlohmann::json& j, const Transmitter& t) // NOLINT(*-use-internal-linkage)
459 {
460 j = nlohmann::json{{"id", sim_id_to_json(t.getId())},
461 {"name", t.getName()},
462 {"waveform", sim_id_to_json((t.getSignal() != nullptr) ? t.getSignal()->getId() : 0)},
463 {"antenna", sim_id_to_json((t.getAntenna() != nullptr) ? t.getAntenna()->getId() : 0)},
464 {"timing", sim_id_to_json(t.getTiming() ? t.getTiming()->getId() : 0)}};
465
467 {
468 j["pulsed_mode"] = {{"prf", t.getPrf()}};
469 }
470 else
471 {
472 j["cw_mode"] = nlohmann::json::object();
473 }
474 if (!t.getSchedule().empty())
475 {
476 j["schedule"] = t.getSchedule();
477 }
478 }
479
480 void to_json(nlohmann::json& j, const Receiver& r) // NOLINT(*-use-internal-linkage)
481 {
482 j = nlohmann::json{{"id", sim_id_to_json(r.getId())},
483 {"name", r.getName()},
484 {"noise_temp", r.getNoiseTemperature()},
485 {"antenna", sim_id_to_json((r.getAntenna() != nullptr) ? r.getAntenna()->getId() : 0)},
486 {"timing", sim_id_to_json(r.getTiming() ? r.getTiming()->getId() : 0)},
488 {"nopropagationloss", r.checkFlag(Receiver::RecvFlag::FLAG_NOPROPLOSS)}};
489
491 {
492 j["pulsed_mode"] = {
493 {"prf", r.getWindowPrf()}, {"window_skip", r.getWindowSkip()}, {"window_length", r.getWindowLength()}};
494 }
495 else
496 {
497 j["cw_mode"] = nlohmann::json::object();
498 }
499 if (!r.getSchedule().empty())
500 {
501 j["schedule"] = r.getSchedule();
502 }
503 }
504
505 void to_json(nlohmann::json& j, const Target& t) // NOLINT(*-use-internal-linkage)
506 {
507 j["id"] = sim_id_to_json(t.getId());
508 j["name"] = t.getName();
509 nlohmann::json rcs_json;
510 if (const auto* iso = dynamic_cast<const IsoTarget*>(&t))
511 {
512 rcs_json["type"] = "isotropic";
513 rcs_json["value"] = iso->getConstRcs();
514 }
515 else if (const auto* file = dynamic_cast<const FileTarget*>(&t))
516 {
517 rcs_json["type"] = "file";
518 rcs_json["filename"] = file->getFilename();
519 }
520 j["rcs"] = rcs_json;
521
522 // Serialize the fluctuation model if it exists.
523 if (const auto* model_base = t.getFluctuationModel())
524 {
525 nlohmann::json model_json;
526 if (const auto* chi_model = dynamic_cast<const RcsChiSquare*>(model_base))
527 {
528 model_json["type"] = "chisquare";
529 model_json["k"] = chi_model->getK();
530 }
531 else // Default to constant if it's not a recognized type (e.g., RcsConst)
532 {
533 model_json["type"] = "constant";
534 }
535 j["model"] = model_json;
536 }
537 }
538
539 void to_json(nlohmann::json& j, const Platform& p) // NOLINT(*-use-internal-linkage)
540 {
541 j = {{"id", sim_id_to_json(p.getId())}, {"name", p.getName()}, {"motionpath", *p.getMotionPath()}};
542
544 {
545 j["fixedrotation"] = *p.getRotationPath();
546 }
547 else
548 {
549 j["rotationpath"] = *p.getRotationPath();
550 }
551 }
552
553}
554
555namespace params
556{
558 {{CoordinateFrame::ENU, "ENU"},
559 {CoordinateFrame::UTM, "UTM"},
560 {CoordinateFrame::ECEF, "ECEF"}})
563
564 void to_json(nlohmann::json& j, const Parameters& p) // NOLINT(*-use-internal-linkage)
565 {
566 j = nlohmann::json{{"starttime", p.start},
567 {"endtime", p.end},
568 {"rate", p.rate},
569 {"c", p.c},
570 {"simSamplingRate", p.sim_sampling_rate},
571 {"adc_bits", p.adc_bits},
572 {"oversample", p.oversample_ratio},
573 {"rotationangleunit", p.rotation_angle_unit}};
574
575 if (p.random_seed.has_value())
576 {
577 j["randomseed"] = p.random_seed.value();
578 }
579
580 j["origin"] = {
581 {"latitude", p.origin_latitude}, {"longitude", p.origin_longitude}, {"altitude", p.origin_altitude}};
582
583 j["coordinatesystem"] = {{"frame", p.coordinate_frame}};
584 if (p.coordinate_frame == CoordinateFrame::UTM)
585 {
586 j["coordinatesystem"]["zone"] = p.utm_zone;
587 j["coordinatesystem"]["hemisphere"] = p.utm_north_hemisphere ? "N" : "S";
588 }
589 }
590
591 void from_json(const nlohmann::json& j, Parameters& p) // NOLINT(*-use-internal-linkage)
592 {
593 p.start = j.at("starttime").get<RealType>();
594 p.end = j.at("endtime").get<RealType>();
595 p.rate = j.at("rate").get<RealType>();
596 p.c = j.value("c", Parameters::DEFAULT_C);
597 p.sim_sampling_rate = j.value("simSamplingRate", 1000.0);
598 p.adc_bits = j.value("adc_bits", 0u);
599 p.oversample_ratio = j.value("oversample", 1u);
600 p.rotation_angle_unit = j.value("rotationangleunit", RotationAngleUnit::Degrees);
601 p.random_seed = j.value<std::optional<unsigned>>("randomseed", std::nullopt);
602
603 const auto& origin = j.at("origin");
604 p.origin_latitude = origin.at("latitude").get<double>();
605 p.origin_longitude = origin.at("longitude").get<double>();
606 p.origin_altitude = origin.at("altitude").get<double>();
607
608 const auto& cs = j.at("coordinatesystem");
609 p.coordinate_frame = cs.at("frame").get<CoordinateFrame>();
610 if (p.coordinate_frame == CoordinateFrame::UTM)
611 {
612 p.utm_zone = cs.at("zone").get<int>();
613 p.utm_north_hemisphere = cs.at("hemisphere").get<std::string>() == "N";
614 }
615 }
616}
617
618namespace
619{
620 nlohmann::json serialize_platform(const radar::Platform* p, const core::World& world)
621 {
622 nlohmann::json plat_json = *p;
623
624 // Initialize components array to ensure it exists even if empty
625 plat_json["components"] = nlohmann::json::array();
626
627 // Add Transmitters and Monostatic Radars
628 for (const auto& t : world.getTransmitters())
629 {
630 if (t->getPlatform() == p)
631 {
632 if (t->getAttached() != nullptr)
633 {
634 nlohmann::json monostatic_comp;
635 monostatic_comp["name"] = t->getName();
636 monostatic_comp["tx_id"] = sim_id_to_json(t->getId());
637 monostatic_comp["rx_id"] = sim_id_to_json(t->getAttached()->getId());
638 monostatic_comp["waveform"] =
639 sim_id_to_json((t->getSignal() != nullptr) ? t->getSignal()->getId() : 0);
640 monostatic_comp["antenna"] =
641 sim_id_to_json((t->getAntenna() != nullptr) ? t->getAntenna()->getId() : 0);
642 monostatic_comp["timing"] = sim_id_to_json(t->getTiming() ? t->getTiming()->getId() : 0);
643
644 if (const auto* recv = dynamic_cast<const radar::Receiver*>(t->getAttached()))
645 {
646 monostatic_comp["noise_temp"] = recv->getNoiseTemperature();
647 monostatic_comp["nodirect"] = recv->checkFlag(radar::Receiver::RecvFlag::FLAG_NODIRECT);
648 monostatic_comp["nopropagationloss"] =
650
651 if (!t->getSchedule().empty())
652 {
653 monostatic_comp["schedule"] = t->getSchedule();
654 }
655
656 if (t->getMode() == radar::OperationMode::PULSED_MODE)
657 {
658 monostatic_comp["pulsed_mode"] = {{"prf", t->getPrf()},
659 {"window_skip", recv->getWindowSkip()},
660 {"window_length", recv->getWindowLength()}};
661 }
662 else
663 {
664 monostatic_comp["cw_mode"] = nlohmann::json::object();
665 }
666 }
667 plat_json["components"].push_back(nlohmann::json{{"monostatic", monostatic_comp}});
668 }
669 else
670 {
671 plat_json["components"].push_back(nlohmann::json{{"transmitter", *t}});
672 }
673 }
674 }
675
676 // Add Standalone Receivers
677 for (const auto& r : world.getReceivers())
678 {
679 if (r->getPlatform() == p)
680 {
681 // This must be a standalone receiver, as monostatic cases were handled above.
682 if (r->getAttached() == nullptr)
683 {
684 plat_json["components"].push_back(nlohmann::json{{"receiver", *r}});
685 }
686 }
687 }
688
689 // Add Targets
690 for (const auto& target : world.getTargets())
691 {
692 if (target->getPlatform() == p)
693 {
694 plat_json["components"].push_back(nlohmann::json{{"target", *target}});
695 }
696 }
697
698 return plat_json;
699 }
700
701 void parse_parameters(const nlohmann::json& sim, std::mt19937& masterSeeder)
702 {
703 auto new_params = sim.at("parameters").get<params::Parameters>();
704
705 // If a random seed is present in the incoming JSON, it is used to re-seed
706 // the master generator. This is crucial for allowing the UI to control
707 // simulation reproducibility.
708 if (sim.at("parameters").contains("randomseed"))
709 {
710 params::params.random_seed = new_params.random_seed;
711 if (params::params.random_seed)
712 {
713 LOG(logging::Level::INFO, "Master seed updated from JSON to: {}", *params::params.random_seed);
714 masterSeeder.seed(*params::params.random_seed);
715 }
716 }
717
718 new_params.random_seed = params::params.random_seed;
719 params::params = new_params;
720 params::params.simulation_name = sim.value("name", "");
721 }
722
723 void parse_assets(const nlohmann::json& sim, core::World& world)
724 {
725 if (sim.contains("waveforms"))
726 {
727 for (auto waveforms = sim.at("waveforms").get<std::vector<std::unique_ptr<fers_signal::RadarSignal>>>();
728 auto& waveform : waveforms)
729 {
730 // Only add valid waveforms. If filename was empty, waveform is nullptr.
731 if (waveform)
732 {
733 world.add(std::move(waveform));
734 }
735 }
736 }
737
738 if (sim.contains("antennas"))
739 {
740 for (auto antennas = sim.at("antennas").get<std::vector<std::unique_ptr<antenna::Antenna>>>();
741 auto& antenna : antennas)
742 {
743 // Only add valid antennas.
744 if (antenna)
745 {
746 world.add(std::move(antenna));
747 }
748 }
749 }
750
751 if (sim.contains("timings"))
752 {
753 for (const auto& timing_json : sim.at("timings"))
754 {
755 auto name = timing_json.at("name").get<std::string>();
756 const auto timing_id = parse_json_id(timing_json, "id", "Timing");
757 auto timing_obj = std::make_unique<timing::PrototypeTiming>(name, timing_id);
758 timing_json.get_to(*timing_obj);
759 world.add(std::move(timing_obj));
760 }
761 }
762 }
763
764 radar::OperationMode parse_mode(const nlohmann::json& comp_json, const std::string& error_context)
765 {
766 if (comp_json.contains("pulsed_mode"))
767 {
769 }
770 if (comp_json.contains("cw_mode"))
771 {
773 }
774 throw std::runtime_error(error_context + " must have a 'pulsed_mode' or 'cw_mode' block.");
775 }
776
777 void parse_transmitter(const nlohmann::json& comp_json, radar::Platform* plat, core::World& world,
778 std::mt19937& masterSeeder)
779 {
780 // --- Dependency Check ---
781 // Validate Waveform and Timing existence before creation to prevent core crashes.
782 const auto wave_id = parse_json_id(comp_json, "waveform", "Transmitter");
783 const auto timing_id = parse_json_id(comp_json, "timing", "Transmitter");
784 const auto antenna_id = parse_json_id(comp_json, "antenna", "Transmitter");
785
786 if (world.findWaveform(wave_id) == nullptr)
787 {
788 LOG(logging::Level::WARNING, "Skipping Transmitter '{}': Missing or invalid waveform '{}'.",
789 comp_json.value("name", "Unnamed"), comp_json.value("waveform", ""));
790 return;
791 }
792 if (world.findTiming(timing_id) == nullptr)
793 {
794 LOG(logging::Level::WARNING, "Skipping Transmitter '{}': Missing or invalid timing source '{}'.",
795 comp_json.value("name", "Unnamed"), comp_json.value("timing", ""));
796 return;
797 }
798 if (world.findAntenna(antenna_id) == nullptr)
799 {
800 LOG(logging::Level::WARNING, "Skipping Transmitter '{}': Missing or invalid antenna '{}'.",
801 comp_json.value("name", "Unnamed"), comp_json.value("antenna", ""));
802 return;
803 }
804
806 parse_mode(comp_json, "Transmitter component '" + comp_json.value("name", "Unnamed") + "'");
807
808 const auto trans_id = parse_json_id(comp_json, "id", "Transmitter");
809 auto trans = std::make_unique<radar::Transmitter>(plat, comp_json.value("name", "Unnamed"), mode, trans_id);
810 if (mode == radar::OperationMode::PULSED_MODE && comp_json.contains("pulsed_mode"))
811 {
812 trans->setPrf(comp_json.at("pulsed_mode").value("prf", 0.0));
813 }
814
815 trans->setWave(world.findWaveform(wave_id));
816 trans->setAntenna(world.findAntenna(antenna_id));
817
818 if (auto* const timing_proto = world.findTiming(timing_id))
819 {
820 const auto timing =
821 std::make_shared<timing::Timing>(timing_proto->getName(), masterSeeder(), timing_proto->getId());
822 timing->initializeModel(timing_proto);
823 trans->setTiming(timing);
824 }
825
826 if (comp_json.contains("schedule"))
827 {
828 auto raw = comp_json.at("schedule").get<std::vector<radar::SchedulePeriod>>();
829 RealType pri = 0.0;
831 {
832 pri = 1.0 / trans->getPrf();
833 }
834 trans->setSchedule(radar::processRawSchedule(std::move(raw), trans->getName(),
836 }
837
838 world.add(std::move(trans));
839 }
840
841 void parse_receiver(const nlohmann::json& comp_json, radar::Platform* plat, core::World& world,
842 std::mt19937& masterSeeder)
843 {
844 // --- Dependency Check ---
845 // Receiver strictly requires a Timing source.
846 const auto timing_id = parse_json_id(comp_json, "timing", "Receiver");
847 const auto antenna_id = parse_json_id(comp_json, "antenna", "Receiver");
848
849 if (world.findTiming(timing_id) == nullptr)
850 {
851 LOG(logging::Level::WARNING, "Skipping Receiver '{}': Missing or invalid timing source '{}'.",
852 comp_json.value("name", "Unnamed"), comp_json.value("timing", ""));
853 return;
854 }
855
856 if (world.findAntenna(antenna_id) == nullptr)
857 {
858 LOG(logging::Level::WARNING, "Skipping Receiver '{}': Missing or invalid antenna '{}'.",
859 comp_json.value("name", "Unnamed"), comp_json.value("antenna", ""));
860 return;
861 }
862
864 parse_mode(comp_json, "Receiver component '" + comp_json.value("name", "Unnamed") + "'");
865
866 const auto recv_id = parse_json_id(comp_json, "id", "Receiver");
867 auto recv =
868 std::make_unique<radar::Receiver>(plat, comp_json.value("name", "Unnamed"), masterSeeder(), mode, recv_id);
869 if (mode == radar::OperationMode::PULSED_MODE && comp_json.contains("pulsed_mode"))
870 {
871 const auto& mode_json = comp_json.at("pulsed_mode");
872 recv->setWindowProperties(mode_json.value("window_length", 0.0), mode_json.value("prf", 0.0),
873 mode_json.value("window_skip", 0.0));
874 }
875
876 recv->setNoiseTemperature(comp_json.value("noise_temp", 0.0));
877
878 recv->setAntenna(world.findAntenna(antenna_id));
879
880 if (auto* const timing_proto = world.findTiming(timing_id))
881 {
882 const auto timing =
883 std::make_shared<timing::Timing>(timing_proto->getName(), masterSeeder(), timing_proto->getId());
884 timing->initializeModel(timing_proto);
885 recv->setTiming(timing);
886 }
887
888 if (comp_json.value("nodirect", false))
889 {
891 }
892 if (comp_json.value("nopropagationloss", false))
893 {
895 }
896
897 if (comp_json.contains("schedule"))
898 {
899 auto raw = comp_json.at("schedule").get<std::vector<radar::SchedulePeriod>>();
900 RealType pri = 0.0;
902 {
903 pri = 1.0 / recv->getWindowPrf();
904 }
905 recv->setSchedule(radar::processRawSchedule(std::move(raw), recv->getName(),
907 }
908
909 world.add(std::move(recv));
910 }
911
912 void parse_target(const nlohmann::json& comp_json, radar::Platform* plat, core::World& world,
913 std::mt19937& masterSeeder)
914 {
915 const auto& rcs_json = comp_json.at("rcs");
916 const auto rcs_type = rcs_json.at("type").get<std::string>();
917 std::unique_ptr<radar::Target> target_obj;
918
919 if (rcs_type == "isotropic")
920 {
921 const auto target_id = parse_json_id(comp_json, "id", "Target");
922 target_obj = radar::createIsoTarget(plat, comp_json.at("name").get<std::string>(),
923 rcs_json.at("value").get<RealType>(),
924 static_cast<unsigned>(masterSeeder()), target_id);
925 }
926 else if (rcs_type == "file")
927 {
928 const auto filename = rcs_json.value("filename", "");
929 if (filename.empty())
930 {
931 LOG(logging::Level::WARNING, "Skipping load of file target '{}': RCS filename is empty.",
932 comp_json.value("name", "Unknown"));
933 return;
934 }
935 const auto target_id = parse_json_id(comp_json, "id", "Target");
936 target_obj = radar::createFileTarget(plat, comp_json.at("name").get<std::string>(), filename,
937 static_cast<unsigned>(masterSeeder()), target_id);
938 }
939 else
940 {
941 throw std::runtime_error("Unsupported target RCS type: " + rcs_type);
942 }
943 world.add(std::move(target_obj));
944
945 // After creating the target, check for and apply the fluctuation model.
946 if (comp_json.contains("model"))
947 {
948 const auto& model_json = comp_json.at("model");
949 const auto model_type = model_json.at("type").get<std::string>();
950
951 if (model_type == "chisquare" || model_type == "gamma")
952 {
953 auto model = std::make_unique<radar::RcsChiSquare>(world.getTargets().back()->getRngEngine(),
954 model_json.at("k").get<RealType>());
955 world.getTargets().back()->setFluctuationModel(std::move(model));
956 }
957 else if (model_type == "constant")
958 {
959 world.getTargets().back()->setFluctuationModel(std::make_unique<radar::RcsConst>());
960 }
961 else
962 {
963 throw std::runtime_error("Unsupported fluctuation model type: " + model_type);
964 }
965 }
966 }
967
968 void parse_monostatic(const nlohmann::json& comp_json, radar::Platform* plat, core::World& world,
969 std::mt19937& masterSeeder)
970 {
971 // This block reconstructs the internal C++ representation of a
972 // monostatic radar (a linked Transmitter and Receiver) from the
973 // single 'monostatic' component in the JSON.
974 // --- Dependency Check ---
975 const auto wave_id = parse_json_id(comp_json, "waveform", "Monostatic");
976 const auto timing_id = parse_json_id(comp_json, "timing", "Monostatic");
977 const auto antenna_id = parse_json_id(comp_json, "antenna", "Monostatic");
978
979 if (world.findWaveform(wave_id) == nullptr)
980 {
981 LOG(logging::Level::WARNING, "Skipping Monostatic '{}': Missing or invalid waveform '{}'.",
982 comp_json.value("name", "Unnamed"), comp_json.value("waveform", ""));
983 return;
984 }
985 if (world.findTiming(timing_id) == nullptr)
986 {
987 LOG(logging::Level::WARNING, "Skipping Monostatic '{}': Missing or invalid timing source '{}'.",
988 comp_json.value("name", "Unnamed"), comp_json.value("timing", ""));
989 return;
990 }
991 if (world.findAntenna(antenna_id) == nullptr)
992 {
993 LOG(logging::Level::WARNING, "Skipping Monostatic '{}': Missing or invalid antenna '{}'.",
994 comp_json.value("name", "Unnamed"), comp_json.value("antenna", ""));
995 return;
996 }
997
999 parse_mode(comp_json, "Monostatic component '" + comp_json.value("name", "Unnamed") + "'");
1000
1001 // Transmitter part
1002 const auto tx_id = parse_json_id(comp_json, "tx_id", "Monostatic");
1003 auto trans = std::make_unique<radar::Transmitter>(plat, comp_json.value("name", "Unnamed"), mode, tx_id);
1004 if (mode == radar::OperationMode::PULSED_MODE && comp_json.contains("pulsed_mode"))
1005 {
1006 trans->setPrf(comp_json.at("pulsed_mode").value("prf", 0.0));
1007 }
1008
1009 trans->setWave(world.findWaveform(wave_id));
1010 trans->setAntenna(world.findAntenna(antenna_id));
1011 auto* const tx_timing_proto = world.findTiming(timing_id);
1012 if (tx_timing_proto != nullptr)
1013 {
1014 const auto tx_timing =
1015 std::make_shared<timing::Timing>(tx_timing_proto->getName(), masterSeeder(), tx_timing_proto->getId());
1016 tx_timing->initializeModel(tx_timing_proto);
1017 trans->setTiming(tx_timing);
1018 }
1019
1020 // Receiver part
1021 const auto rx_id = parse_json_id(comp_json, "rx_id", "Monostatic");
1022 auto recv =
1023 std::make_unique<radar::Receiver>(plat, comp_json.value("name", "Unnamed"), masterSeeder(), mode, rx_id);
1024 if (mode == radar::OperationMode::PULSED_MODE && comp_json.contains("pulsed_mode"))
1025 {
1026 const auto& mode_json = comp_json.at("pulsed_mode");
1027 recv->setWindowProperties(mode_json.value("window_length", 0.0),
1028 trans->getPrf(), // Use transmitter's PRF
1029 mode_json.value("window_skip", 0.0));
1030 }
1031 recv->setNoiseTemperature(comp_json.value("noise_temp", 0.0));
1032
1033 recv->setAntenna(world.findAntenna(antenna_id));
1034 auto* const rx_timing_proto = world.findTiming(timing_id);
1035 if (rx_timing_proto != nullptr)
1036 {
1037 const auto rx_timing =
1038 std::make_shared<timing::Timing>(rx_timing_proto->getName(), masterSeeder(), rx_timing_proto->getId());
1039 rx_timing->initializeModel(rx_timing_proto);
1040 recv->setTiming(rx_timing);
1041 }
1042
1043 if (comp_json.value("nodirect", false))
1044 {
1046 }
1047 if (comp_json.value("nopropagationloss", false))
1048 {
1050 }
1051 if (comp_json.contains("schedule"))
1052 {
1053 auto raw = comp_json.at("schedule").get<std::vector<radar::SchedulePeriod>>();
1054 RealType pri = 0.0;
1056 {
1057 pri = 1.0 / trans->getPrf();
1058 }
1059
1060 // Process once, apply to both
1061 auto processed_schedule = radar::processRawSchedule(std::move(raw), trans->getName(),
1063
1064 trans->setSchedule(processed_schedule);
1065 recv->setSchedule(processed_schedule);
1066 }
1067
1068 // Link them and add to world
1069 trans->setAttached(recv.get());
1070 recv->setAttached(trans.get());
1071 world.add(std::move(trans));
1072 world.add(std::move(recv));
1073 }
1074
1075 void parse_platform(const nlohmann::json& plat_json, core::World& world, std::mt19937& masterSeeder)
1076 {
1077 auto name = plat_json.at("name").get<std::string>();
1078 const auto platform_id = parse_json_id(plat_json, "id", "Platform");
1079 auto plat = std::make_unique<radar::Platform>(name, platform_id);
1080
1081 serial::update_platform_paths_from_json(plat_json, plat.get());
1082
1083 // Components - Strict array format
1084 if (plat_json.contains("components"))
1085 {
1086 for (const auto& comp_json_outer : plat_json.at("components"))
1087 {
1088 if (comp_json_outer.contains("transmitter"))
1089 {
1090 parse_transmitter(comp_json_outer.at("transmitter"), plat.get(), world, masterSeeder);
1091 }
1092 else if (comp_json_outer.contains("receiver"))
1093 {
1094 parse_receiver(comp_json_outer.at("receiver"), plat.get(), world, masterSeeder);
1095 }
1096 else if (comp_json_outer.contains("target"))
1097 {
1098 parse_target(comp_json_outer.at("target"), plat.get(), world, masterSeeder);
1099 }
1100 else if (comp_json_outer.contains("monostatic"))
1101 {
1102 parse_monostatic(comp_json_outer.at("monostatic"), plat.get(), world, masterSeeder);
1103 }
1104 }
1105 }
1106
1107 world.add(std::move(plat));
1108 }
1109}
1110
1111namespace serial
1112{
1113 std::unique_ptr<antenna::Antenna> parse_antenna_from_json(const nlohmann::json& j)
1114 {
1115 std::unique_ptr<antenna::Antenna> ant;
1116 antenna::from_json(j, ant);
1117 return ant;
1118 }
1119
1120 std::unique_ptr<fers_signal::RadarSignal> parse_waveform_from_json(const nlohmann::json& j)
1121 {
1122 std::unique_ptr<fers_signal::RadarSignal> wf;
1124 return wf;
1125 }
1126
1127 std::unique_ptr<timing::PrototypeTiming> parse_timing_from_json(const nlohmann::json& j, const SimId id)
1128 {
1129 auto timing = std::make_unique<timing::PrototypeTiming>(j.at("name").get<std::string>(), id);
1130 j.get_to(*timing);
1131 return timing;
1132 }
1133
1134 void update_parameters_from_json(const nlohmann::json& j, std::mt19937& masterSeeder)
1135 {
1136 nlohmann::json sim;
1137 sim["parameters"] = j;
1138 parse_parameters(sim, masterSeeder);
1139 }
1140
1141 void update_antenna_from_json(const nlohmann::json& j, antenna::Antenna* ant, core::World& world)
1142 {
1143 auto new_pattern = j.value("pattern", "isotropic");
1144 bool type_changed = false;
1145
1146 const auto parse_required_antenna = [&j]()
1147 {
1148 auto parsed = parse_antenna_from_json(j);
1149 if (parsed == nullptr)
1150 {
1151 const auto name = j.value("name", std::string{});
1152 const auto pattern = j.value("pattern", "isotropic");
1153 throw std::runtime_error("Cannot update antenna '" + name + "' to pattern '" + pattern +
1154 "' without a filename.");
1155 }
1156 return parsed;
1157 };
1158
1159 if (new_pattern == "isotropic" && (dynamic_cast<antenna::Isotropic*>(ant) == nullptr))
1160 type_changed = true;
1161 else if (new_pattern == "sinc" && (dynamic_cast<antenna::Sinc*>(ant) == nullptr))
1162 type_changed = true;
1163 else if (new_pattern == "gaussian" && (dynamic_cast<antenna::Gaussian*>(ant) == nullptr))
1164 type_changed = true;
1165 else if (new_pattern == "squarehorn" && (dynamic_cast<antenna::SquareHorn*>(ant) == nullptr))
1166 type_changed = true;
1167 else if (new_pattern == "parabolic" && (dynamic_cast<antenna::Parabolic*>(ant) == nullptr))
1168 type_changed = true;
1169 else if (new_pattern == "xml" && (dynamic_cast<antenna::XmlAntenna*>(ant) == nullptr))
1170 type_changed = true;
1171 else if (new_pattern == "file" && (dynamic_cast<antenna::H5Antenna*>(ant) == nullptr))
1172 type_changed = true;
1173
1174 if (type_changed)
1175 {
1176 world.replace(parse_required_antenna());
1177 return;
1178 }
1179
1180 ant->setName(j.at("name").get<std::string>());
1181 ant->setEfficiencyFactor(j.value("efficiency", 1.0));
1182
1183 if (auto* sinc = dynamic_cast<antenna::Sinc*>(ant))
1184 {
1185 sinc->setAlpha(j.value("alpha", 1.0));
1186 sinc->setBeta(j.value("beta", 1.0));
1187 sinc->setGamma(j.value("gamma", 2.0));
1188 }
1189 else if (auto* gauss = dynamic_cast<antenna::Gaussian*>(ant))
1190 {
1191 gauss->setAzimuthScale(j.value("azscale", 1.0));
1192 gauss->setElevationScale(j.value("elscale", 1.0));
1193 }
1194 else if (auto* horn = dynamic_cast<antenna::SquareHorn*>(ant))
1195 {
1196 horn->setDimension(j.value("diameter", 0.5));
1197 }
1198 else if (auto* para = dynamic_cast<antenna::Parabolic*>(ant))
1199 {
1200 para->setDiameter(j.value("diameter", 0.5));
1201 }
1202 else if (auto* xml = dynamic_cast<antenna::XmlAntenna*>(ant))
1203 {
1204 if (xml->getFilename() != j.value("filename", ""))
1205 {
1206 world.replace(parse_required_antenna());
1207 }
1208 }
1209 else if (auto* h5 = dynamic_cast<antenna::H5Antenna*>(ant))
1210 {
1211 if (h5->getFilename() != j.value("filename", ""))
1212 {
1213 world.replace(parse_required_antenna());
1214 }
1215 }
1216 }
1217
1218 void update_platform_paths_from_json(const nlohmann::json& j, radar::Platform* plat)
1219 {
1220 if (j.contains("motionpath"))
1221 {
1222 auto path = std::make_unique<math::Path>();
1223 j.at("motionpath").get_to(*path);
1224 plat->setMotionPath(std::move(path));
1225 }
1226 if (j.contains("rotationpath"))
1227 {
1228 auto rot_path = std::make_unique<math::RotationPath>();
1229 const auto& rotation_json = j.at("rotationpath");
1230 rot_path->setInterp(rotation_json.at("interpolation").get<math::RotationPath::InterpType>());
1231 unsigned waypoint_index = 0;
1232 for (const auto& waypoint_json : rotation_json.at("rotationwaypoints"))
1233 {
1234 const RealType azimuth = waypoint_json.at("azimuth").get<RealType>();
1235 const RealType elevation = waypoint_json.at("elevation").get<RealType>();
1236 const RealType time = waypoint_json.at("time").get<RealType>();
1237 const std::string owner =
1238 std::format("platform '{}' rotation waypoint {}", plat->getName(), waypoint_index);
1239
1242 "JSON", owner, "azimuth");
1245 "JSON", owner, "elevation");
1246
1247 rot_path->addCoord(rotation_angle_utils::external_rotation_to_internal(azimuth, elevation, time,
1249 ++waypoint_index;
1250 }
1251 rot_path->finalize();
1252 plat->setRotationPath(std::move(rot_path));
1253 }
1254 else if (j.contains("fixedrotation"))
1255 {
1256 auto rot_path = std::make_unique<math::RotationPath>();
1257 const auto& fixed_json = j.at("fixedrotation");
1258 const RealType start_az_deg = fixed_json.at("startazimuth").get<RealType>();
1259 const RealType start_el_deg = fixed_json.at("startelevation").get<RealType>();
1260 const RealType rate_az_deg_s = fixed_json.at("azimuthrate").get<RealType>();
1261 const RealType rate_el_deg_s = fixed_json.at("elevationrate").get<RealType>();
1262 const std::string owner = std::format("platform '{}' fixedrotation", plat->getName());
1263
1266 owner, "startazimuth");
1269 owner, "startelevation");
1272 owner, "azimuthrate");
1275 owner, "elevationrate");
1276
1278 start_az_deg, start_el_deg, 0.0, params::rotationAngleUnit());
1280 rate_az_deg_s, rate_el_deg_s, 0.0, params::rotationAngleUnit());
1281 rot_path->setConstantRate(start, rate);
1282 rot_path->finalize();
1283 plat->setRotationPath(std::move(rot_path));
1284 }
1285 }
1286
1287 void update_transmitter_from_json(const nlohmann::json& j, radar::Transmitter* tx, core::World& world,
1288 std::mt19937& /*masterSeeder*/)
1289 {
1290 if (j.contains("name"))
1291 tx->setName(j.at("name").get<std::string>());
1292
1293 if (j.contains("pulsed_mode"))
1294 {
1296 tx->setPrf(j.at("pulsed_mode").value("prf", 0.0));
1297 }
1298 else if (j.contains("cw_mode"))
1299 {
1301 }
1302
1303 if (j.contains("waveform"))
1304 {
1305 auto id = parse_json_id(j, "waveform", "Transmitter");
1306 auto* wf = world.findWaveform(id);
1307 if (wf == nullptr)
1308 throw std::runtime_error("Waveform ID " + std::to_string(id) + " not found.");
1309 tx->setWave(wf);
1310 }
1311
1312 if (j.contains("antenna"))
1313 {
1314 auto id = parse_json_id(j, "antenna", "Transmitter");
1315 auto* ant = world.findAntenna(id);
1316 if (ant == nullptr)
1317 throw std::runtime_error("Antenna ID " + std::to_string(id) + " not found.");
1318 tx->setAntenna(ant);
1319 }
1320
1321 if (j.contains("timing"))
1322 {
1323 auto timing_id = parse_json_id(j, "timing", "Transmitter");
1324 if (auto* const timing_proto = world.findTiming(timing_id))
1325 {
1326 unsigned seed = tx->getTiming() ? tx->getTiming()->getSeed() : 0;
1327 auto timing = std::make_shared<timing::Timing>(timing_proto->getName(), seed, timing_proto->getId());
1328 timing->initializeModel(timing_proto);
1329 tx->setTiming(timing);
1330 }
1331 else
1332 {
1333 throw std::runtime_error("Timing ID " + std::to_string(timing_id) + " not found.");
1334 }
1335 }
1336 if (j.contains("schedule"))
1337 {
1338 auto raw = j.at("schedule").get<std::vector<radar::SchedulePeriod>>();
1339 RealType pri = 0.0;
1341 pri = 1.0 / tx->getPrf();
1342 tx->setSchedule(radar::processRawSchedule(std::move(raw), tx->getName(),
1344 }
1345 }
1346
1347 void update_receiver_from_json(const nlohmann::json& j, radar::Receiver* rx, core::World& world,
1348 std::mt19937& /*masterSeeder*/)
1349 {
1350 if (j.contains("name"))
1351 rx->setName(j.at("name").get<std::string>());
1352
1353 if (j.contains("pulsed_mode"))
1354 {
1356 const auto& mode_json = j.at("pulsed_mode");
1357 rx->setWindowProperties(mode_json.value("window_length", 0.0), mode_json.value("prf", 0.0),
1358 mode_json.value("window_skip", 0.0));
1359 }
1360 else if (j.contains("cw_mode"))
1361 {
1363 }
1364
1365 if (j.contains("noise_temp"))
1366 rx->setNoiseTemperature(j.value("noise_temp", 0.0));
1367
1368 if (j.contains("nodirect"))
1369 {
1370 if (j.value("nodirect", false))
1372 else
1374 }
1375 if (j.contains("nopropagationloss"))
1376 {
1377 if (j.value("nopropagationloss", false))
1379 else
1381 }
1382
1383 if (j.contains("antenna"))
1384 {
1385 auto id = parse_json_id(j, "antenna", "Receiver");
1386 auto* ant = world.findAntenna(id);
1387 if (ant == nullptr)
1388 throw std::runtime_error("Antenna ID " + std::to_string(id) + " not found.");
1389 rx->setAntenna(ant);
1390 }
1391
1392 if (j.contains("timing"))
1393 {
1394 auto timing_id = parse_json_id(j, "timing", "Receiver");
1395 if (auto* const timing_proto = world.findTiming(timing_id))
1396 {
1397 unsigned seed = rx->getTiming() ? rx->getTiming()->getSeed() : 0;
1398 auto timing = std::make_shared<timing::Timing>(timing_proto->getName(), seed, timing_proto->getId());
1399 timing->initializeModel(timing_proto);
1400 rx->setTiming(timing);
1401 }
1402 else
1403 {
1404 throw std::runtime_error("Timing ID " + std::to_string(timing_id) + " not found.");
1405 }
1406 }
1407 if (j.contains("schedule"))
1408 {
1409 auto raw = j.at("schedule").get<std::vector<radar::SchedulePeriod>>();
1410 RealType pri = 0.0;
1412 pri = 1.0 / rx->getWindowPrf();
1413 rx->setSchedule(radar::processRawSchedule(std::move(raw), rx->getName(),
1415 }
1416 }
1417
1419 core::World& world, std::mt19937& masterSeeder)
1420 {
1421 update_transmitter_from_json(j, tx, world, masterSeeder);
1422
1423 if (j.contains("name"))
1424 rx->setName(j.at("name").get<std::string>());
1425 rx->setMode(tx->getMode());
1426 if (rx->getMode() == radar::OperationMode::PULSED_MODE && j.contains("pulsed_mode"))
1427 {
1428 const auto& mode_json = j.at("pulsed_mode");
1429 rx->setWindowProperties(mode_json.value("window_length", 0.0), tx->getPrf(),
1430 mode_json.value("window_skip", 0.0));
1431 }
1432 if (j.contains("noise_temp"))
1433 rx->setNoiseTemperature(j.value("noise_temp", 0.0));
1434 if (j.contains("nodirect"))
1435 {
1436 if (j.value("nodirect", false))
1438 else
1440 }
1441 if (j.contains("nopropagationloss"))
1442 {
1443 if (j.value("nopropagationloss", false))
1445 else
1447 }
1448 if (j.contains("antenna"))
1449 rx->setAntenna(world.findAntenna(parse_json_id(j, "antenna", "Monostatic")));
1450 if (j.contains("timing"))
1451 {
1452 auto timing_id = parse_json_id(j, "timing", "Monostatic");
1453 if (auto* const timing_proto = world.findTiming(timing_id))
1454 {
1455 unsigned seed = rx->getTiming() ? rx->getTiming()->getSeed() : 0;
1456 auto rx_timing = std::make_shared<timing::Timing>(timing_proto->getName(), seed, timing_proto->getId());
1457 rx_timing->initializeModel(timing_proto);
1458 rx->setTiming(rx_timing);
1459 }
1460 else
1461 {
1462 rx->setTiming(nullptr);
1463 }
1464 }
1465 if (j.contains("schedule"))
1466 {
1467 auto raw = j.at("schedule").get<std::vector<radar::SchedulePeriod>>();
1468 RealType pri = 0.0;
1470 pri = 1.0 / tx->getPrf();
1471 auto processed_schedule = radar::processRawSchedule(
1472 std::move(raw), tx->getName(), tx->getMode() == radar::OperationMode::PULSED_MODE, pri);
1473 tx->setSchedule(processed_schedule);
1474 rx->setSchedule(processed_schedule);
1475 }
1476 }
1477
1478 void update_target_from_json(const nlohmann::json& j, radar::Target* existing_tgt, core::World& world,
1479 std::mt19937& /*masterSeeder*/)
1480 {
1481 auto* plat = existing_tgt->getPlatform();
1482 const auto& rcs_json = j.at("rcs");
1483 const auto rcs_type = rcs_json.at("type").get<std::string>();
1484 std::unique_ptr<radar::Target> target_obj;
1485
1486 const auto target_id = existing_tgt->getId();
1487 const auto name = j.value("name", existing_tgt->getName());
1488 unsigned seed = existing_tgt->getSeed();
1489
1490 if (rcs_type == "isotropic")
1491 {
1492 target_obj = radar::createIsoTarget(plat, name, rcs_json.value("value", 1.0), seed, target_id);
1493 }
1494 else if (rcs_type == "file")
1495 {
1496 const auto filename = rcs_json.value("filename", "");
1497 target_obj = radar::createFileTarget(plat, name, filename, seed, target_id);
1498 }
1499 else
1500 {
1501 throw std::runtime_error("Unsupported target RCS type: " + rcs_type);
1502 }
1503
1504 if (j.contains("model"))
1505 {
1506 const auto& model_json = j.at("model");
1507 const auto model_type = model_json.at("type").get<std::string>();
1508 if (model_type == "chisquare" || model_type == "gamma")
1509 {
1510 auto model =
1511 std::make_unique<radar::RcsChiSquare>(target_obj->getRngEngine(), model_json.value("k", 1.0));
1512 target_obj->setFluctuationModel(std::move(model));
1513 }
1514 else if (model_type == "constant")
1515 {
1516 target_obj->setFluctuationModel(std::make_unique<radar::RcsConst>());
1517 }
1518 }
1519
1520 world.replace(std::move(target_obj));
1521 }
1522
1523 void update_timing_from_json(const nlohmann::json& j, core::World& world, const SimId id)
1524 {
1525 auto* existing = world.findTiming(id);
1526 if (existing == nullptr)
1527 {
1528 throw std::runtime_error("Timing ID " + std::to_string(id) + " not found.");
1529 }
1530
1531 auto patched = j;
1532 if (!patched.contains("name"))
1533 {
1534 patched["name"] = existing->getName();
1535 }
1536
1537 world.replace(parse_timing_from_json(patched, id));
1538 }
1539
1540 nlohmann::json world_to_json(const core::World& world)
1541 {
1542 nlohmann::json sim_json;
1543
1544 sim_json["name"] = params::params.simulation_name;
1545 sim_json["parameters"] = params::params;
1546
1547 sim_json["waveforms"] = nlohmann::json::array();
1548 for (const auto& waveform : world.getWaveforms() | std::views::values)
1549 {
1550 sim_json["waveforms"].push_back(*waveform);
1551 }
1552
1553 sim_json["antennas"] = nlohmann::json::array();
1554 for (const auto& antenna : world.getAntennas() | std::views::values)
1555 {
1556 sim_json["antennas"].push_back(*antenna);
1557 }
1558
1559 sim_json["timings"] = nlohmann::json::array();
1560 for (const auto& timing : world.getTimings() | std::views::values)
1561 {
1562 sim_json["timings"].push_back(*timing);
1563 }
1564
1565 sim_json["platforms"] = nlohmann::json::array();
1566 for (const auto& p : world.getPlatforms())
1567 {
1568 sim_json["platforms"].push_back(serialize_platform(p.get(), world));
1569 }
1570
1571 return {{"simulation", sim_json}};
1572 }
1573
1574 void json_to_world(const nlohmann::json& j, core::World& world, std::mt19937& masterSeeder)
1575 {
1576 // 1. Clear the existing world state. This function always performs a full
1577 // replacement to ensure the C++ state is a perfect mirror of the UI state.
1578 world.clear();
1579
1580 const auto& sim = j.at("simulation");
1581
1582 parse_parameters(sim, masterSeeder);
1583 parse_assets(sim, world);
1584
1585 // 3. Restore platforms and their components.
1586 if (sim.contains("platforms"))
1587 {
1588 for (const auto& plat_json : sim.at("platforms"))
1589 {
1590 parse_platform(plat_json, world, masterSeeder);
1591 }
1592 }
1593
1594 // 4. Finalize world state after all objects are loaded.
1595
1596 // Prepare CW receiver buffers before starting simulation
1597 const RealType start_time = params::startTime();
1598 const RealType end_time = params::endTime();
1599 const RealType dt_sim = 1.0 / (params::rate() * params::oversampleRatio());
1600 const auto num_samples = static_cast<size_t>(std::ceil((end_time - start_time) / dt_sim));
1601
1602 for (const auto& receiver : world.getReceivers())
1603 {
1604 if (receiver->getMode() == radar::OperationMode::CW_MODE)
1605 {
1606 receiver->prepareCwData(num_samples);
1607 }
1608 }
1609
1610 // Schedule initial events after all objects are loaded.
1611 world.scheduleInitialEvents();
1612 }
1613}
Header file defining various types of antennas and their gain patterns.
Abstract base class representing an antenna.
SimId getId() const noexcept
Retrieves the unique ID of the antenna.
void setEfficiencyFactor(RealType loss) noexcept
Sets the efficiency factor of the antenna.
RealType getEfficiencyFactor() const noexcept
Retrieves the efficiency factor of the antenna.
void setName(std::string name) noexcept
Sets the name of the antenna.
std::string getName() const noexcept
Retrieves the name of the antenna.
Represents a Gaussian-shaped antenna gain pattern.
Represents an antenna whose gain pattern is loaded from a HDF5 file.
Represents an isotropic antenna with uniform gain in all directions.
Represents a parabolic reflector antenna.
Represents a sinc function-based antenna gain pattern.
Represents a square horn antenna.
Represents an antenna whose gain pattern is defined by an XML file.
The World class manages the simulator environment.
Definition world.h:39
void scheduleInitialEvents()
Populates the event queue with the initial events for the simulation.
Definition world.cpp:256
void add(std::unique_ptr< radar::Platform > plat) noexcept
Adds a radar platform to the simulation world.
Definition world.cpp:38
void replace(std::unique_ptr< radar::Target > target)
Replaces an existing target, updating internal pointers.
Definition world.cpp:128
fers_signal::RadarSignal * findWaveform(const SimId id)
Finds a radar signal by ID.
Definition world.cpp:76
const std::vector< std::unique_ptr< radar::Target > > & getTargets() const noexcept
Retrieves the list of radar targets.
Definition world.h:200
const std::unordered_map< SimId, std::unique_ptr< antenna::Antenna > > & getAntennas() const noexcept
Retrieves the map of antennas.
Definition world.h:239
void clear() noexcept
Clears all objects and assets from the simulation world.
Definition world.cpp:243
const std::unordered_map< SimId, std::unique_ptr< fers_signal::RadarSignal > > & getWaveforms() const noexcept
Retrieves the map of radar signals (waveforms).
Definition world.h:230
timing::PrototypeTiming * findTiming(const SimId id)
Finds a timing source by ID.
Definition world.cpp:88
antenna::Antenna * findAntenna(const SimId id)
Finds an antenna by ID.
Definition world.cpp:82
const std::unordered_map< SimId, std::unique_ptr< timing::PrototypeTiming > > & getTimings() const noexcept
Retrieves the map of timing prototypes.
Definition world.h:249
const std::vector< std::unique_ptr< radar::Platform > > & getPlatforms() const noexcept
Retrieves the list of platforms.
Definition world.h:190
const std::vector< std::unique_ptr< radar::Receiver > > & getReceivers() const noexcept
Retrieves the list of radar receivers.
Definition world.h:210
Class representing a radar signal with associated properties.
SimId getId() const noexcept
Gets the unique ID of the radar signal.
const std::optional< std::string > & getFilename() const noexcept
Gets the filename associated with this signal.
const std::string & getName() const noexcept
Gets the name of the radar signal.
RealType getCarrier() const noexcept
Gets the carrier frequency of the radar signal.
const Signal * getSignal() const noexcept
Gets the underlying signal object.
RealType getPower() const noexcept
Gets the power of the radar signal.
Represents a path with coordinates and allows for various interpolation methods.
Definition path.h:30
void setInterp(InterpType settype) noexcept
Changes the interpolation type.
Definition path.cpp:164
InterpType
Types of interpolation supported by the Path class.
Definition path.h:36
void addCoord(const Coord &coord) noexcept
Adds a coordinate to the path.
Definition path.cpp:27
void finalize()
Finalizes the path, preparing it for interpolation.
Definition path.cpp:147
Manages rotational paths with different interpolation techniques.
void finalize()
Finalizes the rotation path for interpolation.
InterpType getType() const noexcept
Gets the interpolation type of the path.
void setInterp(InterpType setinterp) noexcept
Sets the interpolation type for the path.
InterpType
Enumeration for types of interpolation.
void addCoord(const RotationCoord &coord) noexcept
Adds a rotation coordinate to the path.
A class representing a vector in rectangular coordinates.
RealType x
The x component of the vector.
RealType z
The z component of the vector.
RealType y
The y component of the vector.
File-based radar target.
Definition target.h:226
Isotropic radar target.
Definition target.h:188
const std::string & getName() const noexcept
Retrieves the name of the object.
Definition object.h:79
Platform * getPlatform() const noexcept
Retrieves the associated platform of the object.
Definition object.h:65
void setName(std::string name) noexcept
Sets the name of the object.
Definition object.h:86
Represents a simulation platform with motion and rotation paths.
Definition platform.h:32
SimId getId() const noexcept
Gets the unique ID of the platform.
Definition platform.h:97
math::Path * getMotionPath() const noexcept
Gets the motion path of the platform.
Definition platform.h:60
const std::string & getName() const noexcept
Gets the name of the platform.
Definition platform.h:90
void setMotionPath(std::unique_ptr< math::Path > path) noexcept
Sets the motion path of the platform.
Definition platform.h:111
math::RotationPath * getRotationPath() const noexcept
Gets the rotation path of the platform.
Definition platform.h:67
void setRotationPath(std::unique_ptr< math::RotationPath > path) noexcept
Sets the rotation path of the platform.
Definition platform.h:104
void setAntenna(const antenna::Antenna *ant)
Sets the antenna for the radar.
Definition radar_obj.cpp:46
const antenna::Antenna * getAntenna() const noexcept
Gets the antenna associated with this radar.
Definition radar_obj.h:90
std::shared_ptr< timing::Timing > getTiming() const
Retrieves the timing source for the radar.
Definition radar_obj.cpp:66
void setTiming(const std::shared_ptr< timing::Timing > &tim)
Sets the timing source for the radar.
Definition radar_obj.cpp:36
Chi-square distributed RCS model.
Definition target.h:82
Manages radar signal reception and response processing.
Definition receiver.h:37
void setMode(OperationMode mode) noexcept
Sets the operational mode of the receiver.
Definition receiver.h:178
bool checkFlag(RecvFlag flag) const noexcept
Checks if a specific flag is set.
Definition receiver.h:88
void setFlag(RecvFlag flag) noexcept
Sets a receiver flag.
Definition receiver.h:216
const std::vector< SchedulePeriod > & getSchedule() const noexcept
Retrieves the list of active reception periods.
Definition receiver.h:277
void clearFlag(RecvFlag flag) noexcept
Clears a receiver flag.
Definition receiver.h:223
void setSchedule(std::vector< SchedulePeriod > schedule)
Sets the active schedule for the receiver.
Definition receiver.cpp:130
SimId getId() const noexcept
Retrieves the unique ID of the receiver.
Definition receiver.h:95
RealType getNoiseTemperature() const noexcept
Retrieves the noise temperature of the receiver.
Definition receiver.h:102
OperationMode getMode() const noexcept
Gets the operational mode of the receiver.
Definition receiver.h:160
RealType getWindowPrf() const noexcept
Retrieves the pulse repetition frequency (PRF) of the radar window.
Definition receiver.h:116
RealType getWindowSkip() const noexcept
Retrieves the window skip time.
Definition receiver.h:123
void setWindowProperties(RealType length, RealType prf, RealType skip) noexcept
Sets the properties for radar windows.
Definition receiver.cpp:90
void setNoiseTemperature(RealType temp)
Sets the noise temperature of the receiver.
Definition receiver.cpp:80
RealType getWindowLength() const noexcept
Retrieves the radar window length.
Definition receiver.h:109
Base class for radar targets.
Definition target.h:118
unsigned getSeed() const noexcept
Gets the initial seed used for the target's RNG.
Definition target.h:174
SimId getId() const noexcept
Gets the unique ID of the target.
Definition target.h:153
const RcsModel * getFluctuationModel() const
Gets the RCS fluctuation model.
Definition target.h:167
Represents a radar transmitter system.
Definition transmitter.h:33
void setWave(fers_signal::RadarSignal *pulse) noexcept
Sets the radar signal wave to be transmitted.
Definition transmitter.h:99
void setMode(OperationMode mode) noexcept
Sets the operational mode of the transmitter.
Definition transmitter.h:92
SimId getId() const noexcept
Retrieves the unique ID of the transmitter.
Definition transmitter.h:78
RealType getPrf() const noexcept
Retrieves the pulse repetition frequency (PRF).
Definition transmitter.h:64
fers_signal::RadarSignal * getSignal() const noexcept
Retrieves the radar signal currently being transmitted.
Definition transmitter.h:71
const std::vector< SchedulePeriod > & getSchedule() const noexcept
Retrieves the list of active transmission periods.
OperationMode getMode() const noexcept
Gets the operational mode of the transmitter.
Definition transmitter.h:85
void setSchedule(std::vector< SchedulePeriod > schedule)
Sets the active schedule for the transmitter.
void setPrf(RealType mprf) noexcept
Sets the pulse repetition frequency (PRF) of the transmitter.
Manages timing properties such as frequency, offsets, and synchronization.
void clearNoiseEntries() noexcept
Clears all noise entries.
void setSyncOnPulse() noexcept
Enables synchronization on pulse.
void clearPhaseOffset() noexcept
Clears the phase offset.
void setAlpha(RealType alpha, RealType weight) noexcept
Sets an alpha and weight value.
void setRandomPhaseOffsetStdev(RealType stdev) noexcept
Sets a random phase offset standard deviation.
std::optional< RealType > getRandomPhaseOffsetStdev() const noexcept
bool getSyncOnPulse() const noexcept
Checks if synchronization on pulse is enabled.
void clearSyncOnPulse() noexcept
Disables synchronization on pulse.
std::string getName() const
Gets the name of the timing source.
void setFreqOffset(RealType offset) noexcept
Sets the frequency offset.
std::optional< RealType > getRandomFreqOffsetStdev() const noexcept
void setPhaseOffset(RealType offset) noexcept
Sets the phase offset.
void setRandomFreqOffsetStdev(RealType stdev) noexcept
Sets a random frequency offset standard deviation.
RealType getFrequency() const noexcept
Gets the current frequency.
void setFrequency(const RealType freq) noexcept
Sets the frequency value.
void clearRandomPhaseOffsetStdev() noexcept
Clears the random phase offset standard deviation.
std::optional< RealType > getPhaseOffset() const noexcept
Gets the phase offset.
void clearFreqOffset() noexcept
Clears the frequency offset.
void copyAlphas(std::vector< RealType > &alphas, std::vector< RealType > &weights) const noexcept
Copies the alphas and weights vectors.
SimId getId() const noexcept
Gets the unique ID of the timing source.
void clearRandomFreqOffsetStdev() noexcept
Clears the random frequency offset standard deviation.
std::optional< RealType > getFreqOffset() const noexcept
Gets the frequency offset.
double RealType
Type for real numbers.
Definition config.h:27
Coordinate and rotation structure operations.
Provides functions to serialize and deserialize the simulation world to/from JSON.
#define LOG(level,...)
Definition logging.h:19
void to_json(nlohmann::json &j, const Antenna &a)
void from_json(const nlohmann::json &j, std::unique_ptr< Antenna > &ant)
void from_json(const nlohmann::json &j, std::unique_ptr< RadarSignal > &rs)
void to_json(nlohmann::json &j, const RadarSignal &rs)
@ WARNING
Warning level for potentially harmful situations.
@ INFO
Info level for informational messages.
Definition coord.h:18
NLOHMANN_JSON_SERIALIZE_ENUM(Path::InterpType, {{Path::InterpType::INTERP_STATIC, "static"}, {Path::InterpType::INTERP_LINEAR, "linear"}, {Path::InterpType::INTERP_CUBIC, "cubic"}}) void to_json(nlohmann
void to_json(nlohmann::json &j, const Vec3 &v)
void from_json(const nlohmann::json &j, Vec3 &v)
RealType endTime() noexcept
Get the end time for the simulation.
Definition parameters.h:109
RealType rate() noexcept
Get the rendering sample rate.
Definition parameters.h:121
RealType startTime() noexcept
Get the start time for the simulation.
Definition parameters.h:103
unsigned oversampleRatio() noexcept
Get the oversampling ratio.
Definition parameters.h:151
RotationAngleUnit
Defines the units used at external rotation-path boundaries.
Definition parameters.h:41
@ Radians
Compass azimuth and elevation expressed in radians.
@ Degrees
Compass azimuth and elevation expressed in degrees.
CoordinateFrame
Defines the coordinate systems supported for scenario definition.
Definition parameters.h:30
@ UTM
Universal Transverse Mercator.
@ ENU
East-North-Up local tangent plane (default)
@ ECEF
Earth-Centered, Earth-Fixed.
NLOHMANN_JSON_SERIALIZE_ENUM(CoordinateFrame, {{CoordinateFrame::ENU, "ENU"}, {CoordinateFrame::UTM, "UTM"}, {CoordinateFrame::ECEF, "ECEF"}}) NLOHMANN_JSON_SERIALIZE_ENUM(RotationAngleUnit
RotationAngleUnit rotationAngleUnit() noexcept
Definition parameters.h:286
Parameters params
Definition parameters.h:85
std::unique_ptr< Target > createIsoTarget(Platform *platform, std::string name, RealType rcs, unsigned seed, const SimId id=0)
Creates an isotropic target.
Definition target.h:282
void to_json(nlohmann::json &j, const SchedulePeriod &p)
void from_json(const nlohmann::json &j, SchedulePeriod &p)
std::unique_ptr< Target > createFileTarget(Platform *platform, std::string name, const std::string &filename, unsigned seed, const SimId id=0)
Creates a file-based target.
Definition target.h:297
std::vector< SchedulePeriod > processRawSchedule(std::vector< SchedulePeriod > periods, const std::string &ownerName, const bool isPulsed, const RealType pri)
Processes a raw list of schedule periods.
OperationMode
Defines the operational mode of a radar component.
Definition radar_obj.h:37
@ PULSED_MODE
The component operates in a pulsed mode.
@ CW_MODE
The component operates in a continuous-wave mode.
RealType internal_elevation_to_external(const RealType elevation, const params::RotationAngleUnit unit) noexcept
RealType internal_azimuth_rate_to_external(const RealType azimuth_rate, const params::RotationAngleUnit unit) noexcept
math::RotationCoord external_rotation_to_internal(const RealType azimuth, const RealType elevation, const RealType time, const params::RotationAngleUnit unit) noexcept
RealType internal_elevation_rate_to_external(const RealType elevation_rate, const params::RotationAngleUnit unit) noexcept
RealType internal_azimuth_to_external(const RealType azimuth, const params::RotationAngleUnit unit) noexcept
math::RotationCoord external_rotation_rate_to_internal(const RealType azimuth_rate, const RealType elevation_rate, const RealType time, const params::RotationAngleUnit unit) noexcept
void maybe_warn_about_rotation_value(const RealType value, const params::RotationAngleUnit declared_unit, const ValueKind kind, const std::string_view source, const std::string_view owner, const std::string_view field)
void update_platform_paths_from_json(const nlohmann::json &j, radar::Platform *plat)
Updates a platform's motion and rotation paths from JSON.
void update_parameters_from_json(const nlohmann::json &j, std::mt19937 &masterSeeder)
Updates global simulation parameters from JSON.
void json_to_world(const nlohmann::json &j, core::World &world, std::mt19937 &masterSeeder)
Deserializes a nlohmann::json object and reconstructs the simulation world.
void update_receiver_from_json(const nlohmann::json &j, radar::Receiver *rx, core::World &world, std::mt19937 &)
Updates a receiver from JSON without full context recreation.
void update_timing_from_json(const nlohmann::json &j, core::World &world, const SimId id)
Updates a timing source from JSON without full context recreation.
std::unique_ptr< RadarSignal > loadWaveformFromFile(const std::string &name, const std::string &filename, const RealType power, const RealType carrierFreq, const SimId id)
Loads a radar waveform from a file and returns a RadarSignal object.
void update_monostatic_from_json(const nlohmann::json &j, radar::Transmitter *tx, radar::Receiver *rx, core::World &world, std::mt19937 &masterSeeder)
Updates a monostatic radar from JSON without full context recreation.
void update_transmitter_from_json(const nlohmann::json &j, radar::Transmitter *tx, core::World &world, std::mt19937 &)
Updates a transmitter from JSON without full context recreation.
void update_antenna_from_json(const nlohmann::json &j, antenna::Antenna *ant, core::World &world)
Updates an antenna from JSON without full context recreation.
std::unique_ptr< antenna::Antenna > parse_antenna_from_json(const nlohmann::json &j)
Parses an Antenna from JSON.
void update_target_from_json(const nlohmann::json &j, radar::Target *existing_tgt, core::World &world, std::mt19937 &)
Updates a target from JSON without full context recreation.
nlohmann::json world_to_json(const core::World &world)
Serializes the entire simulation world into a nlohmann::json object.
std::unique_ptr< timing::PrototypeTiming > parse_timing_from_json(const nlohmann::json &j, const SimId id)
Parses a timing prototype from JSON.
std::unique_ptr< fers_signal::RadarSignal > parse_waveform_from_json(const nlohmann::json &j)
Parses a Waveform from JSON.
void from_json(const nlohmann::json &j, PrototypeTiming &pt)
void to_json(nlohmann::json &j, const PrototypeTiming &pt)
Defines the Parameters struct and provides methods for managing simulation parameters.
Provides the definition and functionality of the Path class for handling coordinate-based paths with ...
Defines the Platform class used in radar simulation.
Header file for the PrototypeTiming class.
Classes for handling radar waveforms and signals.
Radar Receiver class for managing signal reception and response handling.
Defines the RotationPath class for handling rotational paths with different interpolation types.
uint64_t SimId
64-bit Unique Simulation ID.
Definition sim_id.h:18
Represents a position in 3D space with an associated time.
Definition coord.h:24
Represents a rotation in terms of azimuth, elevation, and time.
Definition coord.h:72
RealType t
Time.
Definition coord.h:75
RealType elevation
Elevation angle.
Definition coord.h:74
RealType azimuth
Azimuth angle.
Definition coord.h:73
Struct to hold simulation parameters.
Definition parameters.h:51
std::optional< unsigned > random_seed
Random seed for simulation.
Definition parameters.h:70
std::string simulation_name
The name of the simulation, from the XML.
Definition parameters.h:74
static constexpr RealType DEFAULT_C
Speed of light (m/s)
Definition parameters.h:52
Represents a time period during which the transmitter is active.
Defines classes for radar targets and their Radar Cross-Section (RCS) models.
Timing source for simulation objects.
Header file for the Transmitter class in the radar namespace.
Interface for loading waveform data into RadarSignal objects.
Header file for the World class in the simulator.