23#include "antenna_pattern_dtd.h"
24#include "antenna_pattern_xsd.h"
43 enum class AxisGainFormat
51 AxisUnit unit{AxisUnit::Radians};
52 AxisGainFormat format{AxisGainFormat::Linear};
55 bool format_explicit{};
56 bool symmetry_explicit{};
63 std::size_t sample_count{};
66 std::string toLowerCopy(std::string value)
68 std::transform(value.begin(), value.end(), value.begin(),
69 [](
const unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
73 RealType parseRealValue(
const std::string_view text,
const std::string_view context)
75 const std::string value(text);
81 parsed = std::stod(value, &index);
83 catch (
const std::exception&)
85 throw std::runtime_error(
"Invalid numeric value for " + std::string(context) +
": '" + value +
"'.");
88 while (index < value.size() && (std::isspace(
static_cast<unsigned char>(value[index])) != 0))
93 if (index != value.size())
95 throw std::runtime_error(
"Invalid numeric value for " + std::string(context) +
": '" + value +
"'.");
98 if (!std::isfinite(parsed))
100 throw std::runtime_error(
"Non-finite numeric value for " + std::string(context) +
".");
103 return static_cast<RealType>(parsed);
106 const char* axisUnitName(
const AxisUnit unit)
noexcept {
return unit == AxisUnit::Degrees ?
"deg" :
"rad"; }
108 const char* axisFormatName(
const AxisGainFormat format)
noexcept
110 return format == AxisGainFormat::DBi ?
"dBi" :
"linear";
118 AxisMetadata parseAxisMetadata(
const XmlElement& axisXml)
120 AxisMetadata metadata;
121 const std::string axis_name(axisXml.
name());
125 metadata.unit_explicit =
true;
126 const std::string value = toLowerCopy(*unit_attr);
129 metadata.unit = AxisUnit::Radians;
131 else if (value ==
"deg")
133 metadata.unit = AxisUnit::Degrees;
137 throw std::runtime_error(
"Unsupported unit '" + *unit_attr +
"' on <" + axis_name +
"> axis.");
143 metadata.format_explicit =
true;
144 const std::string value = toLowerCopy(*format_attr);
145 if (value ==
"linear")
147 metadata.format = AxisGainFormat::Linear;
149 else if (value ==
"dbi")
151 metadata.format = AxisGainFormat::DBi;
155 throw std::runtime_error(
"Unsupported format '" + *format_attr +
"' on <" + axis_name +
"> axis.");
161 metadata.symmetry_explicit =
true;
162 const std::string value = toLowerCopy(*symmetry_attr);
163 if (value ==
"mirrored")
167 else if (value ==
"none")
173 throw std::runtime_error(
"Unsupported symmetry '" + *symmetry_attr +
"' on <" + axis_name +
"> axis.");
192 return std::sin(theta) / theta;
213 throw std::runtime_error(
"XML antenna pattern is missing a required gain axis.");
216 const AxisMetadata metadata = parseAxisMetadata(axisXml);
217 const std::string axis_name(axisXml.
name());
221 throw std::runtime_error(
"XML antenna <" + axis_name +
"> axis must contain at least one <gainsample>.");
224 RealType min_angle = std::numeric_limits<RealType>::max();
225 RealType max_angle = std::numeric_limits<RealType>::lowest();
227 std::size_t sample_count = 0;
234 angle_element.
isValid() && gain_element.isValid())
236 const RealType raw_angle = parseRealValue(angle_element.
getText(),
"<" + axis_name +
"> sample angle");
237 const RealType raw_gain = parseRealValue(gain_element.getText(),
"<" + axis_name +
"> sample gain");
238 const RealType angle = metadata.unit == AxisUnit::Degrees ? raw_angle * (
PI / 180.0) : raw_angle;
240 metadata.format == AxisGainFormat::DBi ? std::pow(10.0, raw_gain / 10.0) : raw_gain;
242 if (!std::isfinite(angle))
244 throw std::runtime_error(
"Converted <" + axis_name +
"> sample angle is non-finite.");
246 if (!std::isfinite(gain))
248 throw std::runtime_error(
"Converted <" + axis_name +
"> sample gain is non-finite.");
252 throw std::runtime_error(
"Converted <" + axis_name +
"> sample gain must be non-negative.");
256 min_angle = std::min(min_angle, angle);
257 max_angle = std::max(max_angle, angle);
258 max_gain = std::max(max_gain, gain);
261 else if (sample.
name() ==
"gainsample")
263 throw std::runtime_error(
"Each <gainsample> in <" + axis_name +
"> must contain <angle> and <gain>.");
269 AxisLoadResult result;
270 result.max_gain = max_gain;
271 result.symmetry = metadata.symmetry;
272 result.sample_count = sample_count;
274 if (metadata.symmetry_explicit)
278 throw std::runtime_error(
"XML antenna <" + axis_name +
279 "> axis uses symmetry='mirrored' but defines negative sample angles.");
283 throw std::runtime_error(
284 "XML antenna <" + axis_name +
285 "> axis uses symmetry='none' but does not span both negative and positive angles.");
288 else if (min_angle < 0.0)
290 if (!(min_angle < 0.0 && max_angle > 0.0))
292 throw std::runtime_error(
"XML antenna <" + axis_name +
293 "> axis contains negative sample angles but does not span both sides of zero "
294 "for direct signed-angle lookup.");
299 const char* unit_source = metadata.unit_explicit ?
"explicit" :
"legacy default";
300 const char* format_source = metadata.format_explicit ?
"explicit" :
"legacy default";
301 const char* symmetry_source = metadata.symmetry_explicit
306 "XML antenna axis '{}' using unit='{}' ({}) format='{}' ({}) symmetry='{}' ({}) with {} samples.",
307 axis_name, axisUnitName(metadata.unit), unit_source, axisFormatName(metadata.format), format_source,
308 axisSymmetryName(result.symmetry), symmetry_source, result.sample_count);
320 LOG(Level::INFO,
"Using greater than unity antenna efficiency.");
327 SVec3 normangle(angle);
329 return std::acos(dotProduct(
Vec3(normangle),
Vec3(refangle)));
334 const SVec3 a = angle - refangle;
340 const RealType theta = getAngle(angle, refangle);
341 const RealType sinc_val = sinc(_beta * theta);
342 const RealType gain_pattern = std::pow(std::abs(sinc_val), _gamma);
343 return _alpha * gain_pattern * getEfficiencyFactor();
348 const RealType ge = 4 *
PI * std::pow(_dimension, 2) / std::pow(wavelength, 2);
349 const RealType x =
PI * _dimension * std::sin(getAngle(angle, refangle)) / wavelength;
350 return ge * std::pow(sinc(x), 2) * getEfficiencyFactor();
355 const RealType ge = std::pow(
PI * _diameter / wavelength, 2);
356 const RealType x =
PI * _diameter * std::sin(getAngle(angle, refangle)) / wavelength;
357 return ge * std::pow(2 * j1C(x), 2) * getEfficiencyFactor();
361 const AxisSymmetry symmetry)
const noexcept
363 if (symmetry == AxisSymmetry::Mirrored)
372 const SVec3 delta_angle = angle - refangle;
374 const std::optional<RealType> azi_value =
375 lookupAxisGain(_azi_samples.get(), delta_angle.
azimuth, _azi_symmetry);
377 if (
const std::optional<RealType> elev_value =
378 lookupAxisGain(_elev_samples.get(), delta_angle.
elevation, _elev_symmetry);
379 azi_value && elev_value)
384 LOG(Level::FATAL,
"Could not get antenna gain value");
385 throw std::runtime_error(
"Could not get antenna gain value");
388 void XmlAntenna::loadAntennaDescription(
const std::string_view filename)
390 _filename = filename;
392 if (!doc.
loadFile(std::string(filename)))
394 LOG(Level::FATAL,
"Could not load antenna description {}", filename.data());
395 throw std::runtime_error(
"Could not load antenna description");
401 const AxisLoadResult elev_result = loadAntennaGainAxis(_elev_samples.get(), root.childElement(
"elevation", 0));
402 const AxisLoadResult azi_result = loadAntennaGainAxis(_azi_samples.get(), root.childElement(
"azimuth", 0));
404 _elev_symmetry = elev_result.symmetry;
405 _azi_symmetry = azi_result.symmetry;
407 _max_gain = std::max(azi_result.max_gain, elev_result.max_gain);
408 if (!std::isfinite(_max_gain) || _max_gain <= 0.0)
410 throw std::runtime_error(
"XML antenna pattern peak linear gain must be greater than zero.");
412 _elev_samples->divide(_max_gain);
413 _azi_samples->divide(_max_gain);
420 const SVec3& pattern_angle = angle - refangle;
422 const double ex1 = (pattern_angle.
azimuth +
PI) / two_pi;
423 const double ey1 = (pattern_angle.
elevation +
PI) / two_pi;
425 const auto calc_grid_point = [](
const double value,
const std::size_t size)
427 const double grid_size =
static_cast<double>(size - 1);
428 const double x1 = std::floor(value * grid_size) / grid_size;
429 const double x2 = std::min(x1 + 1.0 /
static_cast<double>(size), 1.0);
430 return std::pair{x1, x2};
433 const std::size_t size_azi = _pattern.size();
434 const std::size_t size_elev = _pattern[0].size();
438 const auto [x1, x2] = calc_grid_point(ex1, size_azi);
439 const auto [y1, y2] = calc_grid_point(ey1, size_elev);
441 const double t = (ex1 - x1) / (x2 - x1);
442 const double u = (ey1 - y1) / (y2 - y1);
444 const auto calc_array_index = [](
const double value,
const std::size_t size)
445 {
return std::min(
static_cast<std::size_t
>(std::floor(value *
static_cast<double>(size))), size - 1); };
447 const std::size_t arr_x = calc_array_index(x1, size_azi);
448 const std::size_t arr_y = calc_array_index(y1, size_elev);
450 const RealType interp = (1.0 - t) * (1.0 - u) * _pattern[arr_x][arr_y] +
451 t * (1.0 - u) * _pattern[(arr_x + 1) % size_azi][arr_y] +
452 t * u * _pattern[(arr_x + 1) % size_azi][(arr_y + 1) % size_elev] +
453 (1.0 - t) * u * _pattern[arr_x][(arr_y + 1) % size_elev];
Header file defining various types of antennas and their gain patterns.
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::optional< std::string > getOptionalAttribute(const XmlElement &element, const std::string_view name)
Get the value of an optional attribute.
std::string_view name() const noexcept
Get the name of the XML element.
bool isValid() const noexcept
Check if the XML element is valid.
xmlNodePtr getNode() const noexcept
Get the underlying XML node pointer.
std::string getText() const
Get the text content of the XML element.
static RealType getAngle(const math::SVec3 &angle, const math::SVec3 &refangle) noexcept
Computes the angle between the input and reference angles.
void setEfficiencyFactor(RealType loss) noexcept
Sets the efficiency factor of the antenna.
RealType getEfficiencyFactor() const noexcept
Retrieves the efficiency factor of the antenna.
RealType getGain(const math::SVec3 &angle, const math::SVec3 &refangle, RealType wavelength) const noexcept override
Computes the gain of the Gaussian antenna.
RealType getGain(const math::SVec3 &angle, const math::SVec3 &refangle, RealType) const override
Computes the gain of the antenna based on the input angle and reference angle.
RealType getGain(const math::SVec3 &angle, const math::SVec3 &refangle, RealType wavelength) const noexcept override
Computes the gain of the parabolic antenna.
RealType getGain(const math::SVec3 &angle, const math::SVec3 &refangle, RealType wavelength) const noexcept override
Computes the gain of the sinc antenna based on the input parameters.
RealType getGain(const math::SVec3 &angle, const math::SVec3 &refangle, RealType wavelength) const noexcept override
Computes the gain of the square horn antenna.
RealType getGain(const math::SVec3 &angle, const math::SVec3 &refangle, RealType wavelength) const override
Computes the gain of the antenna based on the input angle and reference angle.
Wrapper class for managing interpolation sets using smart pointers.
std::optional< T > getValueAt(T x) const noexcept
Retrieves the interpolated value at a given point.
void insertSample(T x, T y) const noexcept
Inserts a sample point into the interpolation set.
A class representing a vector in spherical coordinates.
RealType elevation
The elevation angle of the vector.
RealType azimuth
The azimuth angle of the vector.
RealType length
The length of the vector.
A class representing a vector in rectangular coordinates.
Global configuration file for the project.
double RealType
Type for real numbers.
constexpr RealType EPSILON
Machine epsilon for real numbers.
constexpr RealType PI
Mathematical constant π (pi).
Classes and operations for 3D geometry.
Wrapper for managing XML documents and elements using libxml2.
Header file for the logging system.
RealType besselJ1(const RealType x) noexcept
Computes the Bessel function of the first kind (order 1) for a given value.
@ TRACE
Trace level for detailed debugging information.
Utility functions for mathematical and system operations.