9#include <GeographicLib/UTMUPS.hpp>
23#include "fers_xml_dtd.h"
24#include "fers_xml_xsd.h"
42namespace fs = std::filesystem;
61 " must specify exactly one radar mode (<pulsed_mode>, <cw_mode>, "
62 "<fmcw_mode>, or <sfcw_mode>).");
74 " must specify exactly one radar mode (<pulsed_mode>, <cw_mode>, "
75 "<fmcw_mode>, or <sfcw_mode>).");
93 fmcw_mode.childElement(
"dechirp_reference", 0).isValid() ||
94 fmcw_mode.childElement(
"if_sample_rate", 0).isValid() ||
95 fmcw_mode.childElement(
"if_filter_bandwidth", 0).isValid() ||
96 fmcw_mode.childElement(
"if_filter_transition_width", 0).isValid();
101 const std::string&
owner)
118 if (value <= 0.0 || !std::isfinite(value))
127 return if_chain.sample_rate_hz.has_value() ||
if_chain.filter_bandwidth_hz.has_value() ||
128 if_chain.filter_transition_width_hz.has_value();
133 const std::string&
owner)
137 throw XmlException(
owner +
" declares <dechirp_reference> while dechirp_mode is 'none'.");
141 throw XmlException(
owner +
" declares IF-chain fields while dechirp_mode is 'none'.");
147 if ((
if_chain.filter_bandwidth_hz.has_value() ||
if_chain.filter_transition_width_hz.has_value()) &&
148 !
if_chain.sample_rate_hz.has_value())
152 if (
if_chain.sample_rate_hz.has_value())
157 throw XmlException(
owner +
" <if_sample_rate> must not exceed the simulation sample rate.");
160 if (
if_chain.sample_rate_hz.has_value() &&
if_chain.filter_bandwidth_hz.has_value() &&
163 throw XmlException(
owner +
" <if_filter_bandwidth> must be less than half <if_sample_rate>.");
172 throw XmlException(
owner +
" enables dechirping but does not declare <dechirp_reference>.");
174 if (
fmcw_mode.childElement(
"dechirp_reference", 1).isValid())
185 catch (
const std::exception&
e)
195 if (transmitter_name.has_value() || waveform_name.has_value())
198 " attached dechirp_reference must not set transmitter_name or "
203 if (!transmitter_name.has_value() || transmitter_name->empty() || waveform_name.has_value())
205 throw XmlException(
owner +
" transmitter dechirp_reference requires transmitter_name only.");
210 if (!waveform_name.has_value() || waveform_name->empty() || transmitter_name.has_value())
212 throw XmlException(
owner +
" custom dechirp_reference requires waveform_name only.");
217 throw XmlException(
owner +
" dechirp_reference source must be attached, transmitter, or custom.");
225 const std::string&
owner)
247 catch (
const std::exception&
e)
257 .filter_transition_width_hz =
285 const std::string&
owner)
300 static_assert(std::mt19937::max() <= std::numeric_limits<unsigned>::max(),
301 "std::mt19937 output must fit into unsigned seeds.");
302 return static_cast<unsigned>(master_seeder());
307 const std::string&
owner)
309 if (
const auto it =
ctx.timing_instances.find(
timing_id);
it !=
ctx.timing_instances.end())
315 if (
proto ==
nullptr)
335 return std::stod(
text);
368 const std::unordered_map<std::string, SimId>&
name_map)
403 catch (
const std::exception&
e)
432 template <
typename Setter>
446 template <
typename Setter>
484 [&](
const unsigned value)
491 [&](
const unsigned value)
543 catch (
const std::exception&
e)
558 if (
params_out.utm_zone < GeographicLib::UTMUPS::MINUTMZONE ||
559 params_out.utm_zone > GeographicLib::UTMUPS::MAXUTMZONE)
562 " is invalid; must be in [1, 60].");
574 throw XmlException(
"KML UTM hemisphere '" +
hem_str +
"' is invalid; must be 'N' or 'S'.");
603 "ENU KML frame specified but no <origin> tag found. Using default KML origin at UCT.");
612 catch (
const std::exception&
e)
615 "Could not parse KML <coordinatesystem> from XML: {}. Defaulting KML export to ENU.",
e.what());
633 throw std::runtime_error(
"Sampling rate must be > 0");
663 ctx.world->add(std::move(
wave));
667 auto cw_signal = std::make_unique<fers_signal::CwSignal>();
668 auto wave = std::make_unique<fers_signal::RadarSignal>(
670 ctx.world->add(std::move(
wave));
679 throw XmlException(
"Waveform '" + name +
"' has an invalid step_count.");
684 std::optional<std::size_t> sweep_count;
691 throw XmlException(
"Waveform '" + name +
"' has an invalid sweep_count.");
693 sweep_count =
static_cast<std::size_t
>(
raw_count);
696 auto sfcw_signal = std::make_unique<fers_signal::SteppedFrequencySignal>(
697 start_frequency_offset, step_size,
static_cast<std::size_t
>(
raw_step_count), dwell_time, step_period,
702 ctx.world->add(std::move(
wave));
712 RealType start_frequency_offset = 0.0;
719 std::optional<std::size_t> chirp_count;
726 throw XmlException(
"Waveform '" + name +
"' has an invalid chirp_count.");
728 chirp_count =
static_cast<std::size_t
>(
raw_count);
731 auto fmcw_signal = std::make_unique<fers_signal::FmcwChirpSignal>(
732 chirp_bandwidth, chirp_duration, chirp_period, start_frequency_offset, chirp_count,
direction);
734 auto wave = std::make_unique<fers_signal::RadarSignal>(name, power,
carrier, chirp_duration,
737 ctx.world->add(std::move(
wave));
745 RealType start_frequency_offset = 0.0;
752 std::optional<std::size_t> triangle_count;
759 throw XmlException(
"Waveform '" + name +
"' has an invalid triangle_count.");
761 triangle_count =
static_cast<std::size_t
>(
raw_count);
764 auto fmcw_signal = std::make_unique<fers_signal::FmcwTriangleSignal>(
765 chirp_bandwidth, chirp_duration, start_frequency_offset, triangle_count);
766 auto wave = std::make_unique<fers_signal::RadarSignal>(
769 ctx.world->add(std::move(
wave));
774 throw XmlException(
"Unsupported waveform type for '" + name +
"'");
783 auto timing_obj = std::make_unique<timing::PrototypeTiming>(name,
id);
841 std::unique_ptr<antenna::Antenna>
ant;
846 ant = std::make_unique<antenna::Isotropic>(name,
id);
854 else if (
pattern ==
"gaussian")
859 else if (
pattern ==
"squarehorn")
863 else if (
pattern ==
"parabolic")
881 if (!
antenna.childElement(
"efficiency", 0).isValid())
897 ctx.world->add(std::move(
ant));
927 "MotionPath interpolation type for platform {} not specified. Defaulting to static.",
970 else if (
interp ==
"cubic")
974 else if (
interp ==
"static")
986 "Failed to set RotationPath interpolation type for platform {}. Defaulting to static",
1008 const std::string
owner =
1037 const std::string
owner = std::format(
"platform '{}' fixedrotation",
platform->getName());
1075 const SimId waveform_id =
1078 if (
wave ==
nullptr)
1080 throw XmlException(
"Waveform ID '" + std::to_string(waveform_id) +
"' not found for transmitter '" + name +
1097 throw XmlException(
"Antenna ID '" + std::to_string(
antenna_id) +
"' not found for transmitter '" + name +
1108 if (
wave->isFmcwFamily() ||
wave->isSteppedFrequency())
1118 return ctx.world->getTransmitters().back().get();
1130 throw XmlException(
"Transmitter '" + name +
"' fmcw_mode must not contain dechirp configuration.");
1151 throw XmlException(
"Antenna ID '" + std::to_string(
ant_id) +
"' not found for receiver '" + name +
"'");
1155 if (!
receiver.childElement(
"noise_temp", 0).isValid())
1176 if (window_length <= 0)
1178 throw XmlException(
"<window_length> must be positive for receiver '" + name +
"'");
1184 throw XmlException(
"<prf> must be positive for receiver '" + name +
"'");
1188 if (window_skip < 0)
1190 throw XmlException(
"<window_skip> must not be negative for receiver '" + name +
"'");
1220 return ctx.world->getReceivers().back().get();
1242 throw XmlException(
"Monostatic '" + name +
"' parsed inconsistent transmitter/receiver modes.");
1244 if (
trans->getSignal() !=
nullptr)
1247 "Monostatic '" +
trans->getName() +
"'");
1261 throw XmlException(
"<rcs> element is required in <target>!");
1287 target_obj->setFluctuationModel(std::make_unique<radar::RcsConst>());
1347 auto plat = std::make_unique<radar::Platform>(name,
id);
1361 "Both <rotationpath> and <fixedrotation> are declared for platform {}. Only <rotationpath> will be "
1375 ctx.world->add(std::move(
plat));
1451 if (
root.name() !=
"simulation")
1453 throw XmlException(
"Root element is not <simulation>!");
1464 throw XmlException(
"Duplicate name '" + name +
"' found for " + std::string(kind) +
1465 "; previously used by " +
iter->second +
".");
1472 if (!
ctx.parameters.simulation_name.empty())
1522 std::unordered_map<std::string, SimId>
timing_refs;
1527 for (
const auto& [
id, waveform] :
ctx.world->getWaveforms())
1529 for (
const auto& [
id,
antenna] :
ctx.world->getAntennas())
1531 for (
const auto& [
id,
timing] :
ctx.world->getTimings())
1543 ctx.world->resolveReceiverDechirpReferences();
1545 ctx.world->scheduleInitialEvents();
1555 .loadXmlAntenna = [](
const std::string& name,
const std::string&
filename,
SimId id)
1556 {
return std::make_unique<antenna::XmlAntenna>(name,
filename,
id); },
1557 .loadH5Antenna = [](
const std::string& name,
const std::string&
filename,
SimId id)
1558 {
return std::make_unique<antenna::H5Antenna>(name,
filename,
id); },
Header file defining various types of antennas and their gain patterns.
const Transmitter & transmitter
const Receiver & receiver
SimId generateId(ObjectType type)
Generate a unique SimId for a given object type.
static SimIdGenerator & instance()
Get the singleton instance of SimIdGenerator.
Class for managing XML documents.
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::optional< std::string > getOptionalAttribute(const XmlElement &element, const std::string_view name)
Get the value of an optional attribute.
static std::string getSafeAttribute(const XmlElement &element, const std::string_view name)
Get the value of an attribute safely.
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.
Class representing a radar signal with associated properties.
Represents a path with coordinates and allows for various interpolation methods.
@ INTERP_STATIC
Hold the first coordinate for all query times.
@ INTERP_LINEAR
Linearly interpolate between neighboring coordinates.
@ INTERP_CUBIC
Cubically interpolate between neighboring coordinates.
void setInterp(InterpType settype) noexcept
Changes the interpolation type.
void addCoord(const Coord &coord) noexcept
Adds a coordinate to the path.
void finalize()
Finalizes the path, preparing it for interpolation.
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.
@ INTERP_STATIC
Hold the first rotation for all query times.
@ INTERP_LINEAR
Linearly interpolate between neighboring rotations.
@ INTERP_CUBIC
Cubically interpolate between neighboring rotations.
A class representing a vector in rectangular coordinates.
Manages radar signal reception and response processing.
@ Transmitter
Use a named transmitter.
@ Attached
Use the attached transmitter.
@ None
No reference configured.
@ Custom
Use a named top-level waveform with the receiver schedule.
DechirpMode
Receiver-side FMCW dechirping mode.
@ None
Output raw pre-mix streaming IQ.
@ FLAG_NODIRECT
Disable direct-path reception.
@ FLAG_NOPROPLOSS
Disable propagation-loss scaling.
Represents a radar transmitter system.
Manages timing properties such as frequency, offsets, and synchronization.
Global configuration file for the project.
double RealType
Type for real numbers.
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.
FmcwChirpDirection parseFmcwChirpDirection(const std::string_view direction)
Parses a schema chirp direction token.
@ 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.
RealType rate() noexcept
Get the rendering sample rate.
unsigned oversampleRatio() noexcept
Get the oversampling ratio.
@ 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
Parses a rotation angle unit from an XML token.
void validateOversampleRatio(const unsigned ratio)
Validates that an oversampling ratio is supported.
RotationAngleUnit
Defines the units used at external rotation-path boundaries.
Parameters params
Global simulation parameter state.
std::unique_ptr< Target > createIsoTarget(Platform *platform, std::string name, RealType rcs, unsigned seed, const SimId id=0)
Creates an isotropic target.
OperationMode
Defines the operational mode of a radar component.
@ SFCW_MODE
The component operates in a stepped-frequency CW streaming mode.
@ PULSED_MODE
The component operates in a pulsed mode.
@ CW_MODE
The component operates in a continuous-wave mode.
@ FMCW_MODE
The component operates in an FMCW streaming mode.
Receiver::DechirpReferenceSource parseDechirpReferenceSourceToken(const std::string_view token)
Parses a dechirp reference source scenario token.
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.
Receiver::DechirpMode parseDechirpModeToken(const std::string_view token)
Parses a dechirp mode scenario token.
std::vector< SchedulePeriod > processRawSchedule(const std::vector< SchedulePeriod > &periods, const std::string &ownerName, const bool isPulsed, const RealType pri)
Processes a raw list of schedule periods.
void validateWaveform(const fers_signal::RadarSignal &wave, const std::string &owner, const Thrower &throw_error)
Validates that a waveform is compatible with FMCW streaming constraints.
void validateSchedule(const std::vector< radar::SchedulePeriod > &schedule, const fers_signal::FmcwChirpSignal &fmcw, const std::string &owner, const Thrower &throw_error)
Validates that an FMCW waveform schedule can emit complete chirps.
void validateWaveformModeMatch(const fers_signal::RadarSignal &wave, const radar::OperationMode mode, const std::string &owner, const Thrower &throw_error)
Validates that a waveform and radar operation mode are compatible.
math::RotationCoord external_rotation_to_internal(const RealType azimuth, const RealType elevation, const RealType time, const params::RotationAngleUnit unit) noexcept
Converts external compass azimuth/elevation into internal rotation coordinates.
math::RotationCoord external_rotation_rate_to_internal(const RealType azimuth_rate, const RealType elevation_rate, const RealType time, const params::RotationAngleUnit unit) noexcept
Converts external compass azimuth/elevation rates into internal rotation rates.
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)
Emits or captures a warning when a rotation value likely uses the wrong unit.
@ Angle
Absolute rotation angle.
SimId assign_id_from_attribute(const std::string &owner, ObjectType type)
Generates a unique SimId based on the requested object type.
void setOptionalUnsignedParameter(const XmlElement ¶meters, const std::string ¶m_name, const unsigned default_value, Setter setter)
void parseUtmCoordinateSystem(const XmlElement &cs_element, params::Parameters ¶ms_out)
void collectIncludeElements(const XmlDocument &doc, const fs::path ¤tDir, std::vector< fs::path > &includePaths)
static radar::Transmitter * parseTransmitterWithMode(const XmlElement &transmitter, radar::Platform *platform, ParserContext &ctx, const ReferenceLookup &refs, const radar::OperationMode mode)
Parses a transmitter after its operation mode has already been determined.
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 ¤tDir)
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 setOptionalRealParameter(const XmlElement ¶meters, const std::string ¶m_name, const RealType default_value, Setter setter)
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)> ®ister_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 parseRotationAngleUnit(const XmlElement ¶meters, params::Parameters ¶ms_out)
void parseParameters(const XmlElement ¶meters, params::Parameters ¶ms_out)
Parses the <parameters> block into the isolated context parameters.
void parseCoordinateSystemParameter(const XmlElement ¶meters, params::Parameters ¶ms_out, const bool origin_set)
void parsePlatform(const XmlElement &platform, ParserContext &ctx, const std::function< void(const XmlElement &, std::string_view)> ®ister_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.
static radar::Receiver * parseReceiverWithMode(const XmlElement &receiver, radar::Platform *platform, ParserContext &ctx, const ReferenceLookup &refs, const radar::OperationMode mode)
Parses a receiver after its operation mode has already been determined.
bool get_attribute_bool(const XmlElement &element, const std::string &attributeName, const bool defaultVal)
Extracts a boolean value from a named attribute.
unsigned parseUnsignedParameter(const std::string_view param_name, const RealType raw_value)
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.
bool parseOriginParameter(const XmlElement ¶meters, params::Parameters ¶ms_out)
void parseOptionalNumericParameters(const XmlElement ¶meters, params::Parameters ¶ms_out)
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 ...
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.
ObjectType
Categorizes objects for ID generation.
Represents a position in 3D space with an associated time.
Represents a rotation in terms of azimuth, elevation, and time.
Struct to hold simulation parameters.
static constexpr RealType DEFAULT_C
Speed of light (m/s)
Parsed and resolved dechirp reference details.
Receiver-local FMCW IF-chain request parsed from scenario input.
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.
Encapsulates the state required during the XML parsing process.
Holds maps to resolve string names to internal SimId references during XML parsing.
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.
Header file for the World class in the simulator.
Core utility layer for parsing FERS XML scenario files.