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>, or "
73 " must specify exactly one radar mode (<pulsed_mode>, <cw_mode>, or <fmcw_mode>).");
82 fmcw_mode.childElement(
"dechirp_reference", 0).isValid() ||
83 fmcw_mode.childElement(
"if_sample_rate", 0).isValid() ||
84 fmcw_mode.childElement(
"if_filter_bandwidth", 0).isValid() ||
85 fmcw_mode.childElement(
"if_filter_transition_width", 0).isValid();
90 const std::string&
owner)
107 if (value <= 0.0 || !std::isfinite(value))
116 return if_chain.sample_rate_hz.has_value() ||
if_chain.filter_bandwidth_hz.has_value() ||
117 if_chain.filter_transition_width_hz.has_value();
122 const std::string&
owner)
126 throw XmlException(
owner +
" declares <dechirp_reference> while dechirp_mode is 'none'.");
130 throw XmlException(
owner +
" declares IF-chain fields while dechirp_mode is 'none'.");
136 if ((
if_chain.filter_bandwidth_hz.has_value() ||
if_chain.filter_transition_width_hz.has_value()) &&
137 !
if_chain.sample_rate_hz.has_value())
141 if (
if_chain.sample_rate_hz.has_value())
146 throw XmlException(
owner +
" <if_sample_rate> must not exceed the simulation sample rate.");
149 if (
if_chain.sample_rate_hz.has_value() &&
if_chain.filter_bandwidth_hz.has_value() &&
152 throw XmlException(
owner +
" <if_filter_bandwidth> must be less than half <if_sample_rate>.");
161 throw XmlException(
owner +
" enables dechirping but does not declare <dechirp_reference>.");
163 if (
fmcw_mode.childElement(
"dechirp_reference", 1).isValid())
174 catch (
const std::exception&
e)
184 if (transmitter_name.has_value() || waveform_name.has_value())
187 " attached dechirp_reference must not set transmitter_name or "
192 if (!transmitter_name.has_value() || transmitter_name->empty() || waveform_name.has_value())
194 throw XmlException(
owner +
" transmitter dechirp_reference requires transmitter_name only.");
199 if (!waveform_name.has_value() || waveform_name->empty() || transmitter_name.has_value())
201 throw XmlException(
owner +
" custom dechirp_reference requires waveform_name only.");
206 throw XmlException(
owner +
" dechirp_reference source must be attached, transmitter, or custom.");
214 const std::string&
owner)
236 catch (
const std::exception&
e)
246 .filter_transition_width_hz =
274 const std::string&
owner)
289 static_assert(std::mt19937::max() <= std::numeric_limits<unsigned>::max(),
290 "std::mt19937 output must fit into unsigned seeds.");
291 return static_cast<unsigned>(master_seeder());
296 const std::string&
owner)
298 if (
const auto it =
ctx.timing_instances.find(
timing_id);
it !=
ctx.timing_instances.end())
304 if (
proto ==
nullptr)
324 return std::stod(
text);
357 const std::unordered_map<std::string, SimId>&
name_map)
392 catch (
const std::exception&
e)
421 template <
typename Setter>
435 template <
typename Setter>
473 [&](
const unsigned value)
480 [&](
const unsigned value)
532 catch (
const std::exception&
e)
547 if (
params_out.utm_zone < GeographicLib::UTMUPS::MINUTMZONE ||
548 params_out.utm_zone > GeographicLib::UTMUPS::MAXUTMZONE)
551 " is invalid; must be in [1, 60].");
563 throw XmlException(
"KML UTM hemisphere '" +
hem_str +
"' is invalid; must be 'N' or 'S'.");
592 "ENU KML frame specified but no <origin> tag found. Using default KML origin at UCT.");
601 catch (
const std::exception&
e)
604 "Could not parse KML <coordinatesystem> from XML: {}. Defaulting KML export to ENU.",
e.what());
622 throw std::runtime_error(
"Sampling rate must be > 0");
652 ctx.world->add(std::move(
wave));
656 auto cw_signal = std::make_unique<fers_signal::CwSignal>();
657 auto wave = std::make_unique<fers_signal::RadarSignal>(
659 ctx.world->add(std::move(
wave));
669 RealType start_frequency_offset = 0.0;
676 std::optional<std::size_t> chirp_count;
683 throw XmlException(
"Waveform '" + name +
"' has an invalid chirp_count.");
685 chirp_count =
static_cast<std::size_t
>(
raw_count);
688 auto fmcw_signal = std::make_unique<fers_signal::FmcwChirpSignal>(
689 chirp_bandwidth, chirp_duration, chirp_period, start_frequency_offset, chirp_count,
direction);
691 auto wave = std::make_unique<fers_signal::RadarSignal>(name, power,
carrier, chirp_duration,
694 ctx.world->add(std::move(
wave));
702 RealType start_frequency_offset = 0.0;
709 std::optional<std::size_t> triangle_count;
716 throw XmlException(
"Waveform '" + name +
"' has an invalid triangle_count.");
718 triangle_count =
static_cast<std::size_t
>(
raw_count);
721 auto fmcw_signal = std::make_unique<fers_signal::FmcwTriangleSignal>(
722 chirp_bandwidth, chirp_duration, start_frequency_offset, triangle_count);
723 auto wave = std::make_unique<fers_signal::RadarSignal>(
726 ctx.world->add(std::move(
wave));
731 throw XmlException(
"Unsupported waveform type for '" + name +
"'");
740 auto timing_obj = std::make_unique<timing::PrototypeTiming>(name,
id);
798 std::unique_ptr<antenna::Antenna>
ant;
803 ant = std::make_unique<antenna::Isotropic>(name,
id);
811 else if (
pattern ==
"gaussian")
816 else if (
pattern ==
"squarehorn")
820 else if (
pattern ==
"parabolic")
838 if (!
antenna.childElement(
"efficiency", 0).isValid())
854 ctx.world->add(std::move(
ant));
884 "MotionPath interpolation type for platform {} not specified. Defaulting to static.",
927 else if (
interp ==
"cubic")
931 else if (
interp ==
"static")
943 "Failed to set RotationPath interpolation type for platform {}. Defaulting to static",
965 const std::string
owner =
994 const std::string
owner = std::format(
"platform '{}' fixedrotation",
platform->getName());
1032 const SimId waveform_id =
1035 if (
wave ==
nullptr)
1037 throw XmlException(
"Waveform ID '" + std::to_string(waveform_id) +
"' not found for transmitter '" + name +
1054 throw XmlException(
"Antenna ID '" + std::to_string(
antenna_id) +
"' not found for transmitter '" + name +
1065 if (
wave->isFmcwFamily())
1075 return ctx.world->getTransmitters().back().get();
1086 throw XmlException(
"Transmitter '" + name +
"' fmcw_mode must not contain dechirp configuration.");
1107 throw XmlException(
"Antenna ID '" + std::to_string(
ant_id) +
"' not found for receiver '" + name +
"'");
1111 if (!
receiver.childElement(
"noise_temp", 0).isValid())
1132 if (window_length <= 0)
1134 throw XmlException(
"<window_length> must be positive for receiver '" + name +
"'");
1140 throw XmlException(
"<prf> must be positive for receiver '" + name +
"'");
1144 if (window_skip < 0)
1146 throw XmlException(
"<window_skip> must not be negative for receiver '" + name +
"'");
1176 return ctx.world->getReceivers().back().get();
1196 throw XmlException(
"Monostatic '" + name +
"' parsed inconsistent transmitter/receiver modes.");
1198 if (
trans->getSignal() !=
nullptr)
1201 "Monostatic '" +
trans->getName() +
"'");
1215 throw XmlException(
"<rcs> element is required in <target>!");
1241 target_obj->setFluctuationModel(std::make_unique<radar::RcsConst>());
1301 auto plat = std::make_unique<radar::Platform>(name,
id);
1315 "Both <rotationpath> and <fixedrotation> are declared for platform {}. Only <rotationpath> will be "
1329 ctx.world->add(std::move(
plat));
1405 if (
root.name() !=
"simulation")
1407 throw XmlException(
"Root element is not <simulation>!");
1418 throw XmlException(
"Duplicate name '" + name +
"' found for " + std::string(kind) +
1419 "; previously used by " +
iter->second +
".");
1426 if (!
ctx.parameters.simulation_name.empty())
1476 std::unordered_map<std::string, SimId>
timing_refs;
1481 for (
const auto& [
id, waveform] :
ctx.world->getWaveforms())
1483 for (
const auto& [
id,
antenna] :
ctx.world->getAntennas())
1485 for (
const auto& [
id,
timing] :
ctx.world->getTimings())
1497 ctx.world->resolveReceiverDechirpReferences();
1499 ctx.world->scheduleInitialEvents();
1509 .loadXmlAntenna = [](
const std::string& name,
const std::string&
filename,
SimId id)
1510 {
return std::make_unique<antenna::XmlAntenna>(name,
filename,
id); },
1511 .loadH5Antenna = [](
const std::string& name,
const std::string&
filename,
SimId id)
1512 {
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.
@ 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.