FERS 1.0.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
xml_parser_utils.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2//
3// Copyright (c) 2026-present FERS Contributors (see AUTHORS.md).
4//
5// See the GNU GPLv2 LICENSE file in the FERS project root for more information.
6
7#include "xml_parser_utils.h"
8
9#include <GeographicLib/UTMUPS.hpp>
10#include <cmath>
11#include <filesystem>
12#include <format>
13#include <limits>
14#include <string_view>
15
17#include "core/config.h"
18#include "core/logging.h"
19#include "core/world.h"
20#include "fers_xml_dtd.h"
21#include "fers_xml_xsd.h"
22#include "math/coord.h"
23#include "math/geometry_ops.h"
24#include "math/path.h"
25#include "math/rotation_path.h"
26#include "radar/platform.h"
27#include "radar/radar_obj.h"
28#include "radar/receiver.h"
29#include "radar/target.h"
30#include "radar/transmitter.h"
34#include "signal/radar_signal.h"
36#include "timing/timing.h"
37
38namespace fs = std::filesystem;
39
41{
42 namespace
43 {
44 [[nodiscard]] unsigned next_seed(std::mt19937& master_seeder)
45 {
46 static_assert(std::mt19937::max() <= std::numeric_limits<unsigned>::max(),
47 "std::mt19937 output must fit into unsigned seeds.");
48 return static_cast<unsigned>(master_seeder());
49 }
50 }
51
52 RealType get_child_real_type(const XmlElement& element, const std::string& elementName)
53 {
54 const std::string text = element.childElement(elementName, 0).getText();
55 if (text.empty())
56 {
57 throw XmlException("Element " + elementName + " is empty!");
58 }
59 return std::stod(text);
60 }
61
62 bool get_attribute_bool(const XmlElement& element, const std::string& attributeName, const bool defaultVal)
63 {
64 try
65 {
66 return XmlElement::getSafeAttribute(element, attributeName) == "true";
67 }
68 catch (const XmlException&)
69 {
70 LOG(logging::Level::WARNING, "Failed to get boolean value for attribute '{}'. Defaulting to {}.",
71 attributeName, defaultVal);
72 return defaultVal;
73 }
74 }
75
76 SimId assign_id_from_attribute(const std::string& owner, ObjectType type)
77 {
78 const SimId id = SimIdGenerator::instance().generateId(type);
79 LOG(logging::Level::TRACE, "Assigned ID {} to {} (generated)", id, owner);
80 return id;
81 }
82
83 SimId resolve_reference_id(const XmlElement& element, const std::string& attributeName, const std::string& owner,
84 const std::unordered_map<std::string, SimId>& name_map)
85 {
86 const std::string value = XmlElement::getSafeAttribute(element, attributeName);
87 if (value.empty())
88 {
89 throw XmlException("Missing " + attributeName + " for " + owner + ".");
90 }
91 const auto it = name_map.find(value);
92 if (it != name_map.end())
93 {
94 return it->second;
95 }
96 throw XmlException("Unknown " + attributeName + " '" + value + "' for " + owner + ".");
97 }
98
99 std::vector<radar::SchedulePeriod> parseSchedule(const XmlElement& parent, const std::string& parentName,
100 const bool isPulsed, const RealType pri)
101 {
102 std::vector<radar::SchedulePeriod> raw_periods;
103 if (const XmlElement schedule_element = parent.childElement("schedule", 0); schedule_element.isValid())
104 {
105 unsigned p_idx = 0;
106 while (true)
107 {
108 XmlElement period_element = schedule_element.childElement("period", p_idx++);
109 if (!period_element.isValid())
110 {
111 break;
112 }
113 try
114 {
115 const RealType start = std::stod(XmlElement::getSafeAttribute(period_element, "start"));
116 const RealType end = std::stod(XmlElement::getSafeAttribute(period_element, "end"));
117 raw_periods.push_back({start, end});
118 }
119 catch (const std::exception& e)
120 {
121 LOG(logging::Level::WARNING, "Failed to parse schedule period for '{}': {}", parentName, e.what());
122 }
123 }
124 }
125 return radar::processRawSchedule(std::move(raw_periods), parentName, isPulsed, pri);
126 }
127
128 void parseParameters(const XmlElement& parameters, params::Parameters& params_out)
129 {
130 params_out.start = get_child_real_type(parameters, "starttime");
131 params_out.end = get_child_real_type(parameters, "endtime");
132 LOG(logging::Level::INFO, "Simulation time set from {:.5f} to {:.5f} seconds", params_out.start,
133 params_out.end);
134
135 params_out.rate = get_child_real_type(parameters, "rate");
136 if (params_out.rate <= 0)
137 {
138 throw std::runtime_error("Sampling rate must be > 0");
139 }
140 LOG(logging::Level::DEBUG, "Sample rate set to: {:.5f}", params_out.rate);
141
142 const auto parse_unsigned_parameter = [&](const std::string_view param_name, const RealType raw_value)
143 {
144 if (!std::isfinite(raw_value))
145 {
146 throw XmlException(std::format("Parameter '{}' must be finite.", param_name));
147 }
148 if (raw_value < 0.0)
149 {
150 throw XmlException(std::format("Parameter '{}' must be non-negative.", param_name));
151 }
152
153 const RealType floored_value = std::floor(raw_value);
154 if (floored_value > static_cast<RealType>(std::numeric_limits<unsigned>::max()))
155 {
156 throw XmlException(std::format("Parameter '{}' exceeds the supported unsigned range.", param_name));
157 }
158
159 return static_cast<unsigned>(floored_value);
160 };
161
162 auto set_optional_real_parameter = [&](const std::string& param_name, const RealType default_value, auto setter)
163 {
164 if (!parameters.childElement(param_name, 0).isValid())
165 {
166 LOG(logging::Level::WARNING, "Failed to set parameter {}. Using default value. {}", param_name,
167 default_value);
168 return;
169 }
170
171 setter(get_child_real_type(parameters, param_name));
172 };
173
174 auto set_optional_unsigned_parameter =
175 [&](const std::string& param_name, const unsigned default_value, auto setter)
176 {
177 if (!parameters.childElement(param_name, 0).isValid())
178 {
179 LOG(logging::Level::WARNING, "Failed to set parameter {}. Using default value. {}", param_name,
180 default_value);
181 return;
182 }
183
184 setter(parse_unsigned_parameter(param_name, get_child_real_type(parameters, param_name)));
185 };
186
187 set_optional_real_parameter("c", params::Parameters::DEFAULT_C,
188 [&](const RealType value)
189 {
190 params_out.c = value;
191 LOG(logging::Level::INFO, "Propagation speed (c) set to: {:.5f}", value);
192 });
193
194 set_optional_real_parameter("simSamplingRate", 1000.0,
195 [&](const RealType value)
196 {
197 params_out.sim_sampling_rate = value;
198 LOG(logging::Level::DEBUG, "Simulation sampling rate set to: {:.5f} Hz", value);
199 });
200
201 if (parameters.childElement("randomseed", 0).isValid())
202 {
203 const auto seed = parse_unsigned_parameter("randomseed", get_child_real_type(parameters, "randomseed"));
204 params_out.random_seed = seed;
205 LOG(logging::Level::DEBUG, "Random seed set to: {}", seed);
206 }
207
208 set_optional_unsigned_parameter("adc_bits", 0,
209 [&](const unsigned value)
210 {
211 params_out.adc_bits = value;
212 LOG(logging::Level::DEBUG, "ADC quantization bits set to: {}", value);
213 });
214
215 set_optional_unsigned_parameter("oversample", 1,
216 [&](const unsigned value)
217 {
218 if (value == 0)
219 {
220 throw std::runtime_error("Oversample ratio must be >= 1");
221 }
222 params_out.oversample_ratio = value;
223 LOG(logging::Level::DEBUG, "Oversampling enabled with ratio: {}", value);
224 });
225
226 try
227 {
228 const auto unit_token = parameters.childElement("rotationangleunit", 0).getText();
229 if (!unit_token.empty())
230 {
231 if (const auto unit = params::rotationAngleUnitFromToken(unit_token))
232 {
233 params_out.rotation_angle_unit = *unit;
234 }
235 else
236 {
237 throw XmlException("Unsupported rotation angle unit '" + unit_token + "'.");
238 }
239 }
240 }
241 catch (const XmlException&)
242 {
243 }
244
245 bool origin_set = false;
246 if (const XmlElement origin_element = parameters.childElement("origin", 0); origin_element.isValid())
247 {
248 try
249 {
250 params_out.origin_latitude = std::stod(XmlElement::getSafeAttribute(origin_element, "latitude"));
251 params_out.origin_longitude = std::stod(XmlElement::getSafeAttribute(origin_element, "longitude"));
252 params_out.origin_altitude = std::stod(XmlElement::getSafeAttribute(origin_element, "altitude"));
253 origin_set = true;
254 LOG(logging::Level::INFO, "Origin set to lat: {}, lon: {}, alt: {}", params_out.origin_latitude,
255 params_out.origin_longitude, params_out.origin_altitude);
256 }
257 catch (const std::exception& e)
258 {
259 LOG(logging::Level::WARNING, "Could not parse origin from XML, using defaults. Error: {}", e.what());
260 }
261 }
262
263 if (const XmlElement cs_element = parameters.childElement("coordinatesystem", 0); cs_element.isValid())
264 {
265 try
266 {
267 const std::string frame_str = XmlElement::getSafeAttribute(cs_element, "frame");
268
269 if (frame_str == "UTM")
270 {
272 params_out.utm_zone = std::stoi(XmlElement::getSafeAttribute(cs_element, "zone"));
273 const std::string hem_str = XmlElement::getSafeAttribute(cs_element, "hemisphere");
274
275 if (params_out.utm_zone < GeographicLib::UTMUPS::MINUTMZONE ||
276 params_out.utm_zone > GeographicLib::UTMUPS::MAXUTMZONE)
277 {
278 throw XmlException("UTM zone " + std::to_string(params_out.utm_zone) +
279 " is invalid; must be in [1, 60].");
280 }
281 if (hem_str == "N" || hem_str == "n")
282 {
283 params_out.utm_north_hemisphere = true;
284 }
285 else if (hem_str == "S" || hem_str == "s")
286 {
287 params_out.utm_north_hemisphere = false;
288 }
289 else
290 {
291 throw XmlException("UTM hemisphere '" + hem_str + "' is invalid; must be 'N' or 'S'.");
292 }
293 LOG(logging::Level::INFO, "Coordinate system set to UTM, zone {}{}", params_out.utm_zone,
294 params_out.utm_north_hemisphere ? 'N' : 'S');
295 }
296 else if (frame_str == "ECEF")
297 {
299 LOG(logging::Level::INFO, "Coordinate system set to ECEF.");
300 }
301 else if (frame_str == "ENU")
302 {
304 if (!origin_set)
305 {
307 "ENU frame specified but no <origin> tag found. Using default origin at UCT.");
308 }
309 LOG(logging::Level::INFO, "Coordinate system set to ENU local tangent plane.");
310 }
311 else
312 {
313 throw XmlException("Unsupported coordinate frame: " + frame_str);
314 }
315 }
316 catch (const std::exception& e)
317 {
318 LOG(logging::Level::WARNING, "Could not parse <coordinatesystem> from XML: {}. Defaulting to ENU.",
319 e.what());
321 params_out.utm_zone = 0;
322 params_out.utm_north_hemisphere = true;
323 }
324 }
325 }
326
327 void parseWaveform(const XmlElement& waveform, ParserContext& ctx)
328 {
329 const std::string name = XmlElement::getSafeAttribute(waveform, "name");
330 const SimId id = assign_id_from_attribute("waveform '" + name + "'", ObjectType::Waveform);
331
332 const auto power = get_child_real_type(waveform, "power");
333 const auto carrier = get_child_real_type(waveform, "carrier_frequency");
334
335 if (const XmlElement pulsed_file = waveform.childElement("pulsed_from_file", 0); pulsed_file.isValid())
336 {
337 const std::string filename_str = XmlElement::getSafeAttribute(pulsed_file, "filename");
338 fs::path pulse_path(filename_str);
339
340 if (!fs::exists(pulse_path))
341 {
342 pulse_path = ctx.base_dir / filename_str;
343 }
344
345 // Defer to dependency-injected file loader
346 auto wave = ctx.loaders.loadWaveform(name, pulse_path, power, carrier, id);
347 ctx.world->add(std::move(wave));
348 }
349 else if (waveform.childElement("cw", 0).isValid())
350 {
351 auto cw_signal = std::make_unique<fers_signal::CwSignal>();
352 auto wave = std::make_unique<fers_signal::RadarSignal>(
353 name, power, carrier, ctx.parameters.end - ctx.parameters.start, std::move(cw_signal), id);
354 ctx.world->add(std::move(wave));
355 }
356 else
357 {
358 LOG(logging::Level::FATAL, "Unsupported waveform type for '{}'", name);
359 throw XmlException("Unsupported waveform type for '" + name + "'");
360 }
361 }
362
364 {
365 const std::string name = XmlElement::getSafeAttribute(timing, "name");
366 const SimId id = assign_id_from_attribute("timing '" + name + "'", ObjectType::Timing);
367 const RealType freq = get_child_real_type(timing, "frequency");
368 auto timing_obj = std::make_unique<timing::PrototypeTiming>(name, id);
369
370 timing_obj->setFrequency(freq);
371
372 unsigned noise_index = 0;
373 while (true)
374 {
375 XmlElement noise_element = timing.childElement("noise_entry", noise_index++);
376 if (!noise_element.isValid())
377 {
378 break;
379 }
380
381 timing_obj->setAlpha(get_child_real_type(noise_element, "alpha"),
382 get_child_real_type(noise_element, "weight"));
383 }
384
385 try
386 {
387 timing_obj->setFreqOffset(get_child_real_type(timing, "freq_offset"));
388 }
389 catch (XmlException&)
390 {
391 LOG(logging::Level::WARNING, "Clock section '{}' does not specify frequency offset.", name);
392 }
393
394 try
395 {
396 timing_obj->setRandomFreqOffsetStdev(get_child_real_type(timing, "random_freq_offset_stdev"));
397 }
398 catch (XmlException&)
399 {
400 LOG(logging::Level::WARNING, "Clock section '{}' does not specify random frequency offset.", name);
401 }
402
403 try
404 {
405 timing_obj->setPhaseOffset(get_child_real_type(timing, "phase_offset"));
406 }
407 catch (XmlException&)
408 {
409 LOG(logging::Level::WARNING, "Clock section '{}' does not specify phase offset.", name);
410 }
411
412 try
413 {
414 timing_obj->setRandomPhaseOffsetStdev(get_child_real_type(timing, "random_phase_offset_stdev"));
415 }
416 catch (XmlException&)
417 {
418 LOG(logging::Level::WARNING, "Clock section '{}' does not specify random phase offset.", name);
419 }
420
421 if (get_attribute_bool(timing, "synconpulse", false))
422 {
423 timing_obj->setSyncOnPulse();
424 }
425
426 ctx.world->add(std::move(timing_obj));
427 }
428
430 {
431 std::string name = XmlElement::getSafeAttribute(antenna, "name");
432 const SimId id = assign_id_from_attribute("antenna '" + name + "'", ObjectType::Antenna);
433 const std::string pattern = XmlElement::getSafeAttribute(antenna, "pattern");
434
435 std::unique_ptr<antenna::Antenna> ant;
436
437 LOG(logging::Level::DEBUG, "Adding antenna '{}' with pattern '{}'", name, pattern);
438 if (pattern == "isotropic")
439 {
440 ant = std::make_unique<antenna::Isotropic>(name, id);
441 }
442 else if (pattern == "sinc")
443 {
444 ant = std::make_unique<antenna::Sinc>(name, get_child_real_type(antenna, "alpha"),
446 get_child_real_type(antenna, "gamma"), id);
447 }
448 else if (pattern == "gaussian")
449 {
450 ant = std::make_unique<antenna::Gaussian>(name, get_child_real_type(antenna, "azscale"),
451 get_child_real_type(antenna, "elscale"), id);
452 }
453 else if (pattern == "squarehorn")
454 {
455 ant = std::make_unique<antenna::SquareHorn>(name, get_child_real_type(antenna, "diameter"), id);
456 }
457 else if (pattern == "parabolic")
458 {
459 ant = std::make_unique<antenna::Parabolic>(name, get_child_real_type(antenna, "diameter"), id);
460 }
461 else if (pattern == "xml")
462 {
463 ant = ctx.loaders.loadXmlAntenna(name, XmlElement::getSafeAttribute(antenna, "filename"), id);
464 }
465 else if (pattern == "file")
466 {
467 ant = ctx.loaders.loadH5Antenna(name, XmlElement::getSafeAttribute(antenna, "filename"), id);
468 }
469 else
470 {
471 LOG(logging::Level::FATAL, "Unsupported antenna pattern: {}", pattern);
472 throw XmlException("Unsupported antenna pattern: " + pattern);
473 }
474
475 try
476 {
477 ant->setEfficiencyFactor(get_child_real_type(antenna, "efficiency"));
478 }
479 catch (XmlException&)
480 {
481 LOG(logging::Level::WARNING, "Antenna '{}' does not specify efficiency, assuming unity.", name);
482 }
483
484 ctx.world->add(std::move(ant));
485 }
486
487 void parseMotionPath(const XmlElement& motionPath, radar::Platform* platform)
488 {
489 math::Path* path = platform->getMotionPath();
490 try
491 {
492 if (std::string interp = XmlElement::getSafeAttribute(motionPath, "interpolation"); interp == "linear")
493 {
495 }
496 else if (interp == "cubic")
497 {
499 }
500 else if (interp == "static")
501 {
503 }
504 else
505 {
506 LOG(logging::Level::ERROR, "Unsupported interpolation type: {} for platform {}. Defaulting to static",
507 interp, platform->getName());
509 }
510 }
511 catch (XmlException&)
512 {
514 "Failed to set MotionPath interpolation type for platform {}. Defaulting to static",
515 platform->getName());
517 }
518
519 unsigned waypoint_index = 0;
520 while (true)
521 {
522 XmlElement waypoint = motionPath.childElement("positionwaypoint", waypoint_index);
523 if (!waypoint.isValid())
524 {
525 break;
526 }
527
528 try
529 {
530 math::Coord coord;
531 coord.t = get_child_real_type(waypoint, "time");
532 coord.pos = math::Vec3(get_child_real_type(waypoint, "x"), get_child_real_type(waypoint, "y"),
533 get_child_real_type(waypoint, "altitude"));
534 path->addCoord(coord);
535 LOG(logging::Level::TRACE, "Added waypoint {} to motion path for platform {}.", waypoint_index,
536 platform->getName());
537 }
538 catch (const XmlException& e)
539 {
540 LOG(logging::Level::ERROR, "Failed to add waypoint to motion path. Discarding waypoint. {}", e.what());
541 }
542
543 waypoint_index++;
544 }
545 path->finalize();
546 }
547
548 void parseRotationPath(const XmlElement& rotation, radar::Platform* platform, const params::RotationAngleUnit unit)
549 {
550 math::RotationPath* path = platform->getRotationPath();
551 try
552 {
553 if (const std::string interp = XmlElement::getSafeAttribute(rotation, "interpolation"); interp == "linear")
554 {
556 }
557 else if (interp == "cubic")
558 {
560 }
561 else if (interp == "static")
562 {
564 }
565 else
566 {
567 throw XmlException("Unsupported interpolation type: " + interp);
568 }
569 }
570 catch (XmlException&)
571 {
573 "Failed to set RotationPath interpolation type for platform {}. Defaulting to static",
574 platform->getName());
576 }
577
578 unsigned waypoint_index = 0;
579 while (true)
580 {
581 XmlElement waypoint = rotation.childElement("rotationwaypoint", waypoint_index);
582 if (!waypoint.isValid())
583 {
584 break;
585 }
586
587 try
588 {
589 LOG(logging::Level::TRACE, "Adding waypoint {} to rotation path for platform {}.", waypoint_index,
590 platform->getName());
591
592 const RealType az_deg = get_child_real_type(waypoint, "azimuth");
593 const RealType el_deg = get_child_real_type(waypoint, "elevation");
594 const RealType time = get_child_real_type(waypoint, "time");
595 const std::string owner =
596 std::format("platform '{}' rotation waypoint {}", platform->getName(), waypoint_index);
597
599 az_deg, unit, rotation_warning_utils::ValueKind::Angle, "XML", owner, "azimuth");
601 el_deg, unit, rotation_warning_utils::ValueKind::Angle, "XML", owner, "elevation");
602
603 path->addCoord(rotation_angle_utils::external_rotation_to_internal(az_deg, el_deg, time, unit));
604 }
605 catch (const XmlException& e)
606 {
607 LOG(logging::Level::ERROR, "Failed to add waypoint to rotation path. Discarding waypoint. {}",
608 e.what());
609 }
610 waypoint_index++;
611 }
612 path->finalize();
613 }
614
615 void parseFixedRotation(const XmlElement& rotation, radar::Platform* platform, const params::RotationAngleUnit unit)
616 {
617 math::RotationPath* path = platform->getRotationPath();
618 try
619 {
620 const RealType start_az_deg = get_child_real_type(rotation, "startazimuth");
621 const RealType start_el_deg = get_child_real_type(rotation, "startelevation");
622 const RealType rate_az_deg_s = get_child_real_type(rotation, "azimuthrate");
623 const RealType rate_el_deg_s = get_child_real_type(rotation, "elevationrate");
624 const std::string owner = std::format("platform '{}' fixedrotation", platform->getName());
625
627 start_az_deg, unit, rotation_warning_utils::ValueKind::Angle, "XML", owner, "startazimuth");
629 start_el_deg, unit, rotation_warning_utils::ValueKind::Angle, "XML", owner, "startelevation");
631 rate_az_deg_s, unit, rotation_warning_utils::ValueKind::Rate, "XML", owner, "azimuthrate");
633 rate_el_deg_s, unit, rotation_warning_utils::ValueKind::Rate, "XML", owner, "elevationrate");
634 const math::RotationCoord start =
635 rotation_angle_utils::external_rotation_to_internal(start_az_deg, start_el_deg, 0.0, unit);
636 const math::RotationCoord rate =
637 rotation_angle_utils::external_rotation_rate_to_internal(rate_az_deg_s, rate_el_deg_s, 0.0, unit);
638
639 path->setConstantRate(start, rate);
640 LOG(logging::Level::DEBUG, "Added fixed rotation to platform {}", platform->getName());
641 }
642 catch (XmlException& e)
643 {
644 LOG(logging::Level::FATAL, "Failed to set fixed rotation for platform {}. {}", platform->getName(),
645 e.what());
646 throw XmlException("Failed to set fixed rotation for platform " + platform->getName());
647 }
648 }
649
651 const ReferenceLookup& refs)
652 {
653 const std::string name = XmlElement::getSafeAttribute(transmitter, "name");
654 const SimId id = assign_id_from_attribute("transmitter '" + name + "'", ObjectType::Transmitter);
655 const XmlElement pulsed_mode_element = transmitter.childElement("pulsed_mode", 0);
656 const bool is_pulsed = pulsed_mode_element.isValid();
658
659 if (!is_pulsed && !transmitter.childElement("cw_mode", 0).isValid())
660 {
661 throw XmlException("Transmitter '" + name + "' must specify a radar mode (<pulsed_mode> or <cw_mode>).");
662 }
663
664 auto transmitter_obj = std::make_unique<radar::Transmitter>(platform, name, mode, id);
665
666 const SimId waveform_id =
667 resolve_reference_id(transmitter, "waveform", "transmitter '" + name + "'", *refs.waveforms);
668 fers_signal::RadarSignal* wave = ctx.world->findWaveform(waveform_id);
669 if (wave == nullptr)
670 {
671 throw XmlException("Waveform ID '" + std::to_string(waveform_id) + "' not found for transmitter '" + name +
672 "'");
673 }
674 transmitter_obj->setWave(wave);
675
676 if (is_pulsed)
677 {
678 transmitter_obj->setPrf(get_child_real_type(pulsed_mode_element, "prf"));
679 }
680
681 const SimId antenna_id =
682 resolve_reference_id(transmitter, "antenna", "transmitter '" + name + "'", *refs.antennas);
683 const antenna::Antenna* ant = ctx.world->findAntenna(antenna_id);
684 if (ant == nullptr)
685 {
686 throw XmlException("Antenna ID '" + std::to_string(antenna_id) + "' not found for transmitter '" + name +
687 "'");
688 }
689 transmitter_obj->setAntenna(ant);
690
691 const SimId timing_id =
692 resolve_reference_id(transmitter, "timing", "transmitter '" + name + "'", *refs.timings);
693 const timing::PrototypeTiming* proto = ctx.world->findTiming(timing_id);
694 if (proto == nullptr)
695 {
696 throw XmlException("Timing ID '" + std::to_string(timing_id) + "' not found for transmitter '" + name +
697 "'");
698 }
699 const auto timing_obj =
700 std::make_shared<timing::Timing>(proto->getName(), next_seed(*ctx.master_seeder), proto->getId());
701 timing_obj->initializeModel(proto);
702 transmitter_obj->setTiming(timing_obj);
703
704 RealType pri = is_pulsed ? (1.0 / transmitter_obj->getPrf()) : 0.0;
705 auto schedule = parseSchedule(transmitter, name, is_pulsed, pri);
706 if (!schedule.empty())
707 {
708 transmitter_obj->setSchedule(std::move(schedule));
709 }
710
711 ctx.world->add(std::move(transmitter_obj));
712 return ctx.world->getTransmitters().back().get();
713 }
714
716 const ReferenceLookup& refs)
717 {
718 const std::string name = XmlElement::getSafeAttribute(receiver, "name");
719 const SimId id = assign_id_from_attribute("receiver '" + name + "'", ObjectType::Receiver);
720 const XmlElement pulsed_mode_element = receiver.childElement("pulsed_mode", 0);
721 const bool is_pulsed = pulsed_mode_element.isValid();
723
724 auto receiver_obj = std::make_unique<radar::Receiver>(platform, name, next_seed(*ctx.master_seeder), mode, id);
725
726 const SimId ant_id = resolve_reference_id(receiver, "antenna", "receiver '" + name + "'", *refs.antennas);
727 const antenna::Antenna* antenna = ctx.world->findAntenna(ant_id);
728 if (antenna == nullptr)
729 {
730 throw XmlException("Antenna ID '" + std::to_string(ant_id) + "' not found for receiver '" + name + "'");
731 }
732 receiver_obj->setAntenna(antenna);
733
734 try
735 {
736 receiver_obj->setNoiseTemperature(get_child_real_type(receiver, "noise_temp"));
737 }
738 catch (XmlException&)
739 {
740 LOG(logging::Level::INFO, "Receiver '{}' does not specify noise temperature",
741 receiver_obj->getName().c_str());
742 }
743
744 if (is_pulsed)
745 {
746 const RealType window_length = get_child_real_type(pulsed_mode_element, "window_length");
747 if (window_length <= 0)
748 {
749 throw XmlException("<window_length> must be positive for receiver '" + name + "'");
750 }
751
752 const RealType prf = get_child_real_type(pulsed_mode_element, "prf");
753 if (prf <= 0)
754 {
755 throw XmlException("<prf> must be positive for receiver '" + name + "'");
756 }
757
758 const RealType window_skip = get_child_real_type(pulsed_mode_element, "window_skip");
759 if (window_skip < 0)
760 {
761 throw XmlException("<window_skip> must not be negative for receiver '" + name + "'");
762 }
763 receiver_obj->setWindowProperties(window_length, prf, window_skip);
764 }
765 else if (!receiver.childElement("cw_mode", 0).isValid())
766 {
767 throw XmlException("Receiver '" + name + "' must specify a radar mode (<pulsed_mode> or <cw_mode>).");
768 }
769
770 const SimId timing_id = resolve_reference_id(receiver, "timing", "receiver '" + name + "'", *refs.timings);
771 const timing::PrototypeTiming* proto = ctx.world->findTiming(timing_id);
772 if (proto == nullptr)
773 {
774 throw XmlException("Timing ID '" + std::to_string(timing_id) + "' not found for receiver '" + name + "'");
775 }
776 const auto timing_obj =
777 std::make_shared<timing::Timing>(proto->getName(), next_seed(*ctx.master_seeder), proto->getId());
778 timing_obj->initializeModel(proto);
779 receiver_obj->setTiming(timing_obj);
780
781 if (get_attribute_bool(receiver, "nodirect", false))
782 {
783 receiver_obj->setFlag(radar::Receiver::RecvFlag::FLAG_NODIRECT);
784 LOG(logging::Level::DEBUG, "Ignoring direct signals for receiver '{}'", receiver_obj->getName().c_str());
785 }
786
787 if (get_attribute_bool(receiver, "nopropagationloss", false))
788 {
789 receiver_obj->setFlag(radar::Receiver::RecvFlag::FLAG_NOPROPLOSS);
790 LOG(logging::Level::DEBUG, "Ignoring propagation losses for receiver '{}'",
791 receiver_obj->getName().c_str());
792 }
793
794 RealType pri = is_pulsed ? (1.0 / receiver_obj->getWindowPrf()) : 0.0;
795 auto schedule = parseSchedule(receiver, name, is_pulsed, pri);
796 if (!schedule.empty())
797 {
798 receiver_obj->setSchedule(std::move(schedule));
799 }
800
801 ctx.world->add(std::move(receiver_obj));
802 return ctx.world->getReceivers().back().get();
803 }
804
805 void parseMonostatic(const XmlElement& monostatic, radar::Platform* platform, ParserContext& ctx,
806 const ReferenceLookup& refs)
807 {
808 radar::Transmitter* trans = parseTransmitter(monostatic, platform, ctx, refs);
809 radar::Receiver* recv = parseReceiver(monostatic, platform, ctx, refs);
810 trans->setAttached(recv);
811 recv->setAttached(trans);
812 }
813
814 void parseTarget(const XmlElement& target, radar::Platform* platform, ParserContext& ctx)
815 {
816 const std::string name = XmlElement::getSafeAttribute(target, "name");
817 const SimId id = assign_id_from_attribute("target '" + name + "'", ObjectType::Target);
818
819 const XmlElement rcs_element = target.childElement("rcs", 0);
820 if (!rcs_element.isValid())
821 {
822 throw XmlException("<rcs> element is required in <target>!");
823 }
824
825 const std::string rcs_type = XmlElement::getSafeAttribute(rcs_element, "type");
826 std::unique_ptr<radar::Target> target_obj;
827 const unsigned seed = next_seed(*ctx.master_seeder);
828
829 if (rcs_type == "isotropic")
830 {
831 target_obj = radar::createIsoTarget(platform, name, get_child_real_type(rcs_element, "value"), seed, id);
832 }
833 else if (rcs_type == "file")
834 {
835 // Defer to dependency-injected file loader
836 target_obj = ctx.loaders.loadFileTarget(platform, name,
837 XmlElement::getSafeAttribute(rcs_element, "filename"), seed, id);
838 }
839 else
840 {
841 throw XmlException("Unsupported RCS type: " + rcs_type);
842 }
843
844 if (const XmlElement model = target.childElement("model", 0); model.isValid())
845 {
846 if (const std::string model_type = XmlElement::getSafeAttribute(model, "type"); model_type == "constant")
847 {
848 target_obj->setFluctuationModel(std::make_unique<radar::RcsConst>());
849 }
850 else if (model_type == "chisquare" || model_type == "gamma")
851 {
852 target_obj->setFluctuationModel(
853 std::make_unique<radar::RcsChiSquare>(target_obj->getRngEngine(), get_child_real_type(model, "k")));
854 }
855 else
856 {
857 throw XmlException("Unsupported model type: " + model_type);
858 }
859 }
860
861 LOG(logging::Level::DEBUG, "Added target {} with RCS type {} to platform {}", name, rcs_type,
862 platform->getName());
863 ctx.world->add(std::move(target_obj));
864 }
865
867 const std::function<void(const XmlElement&, std::string_view)>& register_name,
868 const ReferenceLookup& refs)
869 {
870 auto parseChildrenWithRefs = [&](const std::string& elementName, auto parseFunc)
871 {
872 unsigned index = 0;
873 while (true)
874 {
875 const XmlElement element = platform.childElement(elementName, index++);
876 if (!element.isValid())
877 break;
878 register_name(element, elementName);
879 parseFunc(element, plat, ctx, refs);
880 }
881 };
882
883 auto parseChildrenWithoutRefs = [&](const std::string& elementName, auto parseFunc)
884 {
885 unsigned index = 0;
886 while (true)
887 {
888 const XmlElement element = platform.childElement(elementName, index++);
889 if (!element.isValid())
890 break;
891 register_name(element, elementName);
892 parseFunc(element, plat, ctx);
893 }
894 };
895
896 parseChildrenWithRefs("monostatic", parseMonostatic);
897 parseChildrenWithRefs("transmitter", parseTransmitter);
898 parseChildrenWithRefs("receiver", parseReceiver);
899 parseChildrenWithoutRefs("target", parseTarget);
900 }
901
902 void parsePlatform(const XmlElement& platform, ParserContext& ctx,
903 const std::function<void(const XmlElement&, std::string_view)>& register_name,
904 const ReferenceLookup& refs)
905 {
906 std::string name = XmlElement::getSafeAttribute(platform, "name");
907 const SimId id = assign_id_from_attribute("platform '" + name + "'", ObjectType::Platform);
908 auto plat = std::make_unique<radar::Platform>(name, id);
909
910 parsePlatformElements(platform, ctx, plat.get(), register_name, refs);
911
912 if (const XmlElement motion_path = platform.childElement("motionpath", 0); motion_path.isValid())
913 {
914 parseMotionPath(motion_path, plat.get());
915 }
916
917 const XmlElement rot_path = platform.childElement("rotationpath", 0);
918 if (const XmlElement fixed_rot = platform.childElement("fixedrotation", 0);
919 rot_path.isValid() && fixed_rot.isValid())
920 {
922 "Both <rotationpath> and <fixedrotation> are declared for platform {}. Only <rotationpath> will be "
923 "used.",
924 plat->getName());
925 parseRotationPath(rot_path, plat.get(), ctx.parameters.rotation_angle_unit);
926 }
927 else if (rot_path.isValid())
928 {
929 parseRotationPath(rot_path, plat.get(), ctx.parameters.rotation_angle_unit);
930 }
931 else if (fixed_rot.isValid())
932 {
933 parseFixedRotation(fixed_rot, plat.get(), ctx.parameters.rotation_angle_unit);
934 }
935
936 ctx.world->add(std::move(plat));
937 }
938
939 void collectIncludeElements(const XmlDocument& doc, const fs::path& currentDir, std::vector<fs::path>& includePaths)
940 {
941 unsigned index = 0;
942 while (true)
943 {
944 XmlElement include_element = doc.getRootElement().childElement("include", index++);
945 if (!include_element.isValid())
946 break;
947
948 std::string include_filename = include_element.getText();
949 if (include_filename.empty())
950 {
951 LOG(logging::Level::ERROR, "<include> element is missing the filename!");
952 continue;
953 }
954
955 fs::path include_path = currentDir / include_filename;
956 includePaths.push_back(include_path);
957
958 XmlDocument included_doc;
959 if (!included_doc.loadFile(include_path.string()))
960 {
961 LOG(logging::Level::ERROR, "Failed to load included XML file: {}", include_path.string());
962 continue;
963 }
964
965 collectIncludeElements(included_doc, include_path.parent_path(), includePaths);
966 }
967 }
968
969 bool addIncludeFilesToMainDocument(const XmlDocument& mainDoc, const fs::path& currentDir)
970 {
971 std::vector<fs::path> include_paths;
972 collectIncludeElements(mainDoc, currentDir, include_paths);
973 bool did_combine = false;
974
975 for (const auto& include_path : include_paths)
976 {
977 XmlDocument included_doc;
978 if (!included_doc.loadFile(include_path.string()))
979 {
980 throw XmlException("Failed to load included XML file: " + include_path.string());
981 }
982
983 mergeXmlDocuments(mainDoc, included_doc);
984 did_combine = true;
985 }
986
987 removeIncludeElements(mainDoc);
988 return did_combine;
989 }
990
991 void validateXml(const bool didCombine, const XmlDocument& mainDoc)
992 {
993 LOG(logging::Level::DEBUG, "Validating the{}XML file...", didCombine ? " combined " : " ");
994 if (!mainDoc.validateWithDtd(fers_xml_dtd))
995 {
996 LOG(logging::Level::FATAL, "{} XML file failed DTD validation!", didCombine ? "Combined" : "Main");
997 throw XmlException("XML file failed DTD validation!");
998 }
999 LOG(logging::Level::DEBUG, "{} XML file passed DTD validation.", didCombine ? "Combined" : "Main");
1000
1001 if (!mainDoc.validateWithXsd(fers_xml_xsd))
1002 {
1003 LOG(logging::Level::FATAL, "{} XML file failed XSD validation!", didCombine ? "Combined" : "Main");
1004 throw XmlException("XML file failed XSD validation!");
1005 }
1006 LOG(logging::Level::DEBUG, "{} XML file passed XSD validation.", didCombine ? "Combined" : "Main");
1007 }
1008
1010 {
1011 const XmlElement root = doc.getRootElement();
1012 if (root.name() != "simulation")
1013 {
1014 throw XmlException("Root element is not <simulation>!");
1015 }
1016
1017 std::unordered_map<std::string, std::string> name_registry;
1018 name_registry.reserve(64); // TODO: reserve 64?
1019 const auto register_name = [&](const XmlElement& element, const std::string_view kind)
1020 {
1021 const std::string name = XmlElement::getSafeAttribute(element, "name");
1022 const auto [iter, inserted] = name_registry.emplace(name, std::string(kind));
1023 if (!inserted)
1024 {
1025 throw XmlException("Duplicate name '" + name + "' found for " + std::string(kind) +
1026 "; previously used by " + iter->second + ".");
1027 }
1028 };
1029
1030 try
1031 {
1033 if (!ctx.parameters.simulation_name.empty())
1034 {
1035 LOG(logging::Level::INFO, "Simulation name set to: {}", ctx.parameters.simulation_name);
1036 }
1037 }
1038 catch (const XmlException&)
1039 {
1040 LOG(logging::Level::WARNING, "No 'name' attribute found in <simulation> tag. KML name will default.");
1041 }
1042
1043 parseParameters(root.childElement("parameters", 0), ctx.parameters);
1044
1046
1047 auto parseElements =
1048 [](const XmlElement& parent, const std::string& elementName, ParserContext& parser_ctx, auto parseFunction)
1049 {
1050 unsigned index = 0;
1051 while (true)
1052 {
1053 XmlElement element = parent.childElement(elementName, index++);
1054 if (!element.isValid())
1055 break;
1056 parseFunction(element, parser_ctx);
1057 }
1058 };
1059
1060 parseElements(root, "waveform", ctx,
1061 [&](const XmlElement& p, ParserContext& c)
1062 {
1063 register_name(p, "waveform");
1064 parseWaveform(p, c);
1065 });
1066
1067 parseElements(root, "timing", ctx,
1068 [&](const XmlElement& p, ParserContext& c)
1069 {
1070 register_name(p, "timing");
1071 parseTiming(p, c);
1072 });
1073
1074 parseElements(root, "antenna", ctx,
1075 [&](const XmlElement& p, ParserContext& c)
1076 {
1077 register_name(p, "antenna");
1078 parseAntenna(p, c);
1079 });
1080
1081 std::unordered_map<std::string, SimId> waveform_refs;
1082 std::unordered_map<std::string, SimId> antenna_refs;
1083 std::unordered_map<std::string, SimId> timing_refs;
1084 waveform_refs.reserve(ctx.world->getWaveforms().size());
1085 antenna_refs.reserve(ctx.world->getAntennas().size());
1086 timing_refs.reserve(ctx.world->getTimings().size());
1087
1088 for (const auto& [id, waveform] : ctx.world->getWaveforms())
1089 waveform_refs.emplace(waveform->getName(), id);
1090 for (const auto& [id, antenna] : ctx.world->getAntennas())
1091 antenna_refs.emplace(antenna->getName(), id);
1092 for (const auto& [id, timing] : ctx.world->getTimings())
1093 timing_refs.emplace(timing->getName(), id);
1094
1095 const ReferenceLookup refs{&waveform_refs, &antenna_refs, &timing_refs};
1096
1097 parseElements(root, "platform", ctx,
1098 [&](const XmlElement& p, ParserContext& c)
1099 {
1100 register_name(p, "platform");
1101 parsePlatform(p, c, register_name, refs);
1102 });
1103
1104 const RealType start_time = ctx.parameters.start;
1105 const RealType end_time = ctx.parameters.end;
1106 const RealType dt_sim = 1.0 / (ctx.parameters.rate * ctx.parameters.oversample_ratio);
1107 const auto num_samples = static_cast<size_t>(std::ceil((end_time - start_time) / dt_sim));
1108
1109 for (const auto& receiver : ctx.world->getReceivers())
1110 {
1111 if (receiver->getMode() == radar::OperationMode::CW_MODE)
1112 {
1113 receiver->prepareCwData(num_samples);
1114 }
1115 }
1116
1118
1119 LOG(logging::Level::DEBUG, "Initial Event Queue State:\n{}", ctx.world->dumpEventQueue());
1120 }
1121
1123 {
1124 return {.loadWaveform = [](const std::string& name, const fs::path& pulse_path, RealType power,
1125 RealType carrierFreq, SimId id)
1126 { return serial::loadWaveformFromFile(name, pulse_path.string(), power, carrierFreq, id); },
1127 .loadXmlAntenna = [](const std::string& name, const std::string& filename, SimId id)
1128 { return std::make_unique<antenna::XmlAntenna>(name, filename, id); },
1129 .loadH5Antenna = [](const std::string& name, const std::string& filename, SimId id)
1130 { return std::make_unique<antenna::H5Antenna>(name, filename, id); },
1131 .loadFileTarget = [](radar::Platform* platform, const std::string& name, const std::string& filename,
1132 unsigned seed, SimId id)
1133 { return radar::createFileTarget(platform, name, filename, seed, id); }};
1134 }
1135}
Header file defining various types of antennas and their gain patterns.
SimId generateId(ObjectType type)
Generate a unique SimId for a given object type.
Definition sim_id.h:60
static SimIdGenerator & instance()
Get the singleton instance of SimIdGenerator.
Definition sim_id.h:48
Class for managing XML documents.
XmlElement getRootElement() const
Get the root element of the document.
bool loadFile(std::string_view filename)
Load an XML file into the document.
bool validateWithDtd(std::span< const unsigned char > dtdData) const
Validate the document using a DTD.
bool validateWithXsd(std::span< const unsigned char > xsdData) const
Validate the document using an XSD schema.
Class representing a node in an XML document.
XmlElement childElement(const std::string_view name="", const unsigned index=0) const noexcept
Retrieve a child element by name and index.
static std::string getSafeAttribute(const XmlElement &element, const std::string_view name)
Get the value of an attribute safely.
std::string_view name() const noexcept
Get the name of the XML element.
bool isValid() const noexcept
Check if the XML element is valid.
std::string getText() const
Get the text content of the XML element.
Exception class for handling XML-related errors.
Abstract base class representing an antenna.
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
fers_signal::RadarSignal * findWaveform(const SimId id)
Finds a radar signal by ID.
Definition world.cpp:76
const std::unordered_map< SimId, std::unique_ptr< antenna::Antenna > > & getAntennas() const noexcept
Retrieves the map of antennas.
Definition world.h:239
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
std::string dumpEventQueue() const
Dumps the current state of the event queue to a string for debugging.
Definition world.cpp:338
const std::vector< std::unique_ptr< radar::Transmitter > > & getTransmitters() const noexcept
Retrieves the list of radar transmitters.
Definition world.h:220
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.
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
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.
void setConstantRate(const RotationCoord &setstart, const RotationCoord &setrate) noexcept
Sets constant rate interpolation.
void setInterp(InterpType setinterp) noexcept
Sets the interpolation type for the path.
void addCoord(const RotationCoord &coord) noexcept
Adds a rotation coordinate to the path.
A class representing a vector in rectangular coordinates.
Represents a simulation platform with motion and rotation paths.
Definition platform.h:32
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
math::RotationPath * getRotationPath() const noexcept
Gets the rotation path of the platform.
Definition platform.h:67
void setAttached(const Radar *obj)
Attaches another radar object to this radar.
Definition radar_obj.cpp:56
Manages radar signal reception and response processing.
Definition receiver.h:37
Represents a radar transmitter system.
Definition transmitter.h:33
Manages timing properties such as frequency, offsets, and synchronization.
std::string getName() const
Gets the name of the timing source.
SimId getId() const noexcept
Gets the unique ID of the timing source.
Global configuration file for the project.
double RealType
Type for real numbers.
Definition config.h:27
Coordinate and rotation structure operations.
Classes and operations for 3D geometry.
void mergeXmlDocuments(const XmlDocument &mainDoc, const XmlDocument &includedDoc)
Merge two XML documents.
void removeIncludeElements(const XmlDocument &doc)
Remove "include" elements from the XML document.
Header file for the logging system.
#define LOG(level,...)
Definition logging.h:19
@ WARNING
Warning level for potentially harmful situations.
@ FATAL
Fatal level for severe error events.
@ TRACE
Trace level for detailed debugging information.
@ INFO
Info level for informational messages.
@ ERROR
Error level for error events.
@ DEBUG
Debug level for general debugging information.
RotationAngleUnit
Defines the units used at external rotation-path boundaries.
Definition parameters.h:41
@ UTM
Universal Transverse Mercator.
@ ENU
East-North-Up local tangent plane (default)
@ ECEF
Earth-Centered, Earth-Fixed.
std::optional< RotationAngleUnit > rotationAngleUnitFromToken(const std::string_view token) noexcept
Definition parameters.h:299
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
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.
math::RotationCoord external_rotation_to_internal(const RealType azimuth, const RealType elevation, const RealType time, 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)
SimId assign_id_from_attribute(const std::string &owner, ObjectType type)
Generates a unique SimId based on the requested object type.
void collectIncludeElements(const XmlDocument &doc, const fs::path &currentDir, std::vector< fs::path > &includePaths)
void parseAntenna(const XmlElement &antenna, ParserContext &ctx)
Parses an <antenna> block and adds it to the World.
void parseWaveform(const XmlElement &waveform, ParserContext &ctx)
Parses a <waveform> block and adds it to the World.
bool addIncludeFilesToMainDocument(const XmlDocument &mainDoc, const fs::path &currentDir)
void processParsedDocument(const XmlDocument &doc, ParserContext &ctx)
Coordinates the full parsing of a validated XML document tree.
std::vector< radar::SchedulePeriod > parseSchedule(const XmlElement &parent, const std::string &parentName, const bool isPulsed, const RealType pri)
Parses a schedule (active periods) for a transmitter or receiver.
SimId resolve_reference_id(const XmlElement &element, const std::string &attributeName, const std::string &owner, const std::unordered_map< std::string, SimId > &name_map)
Resolves an XML string reference into an internal SimId.
void parseFixedRotation(const XmlElement &rotation, radar::Platform *platform, const params::RotationAngleUnit unit)
Parses a <fixedrotation> block and attaches it to a Platform.
void parseRotationPath(const XmlElement &rotation, radar::Platform *platform, const params::RotationAngleUnit unit)
Parses a <rotationpath> block and attaches it to a Platform.
radar::Transmitter * parseTransmitter(const XmlElement &transmitter, radar::Platform *platform, ParserContext &ctx, const ReferenceLookup &refs)
Parses a <transmitter> block, resolves its dependencies, and adds it to the World.
void parsePlatformElements(const XmlElement &platform, ParserContext &ctx, radar::Platform *plat, const std::function< void(const XmlElement &, std::string_view)> &register_name, const ReferenceLookup &refs)
Iterates and parses all children elements (radars, targets) of a platform.
void parseTiming(const XmlElement &timing, ParserContext &ctx)
Parses a <timing> block and adds the prototype timing to the World.
void parseParameters(const XmlElement &parameters, params::Parameters &params_out)
Parses the <parameters> block into the isolated context parameters.
void parsePlatform(const XmlElement &platform, ParserContext &ctx, const std::function< void(const XmlElement &, std::string_view)> &register_name, const ReferenceLookup &refs)
Parses a complete <platform> block, including its motion paths and sub-elements.
void parseTarget(const XmlElement &target, radar::Platform *platform, ParserContext &ctx)
Parses a <target> block and adds it to the World.
RealType get_child_real_type(const XmlElement &element, const std::string &elementName)
Extracts a floating-point (RealType) value from a named child element.
radar::Receiver * parseReceiver(const XmlElement &receiver, radar::Platform *platform, ParserContext &ctx, const ReferenceLookup &refs)
Parses a <receiver> block, resolves its dependencies, and adds it to the World.
bool get_attribute_bool(const XmlElement &element, const std::string &attributeName, const bool defaultVal)
Extracts a boolean value from a named attribute.
void validateXml(const bool didCombine, const XmlDocument &mainDoc)
Validates an XML document against the embedded DTD and XSD schemas.
void parseMotionPath(const XmlElement &motionPath, radar::Platform *platform)
Parses a <motionpath> block and attaches it to a Platform.
AssetLoaders createDefaultAssetLoaders()
Creates an AssetLoaders struct populated with standard file-I/O implementations.
void parseMonostatic(const XmlElement &monostatic, radar::Platform *platform, ParserContext &ctx, const ReferenceLookup &refs)
Parses a <monostatic> block, creating a linked transmitter and receiver pair.
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.
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.
Defines the Radar class and associated functionality.
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
ObjectType
Categorizes objects for ID generation.
Definition sim_id.h:25
Represents a position in 3D space with an associated time.
Definition coord.h:24
RealType t
Time.
Definition coord.h:26
Vec3 pos
3D position
Definition coord.h:25
Represents a rotation in terms of azimuth, elevation, and time.
Definition coord.h:72
Struct to hold simulation parameters.
Definition parameters.h:51
RealType rate
Rendering sample rate.
Definition parameters.h:69
double origin_longitude
Geodetic origin longitude.
Definition parameters.h:63
RealType start
Start time for the simulation.
Definition parameters.h:56
double origin_altitude
Geodetic origin altitude (in meters)
Definition parameters.h:64
CoordinateFrame coordinate_frame
Scenario coordinate frame.
Definition parameters.h:65
RealType end
End time for the simulation.
Definition parameters.h:57
int utm_zone
UTM zone (1-60), if applicable.
Definition parameters.h:67
unsigned oversample_ratio
Oversampling ratio.
Definition parameters.h:75
std::optional< unsigned > random_seed
Random seed for simulation.
Definition parameters.h:70
RealType sim_sampling_rate
Temporal sampling rate (Hz) that determines time-step resolution for radar pulse simulation.
Definition parameters.h:58
std::string simulation_name
The name of the simulation, from the XML.
Definition parameters.h:74
RotationAngleUnit rotation_angle_unit
External rotation angle unit.
Definition parameters.h:66
RealType c
Speed of light (modifiable)
Definition parameters.h:54
static constexpr RealType DEFAULT_C
Speed of light (m/s)
Definition parameters.h:52
unsigned adc_bits
ADC quantization bits.
Definition parameters.h:71
bool utm_north_hemisphere
UTM hemisphere, if applicable.
Definition parameters.h:68
double origin_latitude
Geodetic origin latitude.
Definition parameters.h:62
Container for functions that load external file-backed assets.
std::function< std::unique_ptr< fers_signal::RadarSignal >(const std::string &name, const std::filesystem::path &pulse_path, RealType power, RealType carrierFreq, SimId id)> loadWaveform
Hook to load a pulsed waveform from an external file.
std::function< std::unique_ptr< radar::Target >(radar::Platform *platform, const std::string &name, const std::string &filename, unsigned seed, SimId id)> loadFileTarget
Hook to load a target's Radar Cross Section (RCS) from a file.
std::function< std::unique_ptr< antenna::Antenna >(const std::string &name, const std::string &filename, SimId id)> loadXmlAntenna
Hook to load an antenna pattern defined in a legacy XML format.
std::function< std::unique_ptr< antenna::Antenna >(const std::string &name, const std::string &filename, SimId id)> loadH5Antenna
Hook to load an antenna pattern from an HDF5 file.
Encapsulates the state required during the XML parsing process.
core::World * world
Pointer to the World where parsed objects are inserted.
std::mt19937 * master_seeder
RNG used to generate independent seeds for simulated objects.
std::filesystem::path base_dir
The directory of the main XML file (used to resolve relative asset paths).
params::Parameters parameters
An isolated copy of the simulation parameters being built.
AssetLoaders loaders
The injected asset loaders for external files.
Holds maps to resolve string names to internal SimId references during XML parsing.
const std::unordered_map< std::string, SimId > * timings
Map of timing object names to IDs.
const std::unordered_map< std::string, SimId > * waveforms
Map of waveform names to IDs.
const std::unordered_map< std::string, SimId > * antennas
Map of antenna names to IDs.
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.
Core utility layer for parsing FERS XML scenario files.