FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
kml_generator_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
8
9#include <GeographicLib/Geodesic.hpp>
10#include <algorithm>
11#include <cmath>
12#include <cstddef>
13#include <iomanip>
14#include <limits>
15#include <map>
16#include <ranges>
17#include <sstream>
18
20#include "core/logging.h"
21#include "core/world.h"
22#include "math/coord.h"
23#include "math/path.h"
24#include "radar/platform.h"
25#include "radar/radar_obj.h"
26#include "radar/receiver.h"
27#include "radar/target.h"
28#include "radar/transmitter.h"
30#include "signal/radar_signal.h"
31
33{
34 constexpr int TRACK_NUM_DIVISIONS = 100;
35 constexpr int ISOTROPIC_PATTERN_POINTS = 100;
36 constexpr double ISOTROPIC_PATTERN_RADIUS_KM = 20.0;
37 constexpr double DIRECTIONAL_ANTENNA_ARROW_LENGTH_M = 20000.0;
38
39 namespace
40 {
41 [[nodiscard]] double unassignedCoordinate() noexcept { return std::numeric_limits<double>::quiet_NaN(); }
42 }
43
44 double sincAntennaGain(const double theta, const double alpha, const double beta, const double gamma)
45 {
46 if (theta == 0.0)
47 {
48 return alpha;
49 }
50 const double gain = alpha * std::pow(std::sin(beta * theta) / (beta * theta), gamma);
51 return gain;
52 }
53
54 double find3DbDropAngle(const double alpha, const double beta, const double gamma)
55 {
56 constexpr std::size_t num_points = 1000;
57 const auto midpoint = static_cast<std::ptrdiff_t>(num_points / 2);
58 std::vector<double> theta(num_points);
59 std::vector<double> gain(num_points);
60 for (std::size_t i = 0; i < num_points; ++i)
61 {
62 theta[i] = -PI + 2.0 * PI * static_cast<double>(i) / static_cast<double>(num_points - 1);
64 }
65 const auto search_begin = gain.begin() + midpoint;
66 const double max_gain = *std::max_element(search_begin, gain.end());
67 const double max_gain_db = 10.0 * std::log10(max_gain);
68 const double target_gain_db = max_gain_db - 3.0;
69 const double target_gain = std::pow(10.0, target_gain_db / 10.0);
70 const auto min_gain = std::min_element(search_begin, gain.end(), [target_gain](const double a, const double b)
71 { return std::abs(a - target_gain) < std::abs(b - target_gain); });
72 const auto idx = static_cast<std::size_t>(std::distance(search_begin, min_gain));
73 const double angle_3db_drop = theta[static_cast<std::size_t>(midpoint) + idx];
74 return angle_3db_drop * 180.0 / PI;
75 }
76
78 {
79 if (gaussianAnt->getAzimuthScale() <= 0.0)
80 {
82 "Gaussian antenna '{}' has a non-positive azimuth scale ({}). 3dB beamwidth is undefined. KML will "
83 "only show boresight.",
84 gaussianAnt->getName(), gaussianAnt->getAzimuthScale());
85 return 0.0;
86 }
87 const double half_angle_rad = std::sqrt(std::log(2.0) / gaussianAnt->getAzimuthScale());
88 return half_angle_rad * 180.0 / PI;
89 }
90
92 {
93 if (parabolicAnt->getDiameter() <= 0.0)
94 {
96 "Parabolic antenna '{}' has a non-positive diameter ({}). This is physically impossible. KML will only "
97 "show boresight.",
98 parabolicAnt->getName(), parabolicAnt->getDiameter());
99 return 0.0;
100 }
101 const double arg = 1.6 * wavelength / (PI * parabolicAnt->getDiameter());
102 if (arg > 1.0)
103 {
105 "Parabolic antenna '{}': The operating wavelength ({:.4f}m) is very large compared to its diameter "
106 "({:.4f}m), resulting in a nearly omnidirectional pattern. KML visualization will cap the 3dB "
107 "half-angle at 90 degrees.",
108 parabolicAnt->getName(), wavelength, parabolicAnt->getDiameter());
109 return 90.0;
110 }
111 const double half_angle_rad = std::asin(arg);
112 return half_angle_rad * 180.0 / PI;
113 }
114
116 {
117 if (squarehornAnt->getDimension() <= 0.0)
118 {
120 "SquareHorn antenna '{}' has a non-positive dimension ({}). This is physically impossible. KML will "
121 "only show boresight.",
122 squarehornAnt->getName(), squarehornAnt->getDimension());
123 return 0.0;
124 }
125 const double arg = 1.39155 * wavelength / (PI * squarehornAnt->getDimension());
126 if (arg > 1.0)
127 {
129 "SquareHorn antenna '{}': The operating wavelength ({:.4f}m) is very large compared to its dimension "
130 "({:.4f}m), resulting in a nearly omnidirectional pattern. KML visualization will cap the 3dB "
131 "half-angle at 90 degrees.",
132 squarehornAnt->getName(), wavelength, squarehornAnt->getDimension());
133 return 90.0;
134 }
135 const double half_angle_rad = std::asin(arg);
136 return half_angle_rad * 180.0 / PI;
137 }
138
139 std::string formatCoordinates(const double lon, const double lat, const double alt)
140 {
141 std::stringstream ss;
142 ss << std::fixed << std::setprecision(6) << lon << "," << lat << "," << alt;
143 return ss.str();
144 }
145
146 void calculateDestinationCoordinate(const double startLatitude, const double startLongitude, const double angle,
147 const double distance, double& destLatitude, double& destLongitude)
148 {
149 const GeographicLib::Geodesic& geod = GeographicLib::Geodesic::WGS84();
151 }
152
153 std::vector<std::pair<double, double>> generateCircleCoordinates(const double lat, const double lon,
154 const double radius_km)
155 {
156 std::vector<std::pair<double, double>> circle_coordinates;
157 for (int i = 0; i < ISOTROPIC_PATTERN_POINTS; i++)
158 {
159 const double bearing = i * 360.0 / ISOTROPIC_PATTERN_POINTS;
160 double new_lat = unassignedCoordinate();
161 double new_lon = unassignedCoordinate();
163 circle_coordinates.emplace_back(new_lat, new_lon);
164 }
165 return circle_coordinates;
166 }
167
168 void writeKmlHeaderAndStyles(std::ostream& out, const KmlContext& ctx)
169 {
170 out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
171 out << "<kml xmlns=\"http://www.opengis.net/kml/2.2\" xmlns:gx=\"http://www.google.com/kml/ext/2.2\">\n";
172 out << "<Document>\n";
173 out << " <name>";
174 if (ctx.parameters.simulation_name.empty())
175 {
176 out << "FERS Simulation Visualization";
177 }
178 else
179 {
180 out << ctx.parameters.simulation_name;
181 }
182 out << "</name>\n";
183 out << " <Style "
184 "id=\"receiver\"><IconStyle><Icon><href>https://cdn-icons-png.flaticon.com/512/645/645436.png</href></"
185 "Icon></IconStyle></Style>\n";
186 out << " <Style "
187 "id=\"transmitter\"><IconStyle><Icon><href>https://cdn-icons-png.flaticon.com/128/224/224666.png</"
188 "href></Icon></IconStyle></Style>\n";
189 out << " <Style "
190 "id=\"target\"><IconStyle><Icon><href>https://upload.wikimedia.org/wikipedia/commons/thumb/a/ad/"
191 "Target_red_dot1.svg/1200px-Target_red_dot1.svg.png</href></Icon></IconStyle><LineStyle><width>2</"
192 "width></LineStyle></Style>\n";
193 out << " <Style "
194 "id=\"translucentPolygon\"><LineStyle><color>ff0000ff</color><width>2</width></"
195 "LineStyle><PolyStyle><color>00ffffff</color></PolyStyle></Style>\n";
196 out << " <Style "
197 "id=\"arrowStyle\"><IconStyle><Icon><href>http://maps.google.com/mapfiles/kml/shapes/arrow.png</href></"
198 "Icon><scale>0.5</scale></IconStyle></Style>\n";
199 out << " <Style id=\"lineStyle\"><LineStyle><color>ff0000ff</color><width>2</width></LineStyle></Style>\n";
200 out << " <Style id=\"lineStyleBlue\"><LineStyle><color>ffff0000</color><width>2</width></LineStyle></Style>\n";
201 }
202
203 void writePoint(std::ostream& out, const std::string& indent, const std::string& name, const std::string& styleUrl,
204 const std::string& coordinates, const double objectAltitude, const double referenceAltitude)
205 {
206 out << indent << "<Placemark>\n";
207 out << indent << " <name>" << name << "</name>\n";
208 out << indent << " <styleUrl>" << styleUrl << "</styleUrl>\n";
209 out << indent << " <Point>\n";
210 out << indent << " <coordinates>" << coordinates << "</coordinates>\n";
211 out << indent << " <altitudeMode>absolute</altitudeMode>\n";
213 {
214 out << indent << " <extrude>1</extrude>\n";
215 }
216 out << indent << " </Point>\n";
217 out << indent << "</Placemark>\n";
218 }
219
220 void writeAntennaBeamLine(std::ostream& out, const std::string& indent, const std::string& name,
221 const std::string& style, const std::string& startCoords, const std::string& endCoords)
222 {
223 out << indent << "<Placemark>\n";
224 out << indent << " <name>" << name << "</name>\n";
225 out << indent << " <styleUrl>" << style << "</styleUrl>\n";
226 out << indent << " <LineString>\n";
227 out << indent << " <altitudeMode>absolute</altitudeMode>\n";
228 out << indent << " <tessellate>1</tessellate>\n";
229 out << indent << " <coordinates>" << startCoords << " " << endCoords << "</coordinates>\n";
230 out << indent << " </LineString>\n";
231 out << indent << "</Placemark>\n";
232 }
233
234 std::string getPlacemarkStyleForPlatform(const std::vector<const radar::Object*>& objects)
235 {
236 bool has_receiver = false;
237 bool has_transmitter = false;
238 for (const auto* obj : objects)
239 {
240 if (dynamic_cast<const radar::Receiver*>(obj) != nullptr)
241 {
242 has_receiver = true;
243 }
244 if (dynamic_cast<const radar::Transmitter*>(obj) != nullptr)
245 {
246 has_transmitter = true;
247 }
248 }
249
250 if (has_receiver)
251 {
252 return "#receiver";
253 }
254 if (has_transmitter)
255 {
256 return "#transmitter";
257 }
258 return "#target";
259 }
260
261 const radar::Radar* getPrimaryRadar(const std::vector<const radar::Object*>& objects)
262 {
263 for (const auto* obj : objects)
264 {
265 if (const auto* const r = dynamic_cast<const radar::Radar*>(obj))
266 {
267 return r;
268 }
269 }
270 return nullptr;
271 }
272
274 const std::string& indent)
275 {
276 double lat = unassignedCoordinate();
277 double lon = unassignedCoordinate();
278 double alt_abs = unassignedCoordinate();
279 ctx.converter(position, lat, lon, alt_abs);
280
282
283 out << indent << "<Placemark>\n";
284 out << indent << " <name>Isotropic pattern range</name>\n";
285 out << indent << " <styleUrl>#translucentPolygon</styleUrl>\n";
286 out << indent << " <Polygon>\n";
287 out << indent << " <extrude>1</extrude>\n";
288 out << indent << " <altitudeMode>absolute</altitudeMode>\n";
289 out << indent << " <outerBoundaryIs><LinearRing><coordinates>\n";
290 for (const auto& [pt_lat, pt_lon] : circle_coordinates)
291 {
292 out << indent << " " << formatCoordinates(pt_lon, pt_lat, alt_abs) << "\n";
293 }
294 out << indent << " "
296 out << indent << " </coordinates></LinearRing></outerBoundaryIs>\n";
297 out << indent << " </Polygon>\n";
298 out << indent << "</Placemark>\n";
299 }
300
302 const std::optional<double>& angle3DbDropDeg, const std::string& indent)
303 {
304 const auto& first_wp_pos = platform->getMotionPath()->getCoords().front().pos;
310
311 const math::SVec3 initial_rotation = platform->getRotationPath()->getPosition(ctx.parameters.start);
313 initial_rotation.azimuth, ctx.parameters.rotation_angle_unit);
315 initial_rotation.elevation, ctx.parameters.rotation_angle_unit);
316
317 const double fers_azimuth_deg = initial_rotation.azimuth * 180.0 / PI;
320 if (start_azimuth_deg_kml < 0.0)
321 {
322 start_azimuth_deg_kml += 360.0;
323 }
324
326 const double delta_altitude = DIRECTIONAL_ANTENNA_ARROW_LENGTH_M * std::sin(initial_rotation.elevation);
327 const double end_alt = start_alt + delta_altitude;
328
332 dest_lon);
334 out << indent << "<Placemark>\n";
335 out << indent << " <name>Antenna Boresight</name>\n";
336 out << indent << " <ExtendedData>\n";
337 out << indent << " <Data name=\"rotationangleunit\"><value>"
338 << params::rotationAngleUnitToken(ctx.parameters.rotation_angle_unit) << "</value></Data>\n";
339 out << indent << " <Data name=\"azimuth\"><value>" << display_azimuth << "</value></Data>\n";
340 out << indent << " <Data name=\"elevation\"><value>" << display_elevation << "</value></Data>\n";
341 out << indent << " </ExtendedData>\n";
342 out << indent << " <styleUrl>#lineStyle</styleUrl>\n";
343 out << indent << " <LineString>\n";
344 out << indent << " <altitudeMode>absolute</altitudeMode>\n";
345 out << indent << " <tessellate>1</tessellate>\n";
346 out << indent << " <coordinates>" << start_coords_str << " " << end_coords_str << "</coordinates>\n";
347 out << indent << " </LineString>\n";
348 out << indent << "</Placemark>\n";
349
350 if (angle3DbDropDeg.has_value() && *angle3DbDropDeg > EPSILON)
351 {
357 writeAntennaBeamLine(out, indent, "Antenna 3dB Beamwidth", "#lineStyleBlue", start_coords_str,
359
365 writeAntennaBeamLine(out, indent, "Antenna 3dB Beamwidth", "#lineStyleBlue", start_coords_str,
367 }
368
369 const double arrow_heading = std::fmod(start_azimuth_deg_kml + 180.0, 360.0);
370 out << indent << "<Placemark>\n";
371 out << indent << " <name>Antenna Arrow</name>\n";
372 out << indent << " <styleUrl>#arrowStyle</styleUrl>\n";
373 out << indent << " <Point><coordinates>" << end_coords_str
374 << "</coordinates><altitudeMode>absolute</altitudeMode></Point>\n";
375 out << indent << " <Style>\n";
376 out << indent << " <IconStyle><heading>" << arrow_heading << "</heading></IconStyle>\n";
377 out << indent << " </Style>\n";
378 out << indent << "</Placemark>\n";
379 }
380
381 std::optional<double> antennaCarrierWavelength(const radar::Radar* radar, const KmlContext& ctx)
382 {
383 if (const auto* tx = dynamic_cast<const radar::Transmitter*>(radar))
384 {
385 if (tx->getSignal() != nullptr)
386 {
387 return ctx.parameters.c / tx->getSignal()->getCarrier();
388 }
389 return std::nullopt;
390 }
391 if (const auto* rx = dynamic_cast<const radar::Receiver*>(radar))
392 {
393 if (const auto* attached_tx = dynamic_cast<const radar::Transmitter*>(rx->getAttached()))
394 {
395 if (attached_tx->getSignal() != nullptr)
396 {
397 return ctx.parameters.c / attached_tx->getSignal()->getCarrier();
398 }
399 }
400 }
401 return std::nullopt;
402 }
403
404 std::optional<double> antenna3DbDropAngle(const antenna::Antenna* ant, const std::optional<double> wavelength)
405 {
406 if (const auto* sinc_ant = dynamic_cast<const antenna::Sinc*>(ant))
407 {
408 return find3DbDropAngle(sinc_ant->getAlpha(), sinc_ant->getBeta(), sinc_ant->getGamma());
409 }
410 if (const auto* gaussian_ant = dynamic_cast<const antenna::Gaussian*>(ant))
411 {
413 }
414 if (const auto* parabolic_ant = dynamic_cast<const antenna::Parabolic*>(ant))
415 {
416 if (wavelength)
417 {
419 }
420 return std::nullopt;
421 }
422 if (const auto* squarehorn_ant = dynamic_cast<const antenna::SquareHorn*>(ant))
423 {
424 if (wavelength)
425 {
427 }
428 return std::nullopt;
429 }
430 return std::nullopt;
431 }
432
434 {
435 if ((dynamic_cast<const antenna::XmlAntenna*>(ant) == nullptr) &&
436 (dynamic_cast<const antenna::H5Antenna*>(ant) == nullptr))
437 {
438 return;
439 }
441 "KML visualization for antenna '{}' ('{}') is symbolic. "
442 "Only the boresight direction is shown, as a 3dB beamwidth is not calculated from file-based "
443 "patterns.",
444 ant->getName(), dynamic_cast<const antenna::XmlAntenna*>(ant) ? "xml" : "file");
445 }
446
448 const KmlContext& ctx, const std::string& indent)
449 {
450 const antenna::Antenna* ant = radar->getAntenna();
451 if ((ant == nullptr) || platform->getMotionPath()->getCoords().empty())
452 {
453 return;
454 }
455
456 if (dynamic_cast<const antenna::Isotropic*>(ant) != nullptr)
457 {
458 const math::Vec3 initial_pos = platform->getMotionPath()->getCoords().front().pos;
460 }
461 else
462 {
467 }
468 }
469
470 void generateDynamicPathKml(std::ostream& out, const radar::Platform* platform, const std::string& styleUrl,
471 const double refAlt, const KmlContext& ctx, const std::string& indent)
472 {
473 const math::Path* path = platform->getMotionPath();
474 const auto& waypoints = path->getCoords();
475
477 {
478 double lat = unassignedCoordinate();
479 double lon = unassignedCoordinate();
480 ctx.converter(waypoints.front().pos, lat, lon, first_alt_abs);
481 }
482
483 out << indent << "<Placemark>\n";
484 out << indent << " <name>" << platform->getName() << " Path</name>\n";
485 out << indent << " <styleUrl>" << styleUrl << "</styleUrl>\n";
486 out << indent << " <gx:Track>\n";
487 out << indent << " <altitudeMode>absolute</altitudeMode>\n";
488 if (first_alt_abs > refAlt)
489 {
490 out << indent << " <extrude>1</extrude>\n";
491 }
492
493 const double start_time = waypoints.front().t;
494 const double end_time = waypoints.back().t;
495
496 if (const double time_diff = end_time - start_time; time_diff <= 0.0)
497 {
498 const math::Vec3 p_pos = path->getPosition(start_time);
499 double p_lon = unassignedCoordinate();
500 double p_lat = unassignedCoordinate();
502 ctx.converter(p_pos, p_lat, p_lon, p_alt_abs);
503 out << indent << " <when>" << start_time << "</when>\n";
504 out << indent << " <gx:coord>" << p_lon << " " << p_lat << " " << p_alt_abs << "</gx:coord>\n";
505 }
506 else
507 {
508 const double time_step = time_diff / TRACK_NUM_DIVISIONS;
509 for (int i = 0; i <= TRACK_NUM_DIVISIONS; ++i)
510 {
511 const double current_time = start_time + i * time_step;
513 double p_lon = unassignedCoordinate();
514 double p_lat = unassignedCoordinate();
516 ctx.converter(p_pos, p_lat, p_lon, p_alt_abs);
517 out << indent << " <when>" << current_time << "</when>\n";
518 out << indent << " <gx:coord>" << p_lon << " " << p_lat << " " << p_alt_abs << "</gx:coord>\n";
519 }
520 }
521
522 out << indent << " </gx:Track>\n";
523 out << indent << "</Placemark>\n";
524 }
525
526 void generateTrackEndpointsKml(std::ostream& out, const radar::Platform* platform, const double refAlt,
527 const KmlContext& ctx, const std::string& indent)
528 {
529 const math::Path* path = platform->getMotionPath();
530 if (path->getCoords().size() <= 1)
531 {
532 return;
533 }
534
535 const auto& [start_wp_pos, start_wp_t] = path->getCoords().front();
536 const auto& [end_wp_pos, end_wp_t] = path->getCoords().back();
537
543
544 double end_lat = unassignedCoordinate();
545 double end_lon = unassignedCoordinate();
549
550 writePoint(out, indent, "Start: " + platform->getName(), "#target", start_coordinates, start_alt_abs, refAlt);
551 writePoint(out, indent, "End: " + platform->getName(), "#target", end_coordinates, end_alt_abs, refAlt);
552 }
553
554 void generateStaticPlacemarkKml(std::ostream& out, const radar::Platform* platform, const std::string& styleUrl,
555 const double refAlt, const KmlContext& ctx, const std::string& indent)
556 {
557 const auto& [first_wp_pos, first_wp_t] = platform->getMotionPath()->getCoords().front();
558 double lat = unassignedCoordinate();
559 double lon = unassignedCoordinate();
560 double alt_abs = unassignedCoordinate();
561 ctx.converter(first_wp_pos, lat, lon, alt_abs);
562 const std::string coordinates = formatCoordinates(lon, lat, alt_abs);
563
564 out << indent << "<Placemark>\n";
565 out << indent << " <name>" << platform->getName() << "</name>\n";
566 out << indent << " <styleUrl>" << styleUrl << "</styleUrl>\n";
567 out << indent << " <LookAt>\n";
568 out << indent << " <longitude>" << lon << "</longitude>\n";
569 out << indent << " <latitude>" << lat << "</latitude>\n";
570 out << indent << " <altitude>" << alt_abs << "</altitude>\n";
571 out << indent << " <heading>-148.41</heading><tilt>40.55</tilt><range>500.65</range>\n";
572 out << indent << " </LookAt>\n";
573 out << indent << " <Point>\n";
574 out << indent << " <coordinates>" << coordinates << "</coordinates>\n";
575 out << indent << " <altitudeMode>absolute</altitudeMode>\n";
576 if (alt_abs > refAlt)
577 {
578 out << indent << " <extrude>1</extrude>\n";
579 }
580 out << indent << " </Point>\n";
581 out << indent << "</Placemark>\n";
582 }
583
584 void generatePlatformPathKml(std::ostream& out, const radar::Platform* platform, const std::string& style,
585 const double refAlt, const KmlContext& ctx, const std::string& indent)
586 {
587 const auto path_type = platform->getMotionPath()->getType();
588 const bool is_dynamic =
590
591 if (is_dynamic)
592 {
595 }
596 else
597 {
599 }
600 }
601
602 void processPlatform(std::ostream& out, const radar::Platform* platform,
603 const std::vector<const radar::Object*>& objects, const KmlContext& ctx,
604 const double referenceAltitude, const std::string& indent)
605 {
606 if (platform->getMotionPath()->getCoords().empty())
607 {
608 return;
609 }
610
611 out << indent << "<Folder>\n";
612 out << indent << " <name>" << platform->getName() << "</name>\n";
613
614 const std::string inner_indent = indent + " ";
616
617 if (const auto* radar_obj = getPrimaryRadar(objects))
618 {
620 }
621
623
624 out << indent << "</Folder>\n";
625 }
626
627 void generateKmlToStream(std::ostream& out, const core::World& world, const KmlContext& ctx)
628 {
629 std::map<const radar::Platform*, std::vector<const radar::Object*>> platform_to_objects;
630 const auto group_objects = [&](const auto& objectCollection)
631 {
632 for (const auto& obj_ptr : objectCollection)
633 {
634 platform_to_objects[obj_ptr->getPlatform()].push_back(obj_ptr.get());
635 }
636 };
637
640 group_objects(world.getTargets());
641
642 double reference_latitude = ctx.parameters.origin_latitude;
643 double reference_longitude = ctx.parameters.origin_longitude;
644 double reference_altitude = ctx.parameters.origin_altitude;
645
646 if (ctx.parameters.coordinate_frame != params::CoordinateFrame::ENU)
647 {
648 bool ref_set = false;
649 for (const auto& platform : platform_to_objects | std::views::keys)
650 {
651 if (!platform->getMotionPath()->getCoords().empty())
652 {
653 const math::Vec3& first_pos = platform->getMotionPath()->getCoords().front().pos;
655 ref_set = true;
656 break;
657 }
658 }
659 if (!ref_set)
660 {
661 reference_latitude = ctx.parameters.origin_latitude;
662 reference_longitude = ctx.parameters.origin_longitude;
663 reference_altitude = ctx.parameters.origin_altitude;
664 }
665 }
666
668
669 out << " <Folder>\n";
670 out << " <name>Reference Coordinate</name>\n";
671 out << " <description>Placemarks for various elements in the FERSXML file. All Placemarks are "
672 "situated relative to this reference point.</description>\n";
673 out << " <LookAt>\n";
674 out << " <longitude>" << reference_longitude << "</longitude>\n";
675 out << " <latitude>" << reference_latitude << "</latitude>\n";
676 out << " <altitude>" << reference_altitude << "</altitude>\n";
677 out << " <heading>-148.41</heading><tilt>40.55</tilt><range>10000</range>\n";
678 out << " </LookAt>\n";
679
680 const std::string platform_indent = " ";
681 for (const auto& [platform, objects] : platform_to_objects)
682 {
684 }
685
686 out << " </Folder>\n";
687 out << "</Document>\n";
688 out << "</kml>\n";
689 }
690}
Header file defining various types of antennas and their gain patterns.
Vec3 position
Abstract base class representing an antenna.
Represents a Gaussian-shaped antenna gain pattern.
Represents an antenna whose gain pattern is loaded from a HDF5 file.
Represents an isotropic antenna with uniform gain in all directions.
Represents a parabolic reflector antenna.
Represents a sinc function-based antenna gain pattern.
Represents a square horn antenna.
Represents an antenna whose gain pattern is defined by an XML file.
The World class manages the simulator environment.
Definition world.h:39
const std::vector< std::unique_ptr< radar::Target > > & getTargets() const noexcept
Retrieves the list of radar targets.
Definition world.h:226
const std::vector< std::unique_ptr< radar::Transmitter > > & getTransmitters() const noexcept
Retrieves the list of radar transmitters.
Definition world.h:246
const std::vector< std::unique_ptr< radar::Receiver > > & getReceivers() const noexcept
Retrieves the list of radar receivers.
Definition world.h:236
Represents a path with coordinates and allows for various interpolation methods.
Definition path.h:31
Vec3 getPosition(RealType t) const
Retrieves the position at a given time along the path.
Definition path.cpp:36
const std::vector< Coord > & getCoords() const noexcept
Gets the list of coordinates in the path.
Definition path.h:84
@ INTERP_LINEAR
Linearly interpolate between neighboring coordinates.
@ INTERP_CUBIC
Cubically interpolate between neighboring coordinates.
A class representing a vector in spherical coordinates.
A class representing a vector in rectangular coordinates.
Represents a simulation platform with motion and rotation paths.
Definition platform.h:32
Represents a radar system on a platform.
Definition radar_obj.h:50
Manages radar signal reception and response processing.
Definition receiver.h:47
Represents a radar transmitter system.
Definition transmitter.h:34
constexpr RealType EPSILON
Machine epsilon for real numbers.
Definition config.h:51
constexpr RealType PI
Mathematical constant π (pi).
Definition config.h:43
Coordinate and rotation structure operations.
Utility definitions and functions for generating KML files from simulation scenarios.
Header file for the logging system.
#define LOG(level,...)
Definition logging.h:19
@ WARNING
Warning level for potentially harmful situations.
@ INFO
Info level for informational messages.
@ ENU
East-North-Up local tangent plane (default)
constexpr std::string_view rotationAngleUnitToken(const RotationAngleUnit unit) noexcept
Converts a rotation angle unit to its XML token.
Definition parameters.h:352
void generateTrackEndpointsKml(std::ostream &out, const radar::Platform *platform, const double refAlt, const KmlContext &ctx, const std::string &indent)
Generates KML rendering start and end pushpins for a moving platform's track.
void writePoint(std::ostream &out, const std::string &indent, const std::string &name, const std::string &styleUrl, const std::string &coordinates, const double objectAltitude, const double referenceAltitude)
Writes a KML <Point> placemark to the output stream.
std::string formatCoordinates(const double lon, const double lat, const double alt)
Formats coordinates into a comma-separated string suitable for KML <coordinates>.
std::vector< std::pair< double, double > > generateCircleCoordinates(const double lat, const double lon, const double radius_km)
Generates a collection of points tracing a circle around a center coordinate.
constexpr double ISOTROPIC_PATTERN_RADIUS_KM
double findSquareHorn3DbDropAngle(const antenna::SquareHorn *squarehornAnt, const double wavelength)
Calculates the 3dB drop angle for a Square Horn antenna.
void generateDirectionalAntennaKml(std::ostream &out, const radar::Platform *platform, const KmlContext &ctx, const std::optional< double > &angle3DbDropDeg, const std::string &indent)
Renders the visual pointing representation for a directional (beam) antenna.
double findParabolic3DbDropAngle(const antenna::Parabolic *parabolicAnt, const double wavelength)
Calculates the 3dB drop angle for a Parabolic antenna.
std::string getPlacemarkStyleForPlatform(const std::vector< const radar::Object * > &objects)
Determines the proper KML style definition ID to use based on the platform's attached objects.
double sincAntennaGain(const double theta, const double alpha, const double beta, const double gamma)
Calculates a normalized sinc-based antenna gain mapping.
double findGaussian3DbDropAngle(const antenna::Gaussian *gaussianAnt)
Calculates the 3dB drop angle for a Gaussian antenna.
std::optional< double > antenna3DbDropAngle(const antenna::Antenna *ant, const std::optional< double > wavelength)
void generateKmlToStream(std::ostream &out, const core::World &world, const KmlContext &ctx)
Master entry point designed to convert the comprehensive simulation world state into a valid KML docu...
double find3DbDropAngle(const double alpha, const double beta, const double gamma)
Numerically determines the 3dB drop angle for a parameterized generic antenna.
void generateDynamicPathKml(std::ostream &out, const radar::Platform *platform, const std::string &styleUrl, const double refAlt, const KmlContext &ctx, const std::string &indent)
Generates KML for a continuously moving dynamic platform path.
void generateAntennaKml(std::ostream &out, const radar::Platform *platform, const radar::Radar *radar, const KmlContext &ctx, const std::string &indent)
Dispatch function that selects and generates the appropriate KML for a given radar's antenna.
const radar::Radar * getPrimaryRadar(const std::vector< const radar::Object * > &objects)
Identifies the primary radar object within a platform for styling and direction tracking.
std::optional< double > antennaCarrierWavelength(const radar::Radar *radar, const KmlContext &ctx)
void processPlatform(std::ostream &out, const radar::Platform *platform, const std::vector< const radar::Object * > &objects, const KmlContext &ctx, const double referenceAltitude, const std::string &indent)
Orchestrates full processing and rendering of an individual platform into the KML stream.
void calculateDestinationCoordinate(const double startLatitude, const double startLongitude, const double angle, const double distance, double &destLatitude, double &destLongitude)
Calculates a destination coordinate given a starting position, bearing, and distance.
void writeKmlHeaderAndStyles(std::ostream &out, const KmlContext &ctx)
Writes the standard KML preamble and style definitions to the output stream.
void generateStaticPlacemarkKml(std::ostream &out, const radar::Platform *platform, const std::string &styleUrl, const double refAlt, const KmlContext &ctx, const std::string &indent)
Generates a simple static placemark KML for a non-moving platform.
void logSymbolicAntennaKml(const antenna::Antenna *ant)
void generatePlatformPathKml(std::ostream &out, const radar::Platform *platform, const std::string &style, const double refAlt, const KmlContext &ctx, const std::string &indent)
Dispatches the generation of a platform's path representation (static vs dynamic).
void writeAntennaBeamLine(std::ostream &out, const std::string &indent, const std::string &name, const std::string &style, const std::string &startCoords, const std::string &endCoords)
Writes a visual cone/beam line representing the antenna look direction.
constexpr double DIRECTIONAL_ANTENNA_ARROW_LENGTH_M
void generateIsotropicAntennaKml(std::ostream &out, const math::Vec3 &position, const KmlContext &ctx, const std::string &indent)
Renders the visual representation for an isotropic antenna into the output stream.
RealType internal_elevation_to_external(const RealType elevation, const params::RotationAngleUnit unit) noexcept
Converts an internal elevation angle to the external unit.
RealType internal_azimuth_to_external(const RealType azimuth, const params::RotationAngleUnit unit) noexcept
Converts an internal azimuth angle to the external compass convention.
Provides the definition and functionality of the Path class for handling coordinate-based paths with ...
Defines the Platform class used in radar simulation.
Defines the Radar class and associated functionality.
Classes for handling radar waveforms and signals.
Radar Receiver class for managing signal reception and response handling.
math::Vec3 max
RealType b
RealType a
Context data required during KML generation.
Defines classes for radar targets and their Radar Cross-Section (RCS) models.
Header file for the Transmitter class in the radar namespace.
Header file for the World class in the simulator.