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 <nlohmann/json.hpp>
20#include <random>
21
23#include "core/parameters.h"
24#include "core/world.h"
25#include "math/coord.h"
26#include "math/path.h"
27#include "math/rotation_path.h"
28#include "radar/platform.h"
29#include "radar/receiver.h"
30#include "radar/target.h"
31#include "radar/transmitter.h"
32#include "signal/radar_signal.h"
34#include "timing/timing.h"
35#include "waveform_factory.h"
36
37// TODO: Add file path validation and error handling as needed.
38
39namespace math
40{
41 void to_json(nlohmann::json& j, const Vec3& v) { j = {{"x", v.x}, {"y", v.y}, {"z", v.z}}; }
42
43 void from_json(const nlohmann::json& j, Vec3& v)
44 {
45 j.at("x").get_to(v.x);
46 j.at("y").get_to(v.y);
47 j.at("z").get_to(v.z);
48 }
49
50 void to_json(nlohmann::json& j, const Coord& c)
51 {
52 j = {{"time", c.t}, {"x", c.pos.x}, {"y", c.pos.y}, {"altitude", c.pos.z}};
53 }
54
55 void from_json(const nlohmann::json& j, Coord& c)
56 {
57 j.at("time").get_to(c.t);
58 j.at("x").get_to(c.pos.x);
59 j.at("y").get_to(c.pos.y);
60 j.at("altitude").get_to(c.pos.z);
61 }
62
63 void to_json(nlohmann::json& j, const RotationCoord& rc)
64 {
65 // The internal engine uses mathematical angles (radians, CCW from East),
66 // but the UI and XML format use compass degrees (CW from North).
67 // We intentionally DO NOT normalize the output (no fmod) to preserve
68 // negative angles or multi-turn rotations (winding) defined by the user.
69 const RealType az_deg = 90.0 - rc.azimuth * 180.0 / PI;
70 const RealType el_deg = rc.elevation * 180.0 / PI;
71 j = {{"time", rc.t}, {"azimuth", az_deg}, {"elevation", el_deg}};
72 }
73
74 void from_json(const nlohmann::json& j, RotationCoord& rc)
75 {
76 j.at("time").get_to(rc.t);
77 const RealType az_deg = j.at("azimuth").get<RealType>();
78 const RealType el_deg = j.at("elevation").get<RealType>();
79
80 // Convert from compass degrees (from JSON/UI) back to the internal engine's
81 // mathematical angle representation (radians, CCW from East). This keeps
82 // all internal physics calculations consistent.
83 rc.azimuth = (90.0 - az_deg) * (PI / 180.0);
84 rc.elevation = el_deg * (PI / 180.0);
85 }
86
91
92 void to_json(nlohmann::json& j, const Path& p)
93 {
94 j = {{"interpolation", p.getType()}, {"positionwaypoints", p.getCoords()}};
95 }
96
97 void from_json(const nlohmann::json& j, Path& p)
98 {
99 p.setInterp(j.at("interpolation").get<Path::InterpType>());
100 for (const auto waypoints = j.at("positionwaypoints").get<std::vector<Coord>>(); const auto& wp : waypoints)
101 {
102 p.addCoord(wp);
103 }
104 p.finalize();
105 }
106
110 "constant"}, // Not used in xml_parser or UI yet, but for completeness
113
114 void to_json(nlohmann::json& j, const RotationPath& p)
115 {
116 j["interpolation"] = p.getType();
117 // This logic exists to map the two different rotation definitions from the
118 // XML schema (<fixedrotation> and <rotationpath>) into a unified JSON
119 // structure that the frontend can more easily handle.
121 {
122 // A constant-rate rotation path corresponds to the <fixedrotation> XML element.
123 // The start and rate values are converted to compass degrees per second.
124 // No normalization is applied to preserve negative start angles.
125 const RealType start_az_deg = 90.0 - p.getStart().azimuth * 180.0 / PI;
126 const RealType start_el_deg = p.getStart().elevation * 180.0 / PI;
127 const RealType rate_az_deg_s = -p.getRate().azimuth * 180.0 / PI; // Invert for CW rate
128 const RealType rate_el_deg_s = p.getRate().elevation * 180.0 / PI;
129 j["startazimuth"] = start_az_deg;
130 j["startelevation"] = start_el_deg;
131 j["azimuthrate"] = rate_az_deg_s;
132 j["elevationrate"] = rate_el_deg_s;
133 }
134 else
135 {
136 j["rotationwaypoints"] = p.getCoords();
137 }
138 }
139
140 void from_json(const nlohmann::json& j, RotationPath& p)
141 {
142 p.setInterp(j.at("interpolation").get<RotationPath::InterpType>());
143 for (const auto waypoints = j.at("rotationwaypoints").get<std::vector<RotationCoord>>();
144 const auto& wp : waypoints)
145 {
146 p.addCoord(wp);
147 }
148 p.finalize();
149 }
150
151}
152
153namespace timing
154{
155 void to_json(nlohmann::json& j, const PrototypeTiming& pt)
156 {
157 j = nlohmann::json{
158 {"name", pt.getName()}, {"frequency", pt.getFrequency()}, {"synconpulse", pt.getSyncOnPulse()}};
159
160 if (pt.getFreqOffset().has_value())
161 {
162 j["freq_offset"] = pt.getFreqOffset().value();
163 }
164 if (pt.getRandomFreqOffsetStdev().has_value())
165 {
166 j["random_freq_offset_stdev"] = pt.getRandomFreqOffsetStdev().value();
167 }
168 if (pt.getPhaseOffset().has_value())
169 {
170 j["phase_offset"] = pt.getPhaseOffset().value();
171 }
172 if (pt.getRandomPhaseOffsetStdev().has_value())
173 {
174 j["random_phase_offset_stdev"] = pt.getRandomPhaseOffsetStdev().value();
175 }
176
177 std::vector<RealType> alphas;
178 std::vector<RealType> weights;
179 pt.copyAlphas(alphas, weights);
180 if (!alphas.empty())
181 {
182 nlohmann::json noise_entries = nlohmann::json::array();
183 for (size_t i = 0; i < alphas.size(); ++i)
184 {
185 noise_entries.push_back({{"alpha", alphas[i]}, {"weight", weights[i]}});
186 }
187 j["noise_entries"] = noise_entries;
188 }
189 }
190
191 void from_json(const nlohmann::json& j, PrototypeTiming& pt)
192 {
193 pt.setFrequency(j.at("frequency").get<RealType>());
194 if (j.value("synconpulse", false))
195 {
196 pt.setSyncOnPulse();
197 }
198
199 if (j.contains("freq_offset"))
200 {
201 pt.setFreqOffset(j.at("freq_offset").get<RealType>());
202 }
203 if (j.contains("random_freq_offset_stdev"))
204 {
205 pt.setRandomFreqOffsetStdev(j.at("random_freq_offset_stdev").get<RealType>());
206 }
207 if (j.contains("phase_offset"))
208 {
209 pt.setPhaseOffset(j.at("phase_offset").get<RealType>());
210 }
211 if (j.contains("random_phase_offset_stdev"))
212 {
213 pt.setRandomPhaseOffsetStdev(j.at("random_phase_offset_stdev").get<RealType>());
214 }
215
216 if (j.contains("noise_entries"))
217 {
218 for (const auto& entry : j.at("noise_entries"))
219 {
220 pt.setAlpha(entry.at("alpha").get<RealType>(), entry.at("weight").get<RealType>());
221 }
222 }
223 }
224}
225
226namespace fers_signal
227{
228 void to_json(nlohmann::json& j, const RadarSignal& rs)
229 {
230 j = nlohmann::json{{"name", rs.getName()}, {"power", rs.getPower()}, {"carrier_frequency", rs.getCarrier()}};
231 if (dynamic_cast<const CwSignal*>(rs.getSignal()))
232 {
233 j["cw"] = nlohmann::json::object();
234 }
235 else
236 {
237 if (const auto& filename = rs.getFilename(); filename.has_value())
238 {
239 j["pulsed_from_file"] = {{"filename", *filename}};
240 }
241 else
242 {
243 throw std::logic_error("Attempted to serialize a file-based waveform named '" + rs.getName() +
244 "' without a source filename.");
245 }
246 }
247 }
248
249 void from_json(const nlohmann::json& j, std::unique_ptr<RadarSignal>& rs)
250 {
251 const auto name = j.at("name").get<std::string>();
252 const auto power = j.at("power").get<RealType>();
253 const auto carrier = j.at("carrier_frequency").get<RealType>();
254
255 if (j.contains("cw"))
256 {
257 auto cw_signal = std::make_unique<CwSignal>();
258 rs = std::make_unique<RadarSignal>(name, power, carrier, params::endTime() - params::startTime(),
259 std::move(cw_signal));
260 }
261 else if (j.contains("pulsed_from_file"))
262 {
263 const auto& pulsed_file = j.at("pulsed_from_file");
264 const auto filename = pulsed_file.value("filename", "");
265 if (filename.empty())
266 {
267 LOG(logging::Level::WARNING, "Skipping load of file-based waveform '{}': filename is empty.", name);
268 return; // rs remains nullptr
269 }
270 rs = serial::loadWaveformFromFile(name, filename, power, carrier);
271 }
272 else
273 {
274 throw std::runtime_error("Unsupported waveform type in from_json for '" + name + "'");
275 }
276 }
277}
278
279namespace antenna
280{
281 void to_json(nlohmann::json& j, const Antenna& a)
282 {
283 j = {{"name", a.getName()}, {"efficiency", a.getEfficiencyFactor()}};
284
285 if (const auto* sinc = dynamic_cast<const Sinc*>(&a))
286 {
287 j["pattern"] = "sinc";
288 j["alpha"] = sinc->getAlpha();
289 j["beta"] = sinc->getBeta();
290 j["gamma"] = sinc->getGamma();
291 }
292 else if (const auto* gaussian = dynamic_cast<const Gaussian*>(&a))
293 {
294 j["pattern"] = "gaussian";
295 j["azscale"] = gaussian->getAzimuthScale();
296 j["elscale"] = gaussian->getElevationScale();
297 }
298 else if (const auto* sh = dynamic_cast<const SquareHorn*>(&a))
299 {
300 j["pattern"] = "squarehorn";
301 j["diameter"] = sh->getDimension();
302 }
303 else if (const auto* parabolic = dynamic_cast<const Parabolic*>(&a))
304 {
305 j["pattern"] = "parabolic";
306 j["diameter"] = parabolic->getDiameter();
307 }
308 else if (const auto* xml = dynamic_cast<const XmlAntenna*>(&a))
309 {
310 j["pattern"] = "xml";
311 j["filename"] = xml->getFilename();
312 }
313 else if (const auto* h5 = dynamic_cast<const H5Antenna*>(&a))
314 {
315 j["pattern"] = "file";
316 j["filename"] = h5->getFilename();
317 }
318 else
319 {
320 j["pattern"] = "isotropic";
321 }
322 }
323
324 void from_json(const nlohmann::json& j, std::unique_ptr<Antenna>& ant)
325 {
326 const auto name = j.at("name").get<std::string>();
327 const auto pattern = j.value("pattern", "isotropic");
328
329 if (pattern == "isotropic")
330 {
331 ant = std::make_unique<Isotropic>(name);
332 }
333 else if (pattern == "sinc")
334 {
335 ant = std::make_unique<Sinc>(name, j.at("alpha").get<RealType>(), j.at("beta").get<RealType>(),
336 j.at("gamma").get<RealType>());
337 }
338 else if (pattern == "gaussian")
339 {
340 ant = std::make_unique<Gaussian>(name, j.at("azscale").get<RealType>(), j.at("elscale").get<RealType>());
341 }
342 else if (pattern == "squarehorn")
343 {
344 ant = std::make_unique<SquareHorn>(name, j.at("diameter").get<RealType>());
345 }
346 else if (pattern == "parabolic")
347 {
348 ant = std::make_unique<Parabolic>(name, j.at("diameter").get<RealType>());
349 }
350 else if (pattern == "xml")
351 {
352 const auto filename = j.value("filename", "");
353 if (filename.empty())
354 {
355 LOG(logging::Level::WARNING, "Skipping load of XML antenna '{}': filename is empty.", name);
356 return; // ant remains nullptr
357 }
358 ant = std::make_unique<XmlAntenna>(name, filename);
359 }
360 else if (pattern == "file")
361 {
362 const auto filename = j.value("filename", "");
363 if (filename.empty())
364 {
365 LOG(logging::Level::WARNING, "Skipping load of H5 antenna '{}': filename is empty.", name);
366 return; // ant remains nullptr
367 }
368 ant = std::make_unique<H5Antenna>(name, filename);
369 }
370 else
371 {
372 throw std::runtime_error("Unsupported antenna pattern in from_json: " + pattern);
373 }
374
375 ant->setEfficiencyFactor(j.value("efficiency", 1.0));
376 }
377}
378
379namespace radar
380{
381 void to_json(nlohmann::json& j, const SchedulePeriod& p) { j = {{"start", p.start}, {"end", p.end}}; }
382
383 void from_json(const nlohmann::json& j, SchedulePeriod& p)
384 {
385 j.at("start").get_to(p.start);
386 j.at("end").get_to(p.end);
387 }
388
389 void to_json(nlohmann::json& j, const Transmitter& t)
390 {
391 j = nlohmann::json{{"name", t.getName()},
392 {"waveform", t.getSignal() ? t.getSignal()->getName() : ""},
393 {"antenna", t.getAntenna() ? t.getAntenna()->getName() : ""},
394 {"timing", t.getTiming() ? t.getTiming()->getName() : ""}};
395
397 {
398 j["pulsed_mode"] = {{"prf", t.getPrf()}};
399 }
400 else
401 {
402 j["cw_mode"] = nlohmann::json::object();
403 }
404 if (!t.getSchedule().empty())
405 {
406 j["schedule"] = t.getSchedule();
407 }
408 }
409
410 void to_json(nlohmann::json& j, const Receiver& r)
411 {
412 j = nlohmann::json{{"name", r.getName()},
413 {"noise_temp", r.getNoiseTemperature()},
414 {"antenna", r.getAntenna() ? r.getAntenna()->getName() : ""},
415 {"timing", r.getTiming() ? r.getTiming()->getName() : ""},
417 {"nopropagationloss", r.checkFlag(Receiver::RecvFlag::FLAG_NOPROPLOSS)}};
418
420 {
421 j["pulsed_mode"] = {
422 {"prf", r.getWindowPrf()}, {"window_skip", r.getWindowSkip()}, {"window_length", r.getWindowLength()}};
423 }
424 else
425 {
426 j["cw_mode"] = nlohmann::json::object();
427 }
428 if (!r.getSchedule().empty())
429 {
430 j["schedule"] = r.getSchedule();
431 }
432 }
433
434 void to_json(nlohmann::json& j, const Target& t)
435 {
436 j["name"] = t.getName();
437 nlohmann::json rcs_json;
438 if (const auto* iso = dynamic_cast<const IsoTarget*>(&t))
439 {
440 rcs_json["type"] = "isotropic";
441 rcs_json["value"] = iso->getConstRcs();
442 }
443 else if (const auto* file = dynamic_cast<const FileTarget*>(&t))
444 {
445 rcs_json["type"] = "file";
446 rcs_json["filename"] = file->getFilename();
447 }
448 j["rcs"] = rcs_json;
449
450 // Serialize the fluctuation model if it exists.
451 if (const auto* model_base = t.getFluctuationModel())
452 {
453 nlohmann::json model_json;
454 if (const auto* chi_model = dynamic_cast<const RcsChiSquare*>(model_base))
455 {
456 model_json["type"] = "chisquare";
457 model_json["k"] = chi_model->getK();
458 }
459 else // Default to constant if it's not a recognized type (e.g., RcsConst)
460 {
461 model_json["type"] = "constant";
462 }
463 j["model"] = model_json;
464 }
465 }
466
467 void to_json(nlohmann::json& j, const Platform& p)
468 {
469 j = {{"name", p.getName()}, {"motionpath", *p.getMotionPath()}};
470
472 {
473 j["fixedrotation"] = *p.getRotationPath();
474 }
475 else
476 {
477 j["rotationpath"] = *p.getRotationPath();
478 }
479 }
480
481}
482
483namespace params
484{
486 {{CoordinateFrame::ENU, "ENU"},
487 {CoordinateFrame::UTM, "UTM"},
488 {CoordinateFrame::ECEF, "ECEF"}})
489
490 void to_json(nlohmann::json& j, const Parameters& p)
491 {
492 j = nlohmann::json{{"starttime", p.start},
493 {"endtime", p.end},
494 {"rate", p.rate},
495 {"c", p.c},
496 {"simSamplingRate", p.sim_sampling_rate},
497 {"adc_bits", p.adc_bits},
498 {"oversample", p.oversample_ratio}};
499
500 if (p.random_seed.has_value())
501 {
502 j["randomseed"] = p.random_seed.value();
503 }
504
505 j["origin"] = {
506 {"latitude", p.origin_latitude}, {"longitude", p.origin_longitude}, {"altitude", p.origin_altitude}};
507
508 j["coordinatesystem"] = {{"frame", p.coordinate_frame}};
509 if (p.coordinate_frame == CoordinateFrame::UTM)
510 {
511 j["coordinatesystem"]["zone"] = p.utm_zone;
512 j["coordinatesystem"]["hemisphere"] = p.utm_north_hemisphere ? "N" : "S";
513 }
514 }
515
516 void from_json(const nlohmann::json& j, Parameters& p)
517 {
518 p.start = j.at("starttime").get<RealType>();
519 p.end = j.at("endtime").get<RealType>();
520 p.rate = j.at("rate").get<RealType>();
521 p.c = j.value("c", Parameters::DEFAULT_C);
522 p.sim_sampling_rate = j.value("simSamplingRate", 1000.0);
523 p.adc_bits = j.value("adc_bits", 0);
524 p.oversample_ratio = j.value("oversample", 1);
525 p.random_seed = j.value<std::optional<unsigned>>("randomseed", std::nullopt);
526
527 const auto& origin = j.at("origin");
528 p.origin_latitude = origin.at("latitude").get<double>();
529 p.origin_longitude = origin.at("longitude").get<double>();
530 p.origin_altitude = origin.at("altitude").get<double>();
531
532 const auto& cs = j.at("coordinatesystem");
533 p.coordinate_frame = cs.at("frame").get<CoordinateFrame>();
535 {
536 p.utm_zone = cs.at("zone").get<int>();
537 p.utm_north_hemisphere = cs.at("hemisphere").get<std::string>() == "N";
538 }
539 }
540}
541
542namespace serial
543{
544 nlohmann::json world_to_json(const core::World& world)
545 {
546 nlohmann::json sim_json;
547
548 sim_json["name"] = params::params.simulation_name;
549 sim_json["parameters"] = params::params;
550
551 sim_json["waveforms"] = nlohmann::json::array();
552 for (const auto& waveform : world.getWaveforms() | std::views::values)
553 {
554 sim_json["waveforms"].push_back(*waveform);
555 }
556
557 sim_json["antennas"] = nlohmann::json::array();
558 for (const auto& antenna : world.getAntennas() | std::views::values)
559 {
560 sim_json["antennas"].push_back(*antenna);
561 }
562
563 sim_json["timings"] = nlohmann::json::array();
564 for (const auto& timing : world.getTimings() | std::views::values)
565 {
566 sim_json["timings"].push_back(*timing);
567 }
568
569 sim_json["platforms"] = nlohmann::json::array();
570 for (const auto& p : world.getPlatforms())
571 {
572 nlohmann::json plat_json = *p;
573
574 // Initialize components array to ensure it exists even if empty
575 plat_json["components"] = nlohmann::json::array();
576
577 // Add Transmitters and Monostatic Radars
578 for (const auto& t : world.getTransmitters())
579 {
580 if (t->getPlatform() == p.get())
581 {
582 if (t->getAttached() != nullptr)
583 {
584 nlohmann::json monostatic_comp;
585 monostatic_comp["name"] = t->getName();
586 monostatic_comp["waveform"] = t->getSignal() ? t->getSignal()->getName() : "";
587 monostatic_comp["antenna"] = t->getAntenna() ? t->getAntenna()->getName() : "";
588 monostatic_comp["timing"] = t->getTiming() ? t->getTiming()->getName() : "";
589
590 if (const auto* recv = dynamic_cast<const radar::Receiver*>(t->getAttached()))
591 {
592 monostatic_comp["noise_temp"] = recv->getNoiseTemperature();
593 monostatic_comp["nodirect"] = recv->checkFlag(radar::Receiver::RecvFlag::FLAG_NODIRECT);
594 monostatic_comp["nopropagationloss"] =
596
597 if (!t->getSchedule().empty())
598 {
599 monostatic_comp["schedule"] = t->getSchedule();
600 }
601
602 if (t->getMode() == radar::OperationMode::PULSED_MODE)
603 {
604 monostatic_comp["pulsed_mode"] = {{"prf", t->getPrf()},
605 {"window_skip", recv->getWindowSkip()},
606 {"window_length", recv->getWindowLength()}};
607 }
608 else
609 {
610 monostatic_comp["cw_mode"] = nlohmann::json::object();
611 }
612 }
613 plat_json["components"].push_back({{"monostatic", monostatic_comp}});
614 }
615 else
616 {
617 plat_json["components"].push_back({{"transmitter", *t}});
618 }
619 }
620 }
621
622 // Add Standalone Receivers
623 for (const auto& r : world.getReceivers())
624 {
625 if (r->getPlatform() == p.get())
626 {
627 // This must be a standalone receiver, as monostatic cases were handled above.
628 if (r->getAttached() == nullptr)
629 {
630 plat_json["components"].push_back({{"receiver", *r}});
631 }
632 }
633 }
634
635 // Add Targets
636 for (const auto& target : world.getTargets())
637 {
638 if (target->getPlatform() == p.get())
639 {
640 plat_json["components"].push_back({{"target", *target}});
641 }
642 }
643
644 sim_json["platforms"].push_back(plat_json);
645 }
646
647 return {{"simulation", sim_json}};
648 }
649
650 void json_to_world(const nlohmann::json& j, core::World& world, std::mt19937& masterSeeder)
651 {
652 // 1. Clear the existing world state. This function always performs a full
653 // replacement to ensure the C++ state is a perfect mirror of the UI state.
654 world.clear();
655
656 const auto& sim = j.at("simulation");
657
658 auto new_params = sim.at("parameters").get<params::Parameters>();
659
660 // If a random seed is present in the incoming JSON, it is used to re-seed
661 // the master generator. This is crucial for allowing the UI to control
662 // simulation reproducibility.
663 if (sim.at("parameters").contains("randomseed"))
664 {
665 params::params.random_seed = new_params.random_seed;
666 if (params::params.random_seed)
667 {
668 LOG(logging::Level::INFO, "Master seed updated from JSON to: {}", *params::params.random_seed);
669 masterSeeder.seed(*params::params.random_seed);
670 }
671 }
672
673 new_params.random_seed = params::params.random_seed;
674 params::params = new_params;
675
676 params::params.simulation_name = sim.value("name", "");
677
678 // 2. Restore assets (Waveforms, Antennas, Timings). This order is critical
679 // because platforms, which are restored next, will reference these
680 // assets by name. The assets must exist before they can be linked.
681 if (sim.contains("waveforms"))
682 {
683 for (auto waveforms = sim.at("waveforms").get<std::vector<std::unique_ptr<fers_signal::RadarSignal>>>();
684 auto& waveform : waveforms)
685 {
686 // Only add valid waveforms. If filename was empty, waveform is nullptr.
687 if (waveform)
688 {
689 world.add(std::move(waveform));
690 }
691 }
692 }
693
694 if (sim.contains("antennas"))
695 {
696 for (auto antennas = sim.at("antennas").get<std::vector<std::unique_ptr<antenna::Antenna>>>();
697 auto& antenna : antennas)
698 {
699 // Only add valid antennas.
700 if (antenna)
701 {
702 world.add(std::move(antenna));
703 }
704 }
705 }
706
707 if (sim.contains("timings"))
708 {
709 for (const auto& timing_json : sim.at("timings"))
710 {
711 auto name = timing_json.at("name").get<std::string>();
712 auto timing_obj = std::make_unique<timing::PrototypeTiming>(name);
713 from_json(timing_json, *timing_obj);
714 world.add(std::move(timing_obj));
715 }
716 }
717
718 // 3. Restore platforms and their components.
719 if (sim.contains("platforms"))
720 {
721 for (const auto& plat_json : sim.at("platforms"))
722 {
723 auto name = plat_json.at("name").get<std::string>();
724 auto plat = std::make_unique<radar::Platform>(name);
725
726 // Paths
727 if (plat_json.contains("motionpath"))
728 {
729 auto path = std::make_unique<math::Path>();
730 from_json(plat_json.at("motionpath"), *path);
731 plat->setMotionPath(std::move(path));
732 }
733 if (plat_json.contains("rotationpath"))
734 {
735 auto rot_path = std::make_unique<math::RotationPath>();
736 from_json(plat_json.at("rotationpath"), *rot_path);
737 plat->setRotationPath(std::move(rot_path));
738 }
739 else if (plat_json.contains("fixedrotation"))
740 {
741 // This logic reconstructs a constant-rate rotation path from the
742 // JSON representation that corresponds to the <fixedrotation> XML element.
743 auto rot_path = std::make_unique<math::RotationPath>();
744 const auto& fixed_json = plat_json.at("fixedrotation");
745 const RealType start_az_deg = fixed_json.at("startazimuth").get<RealType>();
746 const RealType start_el_deg = fixed_json.at("startelevation").get<RealType>();
747 const RealType rate_az_deg_s = fixed_json.at("azimuthrate").get<RealType>();
748 const RealType rate_el_deg_s = fixed_json.at("elevationrate").get<RealType>();
749
750 math::RotationCoord start, rate;
751 start.azimuth = (90.0 - start_az_deg) * (PI / 180.0);
752 start.elevation = start_el_deg * (PI / 180.0);
753 rate.azimuth = -rate_az_deg_s * (PI / 180.0);
754 rate.elevation = rate_el_deg_s * (PI / 180.0);
755 rot_path->setConstantRate(start, rate);
756 rot_path->finalize();
757 plat->setRotationPath(std::move(rot_path));
758 }
759
760 // Components - Strict array format
761 if (plat_json.contains("components"))
762 {
763 for (const auto& comp_json_outer : plat_json.at("components"))
764 {
765 if (comp_json_outer.contains("transmitter"))
766 {
767 const auto& comp_json = comp_json_outer.at("transmitter");
768
769 // --- Dependency Check ---
770 // Validate Waveform and Timing existence before creation to prevent core crashes.
771 const auto wave_name = comp_json.value("waveform", "");
772 const auto timing_name = comp_json.value("timing", "");
773 const auto antenna_name = comp_json.value("antenna", "");
774
775 if (wave_name.empty() || !world.findWaveform(wave_name))
776 {
778 "Skipping Transmitter '{}': Missing or invalid waveform '{}'.",
779 comp_json.value("name", "Unnamed"), wave_name);
780 continue;
781 }
782 if (timing_name.empty() || !world.findTiming(timing_name))
783 {
785 "Skipping Transmitter '{}': Missing or invalid timing source '{}'.",
786 comp_json.value("name", "Unnamed"), timing_name);
787 continue;
788 }
789 if (antenna_name.empty() || !world.findAntenna(antenna_name))
790 {
792 "Skipping Transmitter '{}': Missing or invalid antenna '{}'.",
793 comp_json.value("name", "Unnamed"), antenna_name);
794 continue;
795 }
796
798 if (comp_json.contains("pulsed_mode"))
799 {
801 }
802 else if (comp_json.contains("cw_mode"))
803 {
805 }
806 else
807 {
808 throw std::runtime_error("Transmitter component '" +
809 comp_json.value("name", "Unnamed") +
810 "' must have a 'pulsed_mode' or 'cw_mode' block.");
811 }
812
813 auto trans = std::make_unique<radar::Transmitter>(plat.get(),
814 comp_json.value("name", "Unnamed"), mode);
815 if (mode == radar::OperationMode::PULSED_MODE && comp_json.contains("pulsed_mode"))
816 {
817 trans->setPrf(comp_json.at("pulsed_mode").value("prf", 0.0));
818 }
819
820 trans->setWave(world.findWaveform(wave_name));
821 trans->setAntenna(world.findAntenna(antenna_name));
822
823 if (const auto timing_proto = world.findTiming(timing_name))
824 {
825 const auto timing = std::make_shared<timing::Timing>(timing_name, masterSeeder());
826 timing->initializeModel(timing_proto);
827 trans->setTiming(timing);
828 }
829
830 if (comp_json.contains("schedule"))
831 {
832 auto raw = comp_json.at("schedule").get<std::vector<radar::SchedulePeriod>>();
833 RealType pri = 0.0;
835 {
836 pri = 1.0 / trans->getPrf();
837 }
838 trans->setSchedule(radar::processRawSchedule(
839 std::move(raw), trans->getName(), mode == radar::OperationMode::PULSED_MODE, pri));
840 }
841
842 world.add(std::move(trans));
843 }
844 else if (comp_json_outer.contains("receiver"))
845 {
846 const auto& comp_json = comp_json_outer.at("receiver");
847
848 // --- Dependency Check ---
849 // Receiver strictly requires a Timing source.
850 const auto timing_name = comp_json.value("timing", "");
851 const auto antenna_name = comp_json.value("antenna", "");
852
853 if (timing_name.empty() || !world.findTiming(timing_name))
854 {
856 "Skipping Receiver '{}': Missing or invalid timing source '{}'.",
857 comp_json.value("name", "Unnamed"), timing_name);
858 continue;
859 }
860
861 if (!antenna_name.empty() && !world.findAntenna(antenna_name))
862 {
863 LOG(logging::Level::WARNING, "Skipping Receiver '{}': Missing or invalid antenna '{}'.",
864 comp_json.value("name", "Unnamed"), antenna_name);
865 continue;
866 }
867
869 if (comp_json.contains("pulsed_mode"))
870 {
872 }
873 else if (comp_json.contains("cw_mode"))
874 {
876 }
877 else
878 {
879 throw std::runtime_error("Receiver component '" + comp_json.value("name", "Unnamed") +
880 "' must have a 'pulsed_mode' or 'cw_mode' block.");
881 }
882
883 auto recv = std::make_unique<radar::Receiver>(
884 plat.get(), comp_json.value("name", "Unnamed"), masterSeeder(), mode);
885 if (mode == radar::OperationMode::PULSED_MODE && comp_json.contains("pulsed_mode"))
886 {
887 const auto& mode_json = comp_json.at("pulsed_mode");
888 recv->setWindowProperties(mode_json.value("window_length", 0.0),
889 mode_json.value("prf", 0.0),
890 mode_json.value("window_skip", 0.0));
891 }
892
893 recv->setNoiseTemperature(comp_json.value("noise_temp", 0.0));
894
895 recv->setAntenna(world.findAntenna(antenna_name));
896
897 if (const auto timing_proto = world.findTiming(timing_name))
898 {
899 const auto timing = std::make_shared<timing::Timing>(timing_name, masterSeeder());
900 timing->initializeModel(timing_proto);
901 recv->setTiming(timing);
902 }
903
904 if (comp_json.value("nodirect", false))
905 {
907 }
908 if (comp_json.value("nopropagationloss", false))
909 {
911 }
912
913 if (comp_json.contains("schedule"))
914 {
915 auto raw = comp_json.at("schedule").get<std::vector<radar::SchedulePeriod>>();
916 RealType pri = 0.0;
918 {
919 pri = 1.0 / recv->getWindowPrf();
920 }
921 recv->setSchedule(radar::processRawSchedule(
922 std::move(raw), recv->getName(), mode == radar::OperationMode::PULSED_MODE, pri));
923 }
924
925 world.add(std::move(recv));
926 }
927 if (comp_json_outer.contains("target"))
928 {
929 const auto& comp_json = comp_json_outer.at("target");
930 const auto& rcs_json = comp_json.at("rcs");
931 const auto rcs_type = rcs_json.at("type").get<std::string>();
932 std::unique_ptr<radar::Target> target_obj;
933
934 if (rcs_type == "isotropic")
935 {
936 target_obj =
937 radar::createIsoTarget(plat.get(), comp_json.at("name").get<std::string>(),
938 rcs_json.at("value").get<RealType>(), masterSeeder());
939 }
940 else if (rcs_type == "file")
941 {
942 const auto filename = rcs_json.value("filename", "");
943 if (filename.empty())
944 {
946 "Skipping load of file target '{}': RCS filename is empty.",
947 comp_json.value("name", "Unknown"));
948 continue;
949 }
950 target_obj = radar::createFileTarget(
951 plat.get(), comp_json.at("name").get<std::string>(), filename, masterSeeder());
952 }
953 else
954 {
955 throw std::runtime_error("Unsupported target RCS type: " + rcs_type);
956 }
957 world.add(std::move(target_obj));
958
959 // After creating the target, check for and apply the fluctuation model.
960 if (comp_json.contains("model"))
961 {
962 const auto& model_json = comp_json.at("model");
963 if (const auto model_type = model_json.at("type").get<std::string>();
964 model_type == "chisquare" || model_type == "gamma")
965 {
966 auto model = std::make_unique<radar::RcsChiSquare>(
967 world.getTargets().back()->getRngEngine(), model_json.at("k").get<RealType>());
968 world.getTargets().back()->setFluctuationModel(std::move(model));
969 }
970 // "constant" is the default, so no action is needed if that's the type.
971 }
972 }
973 else if (comp_json_outer.contains("monostatic"))
974 {
975 // This block reconstructs the internal C++ representation of a
976 // monostatic radar (a linked Transmitter and Receiver) from the
977 // single 'monostatic' component in the JSON.
978 const auto& comp_json = comp_json_outer.at("monostatic");
979
980 // --- Dependency Check ---
981 const auto wave_name = comp_json.value("waveform", "");
982 const auto timing_name = comp_json.value("timing", "");
983 const auto antenna_name = comp_json.value("antenna", "");
984
985 if (wave_name.empty() || !world.findWaveform(wave_name))
986 {
988 "Skipping Monostatic '{}': Missing or invalid waveform '{}'.",
989 comp_json.value("name", "Unnamed"), wave_name);
990 continue;
991 }
992 if (timing_name.empty() || !world.findTiming(timing_name))
993 {
995 "Skipping Monostatic '{}': Missing or invalid timing source '{}'.",
996 comp_json.value("name", "Unnamed"), timing_name);
997 continue;
998 }
999 if (antenna_name.empty() || !world.findAntenna(antenna_name))
1000 {
1002 "Skipping Monostatic '{}': Missing or invalid antenna '{}'.",
1003 comp_json.value("name", "Unnamed"), antenna_name);
1004 continue;
1005 }
1006
1008 if (comp_json.contains("pulsed_mode"))
1009 {
1011 }
1012 else if (comp_json.contains("cw_mode"))
1013 {
1015 }
1016 else
1017 {
1018 throw std::runtime_error("Monostatic component '" + comp_json.value("name", "Unnamed") +
1019 "' must have a 'pulsed_mode' or 'cw_mode' block.");
1020 }
1021
1022 // Transmitter part
1023 auto trans = std::make_unique<radar::Transmitter>(plat.get(),
1024 comp_json.value("name", "Unnamed"), mode);
1025 if (mode == radar::OperationMode::PULSED_MODE && comp_json.contains("pulsed_mode"))
1026 {
1027 trans->setPrf(comp_json.at("pulsed_mode").value("prf", 0.0));
1028 }
1029
1030 trans->setWave(world.findWaveform(wave_name));
1031 trans->setAntenna(world.findAntenna(antenna_name));
1032 const auto tx_timing_proto = world.findTiming(timing_name);
1033 if (tx_timing_proto)
1034 {
1035 const auto tx_timing = std::make_shared<timing::Timing>(timing_name, masterSeeder());
1036 tx_timing->initializeModel(tx_timing_proto);
1037 trans->setTiming(tx_timing);
1038 }
1039
1040 // Receiver part
1041 auto recv = std::make_unique<radar::Receiver>(
1042 plat.get(), comp_json.value("name", "Unnamed"), masterSeeder(), mode);
1043 if (mode == radar::OperationMode::PULSED_MODE && comp_json.contains("pulsed_mode"))
1044 {
1045 const auto& mode_json = comp_json.at("pulsed_mode");
1046 recv->setWindowProperties(mode_json.value("window_length", 0.0),
1047 trans->getPrf(), // Use transmitter's PRF
1048 mode_json.value("window_skip", 0.0));
1049 }
1050 recv->setNoiseTemperature(comp_json.value("noise_temp", 0.0));
1051
1052 recv->setAntenna(world.findAntenna(antenna_name));
1053 const auto rx_timing_proto = world.findTiming(timing_name);
1054 if (rx_timing_proto)
1055 {
1056 const auto rx_timing = std::make_shared<timing::Timing>(timing_name, masterSeeder());
1057 rx_timing->initializeModel(rx_timing_proto);
1058 recv->setTiming(rx_timing);
1059 }
1060
1061 if (comp_json.value("nodirect", false))
1062 {
1064 }
1065 if (comp_json.value("nopropagationloss", false))
1066 {
1068 }
1069 if (comp_json.contains("schedule"))
1070 {
1071 auto raw = comp_json.at("schedule").get<std::vector<radar::SchedulePeriod>>();
1072 RealType pri = 0.0;
1074 {
1075 pri = 1.0 / trans->getPrf();
1076 }
1077
1078 // Process once, apply to both
1079 auto processed_schedule = radar::processRawSchedule(
1080 std::move(raw), trans->getName(), mode == radar::OperationMode::PULSED_MODE, pri);
1081
1082 trans->setSchedule(processed_schedule);
1083 recv->setSchedule(processed_schedule);
1084 }
1085
1086 // Link them and add to world
1087 trans->setAttached(recv.get());
1088 recv->setAttached(trans.get());
1089 world.add(std::move(trans));
1090 world.add(std::move(recv));
1091 }
1092 }
1093 }
1094
1095 world.add(std::move(plat));
1096 }
1097 }
1098
1099 // 4. Finalize world state after all objects are loaded.
1100
1101 // Prepare CW receiver buffers before starting simulation
1102 const RealType start_time = params::startTime();
1103 const RealType end_time = params::endTime();
1104 const RealType dt_sim = 1.0 / (params::rate() * params::oversampleRatio());
1105 const auto num_samples = static_cast<size_t>(std::ceil((end_time - start_time) / dt_sim));
1106
1107 for (const auto& receiver : world.getReceivers())
1108 {
1109 if (receiver->getMode() == radar::OperationMode::CW_MODE)
1110 {
1111 receiver->prepareCwData(num_samples);
1112 }
1113 }
1114
1115 // Schedule initial events after all objects are loaded.
1116 world.scheduleInitialEvents();
1117 }
1118}
Header file defining various types of antennas and their gain patterns.
Abstract base class representing an antenna.
RealType getEfficiencyFactor() const noexcept
Retrieves the efficiency factor 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 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:38
void scheduleInitialEvents()
Populates the event queue with the initial events for the simulation.
Definition world.cpp:98
timing::PrototypeTiming * findTiming(const std::string &name)
Finds a timing source by name.
Definition world.cpp:80
void add(std::unique_ptr< radar::Platform > plat) noexcept
Adds a radar platform to the simulation world.
Definition world.cpp:35
antenna::Antenna * findAntenna(const std::string &name)
Finds an antenna by name.
Definition world.cpp:75
const std::vector< std::unique_ptr< radar::Target > > & getTargets() const noexcept
Retrieves the list of radar targets.
Definition world.h:143
const std::unordered_map< std::string, std::unique_ptr< fers_signal::RadarSignal > > & getWaveforms() const noexcept
Retrieves the map of radar signals (waveforms).
Definition world.h:173
fers_signal::RadarSignal * findWaveform(const std::string &name)
Finds a radar signal by name.
Definition world.cpp:70
void clear() noexcept
Clears all objects and assets from the simulation world.
Definition world.cpp:85
const std::vector< std::unique_ptr< radar::Platform > > & getPlatforms() const noexcept
Retrieves the list of platforms.
Definition world.h:133
const std::vector< std::unique_ptr< radar::Transmitter > > & getTransmitters() const noexcept
Retrieves the list of radar transmitters.
Definition world.h:163
const std::unordered_map< std::string, std::unique_ptr< timing::PrototypeTiming > > & getTimings() const noexcept
Retrieves the map of timing prototypes.
Definition world.h:193
const std::vector< std::unique_ptr< radar::Receiver > > & getReceivers() const noexcept
Retrieves the list of radar receivers.
Definition world.h:153
const std::unordered_map< std::string, std::unique_ptr< antenna::Antenna > > & getAntennas() const noexcept
Retrieves the map of antennas.
Definition world.h:183
Class representing a radar signal with associated properties.
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:158
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:26
void finalize()
Finalizes the path, preparing it for interpolation.
Definition path.cpp:141
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:210
Isotropic radar target.
Definition target.h:172
const std::string & getName() const noexcept
Retrieves the name of the object.
Definition object.h:68
Represents a simulation platform with motion and rotation paths.
Definition platform.h:31
math::Path * getMotionPath() const noexcept
Gets the motion path of the platform.
Definition platform.h:59
const std::string & getName() const noexcept
Gets the name of the platform.
Definition platform.h:89
math::RotationPath * getRotationPath() const noexcept
Gets the rotation path of the platform.
Definition platform.h:66
const antenna::Antenna * getAntenna() const noexcept
Gets the antenna associated with this radar.
Definition radar_obj.h:78
std::shared_ptr< timing::Timing > getTiming() const
Retrieves the timing source for the radar.
Definition radar_obj.cpp:66
Chi-square distributed RCS model.
Definition target.h:81
Manages radar signal reception and response processing.
Definition receiver.h:36
bool checkFlag(RecvFlag flag) const noexcept
Checks if a specific flag is set.
Definition receiver.h:86
const std::vector< SchedulePeriod > & getSchedule() const noexcept
Retrieves the list of active reception periods.
Definition receiver.h:255
RealType getNoiseTemperature() const noexcept
Retrieves the noise temperature of the receiver.
Definition receiver.h:93
OperationMode getMode() const noexcept
Gets the operational mode of the receiver.
Definition receiver.h:151
RealType getWindowPrf() const noexcept
Retrieves the pulse repetition frequency (PRF) of the radar window.
Definition receiver.h:107
RealType getWindowSkip() const noexcept
Retrieves the window skip time.
Definition receiver.h:114
RealType getWindowLength() const noexcept
Retrieves the radar window length.
Definition receiver.h:100
Base class for radar targets.
Definition target.h:117
const RcsModel * getFluctuationModel() const
Gets the RCS fluctuation model.
Definition target.h:159
Represents a radar transmitter system.
Definition transmitter.h:32
RealType getPrf() const noexcept
Retrieves the pulse repetition frequency (PRF).
Definition transmitter.h:61
fers_signal::RadarSignal * getSignal() const noexcept
Retrieves the radar signal currently being transmitted.
Definition transmitter.h:68
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:75
Manages timing properties such as frequency, offsets, and synchronization.
void setSyncOnPulse() noexcept
Enables synchronization on pulse.
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.
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.
std::optional< RealType > getPhaseOffset() const noexcept
Gets the phase offset.
void copyAlphas(std::vector< RealType > &alphas, std::vector< RealType > &weights) const noexcept
Copies the alphas and weights vectors.
std::optional< RealType > getFreqOffset() const noexcept
Gets the frequency offset.
double RealType
Type for real numbers.
Definition config.h:27
constexpr RealType PI
Mathematical constant π (pi).
Definition config.h:43
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:97
RealType rate() noexcept
Get the rendering sample rate.
Definition parameters.h:109
RealType startTime() noexcept
Get the start time for the simulation.
Definition parameters.h:91
unsigned oversampleRatio() noexcept
Get the oversampling ratio.
Definition parameters.h:139
CoordinateFrame
Defines the coordinate systems supported for scenario definition.
Definition parameters.h:29
@ 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"}}) void to_json(nlohmann
void from_json(const nlohmann::json &j, Parameters &p)
Parameters params
Definition parameters.h:73
void to_json(nlohmann::json &j, const SchedulePeriod &p)
std::unique_ptr< Target > createIsoTarget(Platform *platform, std::string name, RealType rcs, unsigned seed)
Creates an isotropic target.
Definition target.h:265
void from_json(const nlohmann::json &j, SchedulePeriod &p)
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.
std::unique_ptr< Target > createFileTarget(Platform *platform, std::string name, const std::string &filename, unsigned seed)
Creates a file-based target.
Definition target.h:279
OperationMode
Defines the operational mode of a radar component.
Definition radar_obj.h:36
@ PULSED_MODE
The component operates in a pulsed mode.
@ CW_MODE
The component operates in a continuous-wave mode.
void json_to_world(const nlohmann::json &j, core::World &world, std::mt19937 &masterSeeder)
Deserializes a nlohmann::json object and reconstructs the simulation world.
std::unique_ptr< RadarSignal > loadWaveformFromFile(const std::string &name, const std::string &filename, const RealType power, const RealType carrierFreq)
Loads a radar waveform from a file and returns a RadarSignal object.
nlohmann::json world_to_json(const core::World &world)
Serializes the entire simulation world into a nlohmann::json object.
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.
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:40
RealType rate
Rendering sample rate.
Definition parameters.h:57
double origin_longitude
Geodetic origin longitude.
Definition parameters.h:52
RealType start
Start time for the simulation.
Definition parameters.h:45
double origin_altitude
Geodetic origin altitude (in meters)
Definition parameters.h:53
CoordinateFrame coordinate_frame
Scenario coordinate frame.
Definition parameters.h:54
RealType end
End time for the simulation.
Definition parameters.h:46
int utm_zone
UTM zone (1-60), if applicable.
Definition parameters.h:55
unsigned oversample_ratio
Oversampling ratio.
Definition parameters.h:63
std::optional< unsigned > random_seed
Random seed for simulation.
Definition parameters.h:58
RealType sim_sampling_rate
Temporal sampling rate (Hz) that determines time-step resolution for radar pulse simulation.
Definition parameters.h:47
std::string simulation_name
The name of the simulation, from the XML.
Definition parameters.h:62
RealType c
Speed of light (modifiable)
Definition parameters.h:43
static constexpr RealType DEFAULT_C
Speed of light (m/s)
Definition parameters.h:41
unsigned adc_bits
ADC quantization bits.
Definition parameters.h:59
bool utm_north_hemisphere
UTM hemisphere, if applicable.
Definition parameters.h:56
double origin_latitude
Geodetic origin latitude.
Definition parameters.h:51
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.