FERS 1.0.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 <map>
15#include <ranges>
16#include <sstream>
17
19#include "core/logging.h"
20#include "core/world.h"
21#include "math/coord.h"
22#include "math/path.h"
23#include "radar/platform.h"
24#include "radar/radar_obj.h"
25#include "radar/receiver.h"
26#include "radar/target.h"
27#include "radar/transmitter.h"
29#include "signal/radar_signal.h"
30
32{
33 constexpr int TRACK_NUM_DIVISIONS = 100;
34 constexpr int ISOTROPIC_PATTERN_POINTS = 100;
35 constexpr double ISOTROPIC_PATTERN_RADIUS_KM = 20.0;
36 constexpr double DIRECTIONAL_ANTENNA_ARROW_LENGTH_M = 20000.0;
37
38 double sincAntennaGain(const double theta, const double alpha, const double beta, const double gamma)
39 {
40 if (theta == 0.0)
41 {
42 return alpha;
43 }
44 const double gain = alpha * std::pow(std::sin(beta * theta) / (beta * theta), gamma);
45 return gain;
46 }
47
48 double find3DbDropAngle(const double alpha, const double beta, const double gamma)
49 {
50 constexpr std::size_t num_points = 1000;
51 const auto midpoint = static_cast<std::ptrdiff_t>(num_points / 2);
52 std::vector<double> theta(num_points);
53 std::vector<double> gain(num_points);
54 for (std::size_t i = 0; i < num_points; ++i)
55 {
56 theta[i] = -PI + 2.0 * PI * static_cast<double>(i) / static_cast<double>(num_points - 1);
57 gain[i] = sincAntennaGain(theta[i], alpha, beta, gamma);
58 }
59 const auto search_begin = gain.begin() + midpoint;
60 const double max_gain = *std::max_element(search_begin, gain.end());
61 const double max_gain_db = 10.0 * std::log10(max_gain);
62 const double target_gain_db = max_gain_db - 3.0;
63 const double target_gain = std::pow(10.0, target_gain_db / 10.0);
64 const auto min_gain = std::min_element(search_begin, gain.end(), [target_gain](const double a, const double b)
65 { return std::abs(a - target_gain) < std::abs(b - target_gain); });
66 const auto idx = static_cast<std::size_t>(std::distance(search_begin, min_gain));
67 const double angle_3db_drop = theta[static_cast<std::size_t>(midpoint) + idx];
68 return angle_3db_drop * 180.0 / PI;
69 }
70
72 {
73 if (gaussianAnt->getAzimuthScale() <= 0.0)
74 {
76 "Gaussian antenna '{}' has a non-positive azimuth scale ({}). 3dB beamwidth is undefined. KML will "
77 "only show boresight.",
78 gaussianAnt->getName(), gaussianAnt->getAzimuthScale());
79 return 0.0;
80 }
81 const double half_angle_rad = std::sqrt(std::log(2.0) / gaussianAnt->getAzimuthScale());
82 return half_angle_rad * 180.0 / PI;
83 }
84
85 double findParabolic3DbDropAngle(const antenna::Parabolic* parabolicAnt, const double wavelength)
86 {
87 if (parabolicAnt->getDiameter() <= 0.0)
88 {
90 "Parabolic antenna '{}' has a non-positive diameter ({}). This is physically impossible. KML will only "
91 "show boresight.",
92 parabolicAnt->getName(), parabolicAnt->getDiameter());
93 return 0.0;
94 }
95 const double arg = 1.6 * wavelength / (PI * parabolicAnt->getDiameter());
96 if (arg > 1.0)
97 {
99 "Parabolic antenna '{}': The operating wavelength ({:.4f}m) is very large compared to its diameter "
100 "({:.4f}m), resulting in a nearly omnidirectional pattern. KML visualization will cap the 3dB "
101 "half-angle at 90 degrees.",
102 parabolicAnt->getName(), wavelength, parabolicAnt->getDiameter());
103 return 90.0;
104 }
105 const double half_angle_rad = std::asin(arg);
106 return half_angle_rad * 180.0 / PI;
107 }
108
109 double findSquareHorn3DbDropAngle(const antenna::SquareHorn* squarehornAnt, const double wavelength)
110 {
111 if (squarehornAnt->getDimension() <= 0.0)
112 {
114 "SquareHorn antenna '{}' has a non-positive dimension ({}). This is physically impossible. KML will "
115 "only show boresight.",
116 squarehornAnt->getName(), squarehornAnt->getDimension());
117 return 0.0;
118 }
119 const double arg = 1.39155 * wavelength / (PI * squarehornAnt->getDimension());
120 if (arg > 1.0)
121 {
123 "SquareHorn antenna '{}': The operating wavelength ({:.4f}m) is very large compared to its dimension "
124 "({:.4f}m), resulting in a nearly omnidirectional pattern. KML visualization will cap the 3dB "
125 "half-angle at 90 degrees.",
126 squarehornAnt->getName(), wavelength, squarehornAnt->getDimension());
127 return 90.0;
128 }
129 const double half_angle_rad = std::asin(arg);
130 return half_angle_rad * 180.0 / PI;
131 }
132
133 std::string formatCoordinates(const double lon, const double lat, const double alt)
134 {
135 std::stringstream ss;
136 ss << std::fixed << std::setprecision(6) << lon << "," << lat << "," << alt;
137 return ss.str();
138 }
139
140 void calculateDestinationCoordinate(const double startLatitude, const double startLongitude, const double angle,
141 const double distance, double& destLatitude, double& destLongitude)
142 {
143 const GeographicLib::Geodesic& geod = GeographicLib::Geodesic::WGS84();
144 geod.Direct(startLatitude, startLongitude, angle, distance, destLatitude, destLongitude);
145 }
146
147 std::vector<std::pair<double, double>> generateCircleCoordinates(const double lat, const double lon,
148 const double radius_km)
149 {
150 std::vector<std::pair<double, double>> circle_coordinates;
151 for (int i = 0; i < ISOTROPIC_PATTERN_POINTS; i++)
152 {
153 const double bearing = i * 360.0 / ISOTROPIC_PATTERN_POINTS;
154 double new_lat, new_lon;
155 calculateDestinationCoordinate(lat, lon, bearing, radius_km * 1000.0, new_lat, new_lon);
156 circle_coordinates.emplace_back(new_lat, new_lon);
157 }
158 return circle_coordinates;
159 }
160
161 void writeKmlHeaderAndStyles(std::ostream& out, const KmlContext& ctx)
162 {
163 out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
164 out << "<kml xmlns=\"http://www.opengis.net/kml/2.2\" xmlns:gx=\"http://www.google.com/kml/ext/2.2\">\n";
165 out << "<Document>\n";
166 out << " <name>";
167 if (ctx.parameters.simulation_name.empty())
168 {
169 out << "FERS Simulation Visualization";
170 }
171 else
172 {
173 out << ctx.parameters.simulation_name;
174 }
175 out << "</name>\n";
176 out << " <Style "
177 "id=\"receiver\"><IconStyle><Icon><href>https://cdn-icons-png.flaticon.com/512/645/645436.png</href></"
178 "Icon></IconStyle></Style>\n";
179 out << " <Style "
180 "id=\"transmitter\"><IconStyle><Icon><href>https://cdn-icons-png.flaticon.com/128/224/224666.png</"
181 "href></Icon></IconStyle></Style>\n";
182 out << " <Style "
183 "id=\"target\"><IconStyle><Icon><href>https://upload.wikimedia.org/wikipedia/commons/thumb/a/ad/"
184 "Target_red_dot1.svg/1200px-Target_red_dot1.svg.png</href></Icon></IconStyle><LineStyle><width>2</"
185 "width></LineStyle></Style>\n";
186 out << " <Style "
187 "id=\"translucentPolygon\"><LineStyle><color>ff0000ff</color><width>2</width></"
188 "LineStyle><PolyStyle><color>00ffffff</color></PolyStyle></Style>\n";
189 out << " <Style "
190 "id=\"arrowStyle\"><IconStyle><Icon><href>http://maps.google.com/mapfiles/kml/shapes/arrow.png</href></"
191 "Icon><scale>0.5</scale></IconStyle></Style>\n";
192 out << " <Style id=\"lineStyle\"><LineStyle><color>ff0000ff</color><width>2</width></LineStyle></Style>\n";
193 out << " <Style id=\"lineStyleBlue\"><LineStyle><color>ffff0000</color><width>2</width></LineStyle></Style>\n";
194 }
195
196 void writePoint(std::ostream& out, const std::string& indent, const std::string& name, const std::string& styleUrl,
197 const std::string& coordinates, const double objectAltitude, const double referenceAltitude)
198 {
199 out << indent << "<Placemark>\n";
200 out << indent << " <name>" << name << "</name>\n";
201 out << indent << " <styleUrl>" << styleUrl << "</styleUrl>\n";
202 out << indent << " <Point>\n";
203 out << indent << " <coordinates>" << coordinates << "</coordinates>\n";
204 out << indent << " <altitudeMode>absolute</altitudeMode>\n";
205 if (objectAltitude > referenceAltitude)
206 {
207 out << indent << " <extrude>1</extrude>\n";
208 }
209 out << indent << " </Point>\n";
210 out << indent << "</Placemark>\n";
211 }
212
213 void writeAntennaBeamLine(std::ostream& out, const std::string& indent, const std::string& name,
214 const std::string& style, const std::string& startCoords, const std::string& endCoords)
215 {
216 out << indent << "<Placemark>\n";
217 out << indent << " <name>" << name << "</name>\n";
218 out << indent << " <styleUrl>" << style << "</styleUrl>\n";
219 out << indent << " <LineString>\n";
220 out << indent << " <altitudeMode>absolute</altitudeMode>\n";
221 out << indent << " <tessellate>1</tessellate>\n";
222 out << indent << " <coordinates>" << startCoords << " " << endCoords << "</coordinates>\n";
223 out << indent << " </LineString>\n";
224 out << indent << "</Placemark>\n";
225 }
226
227 std::string getPlacemarkStyleForPlatform(const std::vector<const radar::Object*>& objects)
228 {
229 bool has_receiver = false;
230 bool has_transmitter = false;
231 for (const auto* obj : objects)
232 {
233 if (dynamic_cast<const radar::Receiver*>(obj) != nullptr)
234 {
235 has_receiver = true;
236 }
237 if (dynamic_cast<const radar::Transmitter*>(obj) != nullptr)
238 {
239 has_transmitter = true;
240 }
241 }
242
243 if (has_receiver)
244 {
245 return "#receiver";
246 }
247 if (has_transmitter)
248 {
249 return "#transmitter";
250 }
251 return "#target";
252 }
253
254 const radar::Radar* getPrimaryRadar(const std::vector<const radar::Object*>& objects)
255 {
256 for (const auto* obj : objects)
257 {
258 if (const auto* const r = dynamic_cast<const radar::Radar*>(obj))
259 {
260 return r;
261 }
262 }
263 return nullptr;
264 }
265
266 void generateIsotropicAntennaKml(std::ostream& out, const math::Vec3& position, const KmlContext& ctx,
267 const std::string& indent)
268 {
269 double lat, lon, alt_abs;
270 ctx.converter(position, lat, lon, alt_abs);
271
272 const auto circle_coordinates = generateCircleCoordinates(lat, lon, ISOTROPIC_PATTERN_RADIUS_KM);
273
274 out << indent << "<Placemark>\n";
275 out << indent << " <name>Isotropic pattern range</name>\n";
276 out << indent << " <styleUrl>#translucentPolygon</styleUrl>\n";
277 out << indent << " <Polygon>\n";
278 out << indent << " <extrude>1</extrude>\n";
279 out << indent << " <altitudeMode>absolute</altitudeMode>\n";
280 out << indent << " <outerBoundaryIs><LinearRing><coordinates>\n";
281 for (const auto& [pt_lat, pt_lon] : circle_coordinates)
282 {
283 out << indent << " " << formatCoordinates(pt_lon, pt_lat, alt_abs) << "\n";
284 }
285 out << indent << " "
286 << formatCoordinates(circle_coordinates[0].second, circle_coordinates[0].first, alt_abs) << "\n";
287 out << indent << " </coordinates></LinearRing></outerBoundaryIs>\n";
288 out << indent << " </Polygon>\n";
289 out << indent << "</Placemark>\n";
290 }
291
292 void generateDirectionalAntennaKml(std::ostream& out, const radar::Platform* platform, const KmlContext& ctx,
293 const std::optional<double>& angle3DbDropDeg, const std::string& indent)
294 {
295 const auto& first_wp_pos = platform->getMotionPath()->getCoords().front().pos;
296 double start_lat, start_lon, start_alt;
297 ctx.converter(first_wp_pos, start_lat, start_lon, start_alt);
298 const std::string start_coords_str = formatCoordinates(start_lon, start_lat, start_alt);
299
300 const math::SVec3 initial_rotation = platform->getRotationPath()->getPosition(ctx.parameters.start);
301 const double display_azimuth = rotation_angle_utils::internal_azimuth_to_external(
302 initial_rotation.azimuth, ctx.parameters.rotation_angle_unit);
303 const double display_elevation = rotation_angle_utils::internal_elevation_to_external(
304 initial_rotation.elevation, ctx.parameters.rotation_angle_unit);
305
306 const double fers_azimuth_deg = initial_rotation.azimuth * 180.0 / PI;
307 double start_azimuth_deg_kml = 90.0 - fers_azimuth_deg;
308 start_azimuth_deg_kml = std::fmod(start_azimuth_deg_kml, 360.0);
309 if (start_azimuth_deg_kml < 0.0)
310 {
311 start_azimuth_deg_kml += 360.0;
312 }
313
314 const double horizontal_distance = DIRECTIONAL_ANTENNA_ARROW_LENGTH_M * std::cos(initial_rotation.elevation);
315 const double delta_altitude = DIRECTIONAL_ANTENNA_ARROW_LENGTH_M * std::sin(initial_rotation.elevation);
316 const double end_alt = start_alt + delta_altitude;
317
318 double dest_lat, dest_lon;
319 calculateDestinationCoordinate(start_lat, start_lon, start_azimuth_deg_kml, horizontal_distance, dest_lat,
320 dest_lon);
321 const std::string end_coords_str = formatCoordinates(dest_lon, dest_lat, end_alt);
322 out << indent << "<Placemark>\n";
323 out << indent << " <name>Antenna Boresight</name>\n";
324 out << indent << " <ExtendedData>\n";
325 out << indent << " <Data name=\"rotationangleunit\"><value>"
327 out << indent << " <Data name=\"azimuth\"><value>" << display_azimuth << "</value></Data>\n";
328 out << indent << " <Data name=\"elevation\"><value>" << display_elevation << "</value></Data>\n";
329 out << indent << " </ExtendedData>\n";
330 out << indent << " <styleUrl>#lineStyle</styleUrl>\n";
331 out << indent << " <LineString>\n";
332 out << indent << " <altitudeMode>absolute</altitudeMode>\n";
333 out << indent << " <tessellate>1</tessellate>\n";
334 out << indent << " <coordinates>" << start_coords_str << " " << end_coords_str << "</coordinates>\n";
335 out << indent << " </LineString>\n";
336 out << indent << "</Placemark>\n";
337
338 if (angle3DbDropDeg.has_value() && *angle3DbDropDeg > EPSILON)
339 {
340 double side1_lat, side1_lon;
341 calculateDestinationCoordinate(start_lat, start_lon, start_azimuth_deg_kml - *angle3DbDropDeg,
342 horizontal_distance, side1_lat, side1_lon);
343 const std::string side1_coords_str = formatCoordinates(side1_lon, side1_lat, end_alt);
344 writeAntennaBeamLine(out, indent, "Antenna 3dB Beamwidth", "#lineStyleBlue", start_coords_str,
345 side1_coords_str);
346
347 double side2_lat, side2_lon;
348 calculateDestinationCoordinate(start_lat, start_lon, start_azimuth_deg_kml + *angle3DbDropDeg,
349 horizontal_distance, side2_lat, side2_lon);
350 const std::string side2_coords_str = formatCoordinates(side2_lon, side2_lat, end_alt);
351 writeAntennaBeamLine(out, indent, "Antenna 3dB Beamwidth", "#lineStyleBlue", start_coords_str,
352 side2_coords_str);
353 }
354
355 const double arrow_heading = std::fmod(start_azimuth_deg_kml + 180.0, 360.0);
356 out << indent << "<Placemark>\n";
357 out << indent << " <name>Antenna Arrow</name>\n";
358 out << indent << " <styleUrl>#arrowStyle</styleUrl>\n";
359 out << indent << " <Point><coordinates>" << end_coords_str
360 << "</coordinates><altitudeMode>absolute</altitudeMode></Point>\n";
361 out << indent << " <Style>\n";
362 out << indent << " <IconStyle><heading>" << arrow_heading << "</heading></IconStyle>\n";
363 out << indent << " </Style>\n";
364 out << indent << "</Placemark>\n";
365 }
366
367 void generateAntennaKml(std::ostream& out, const radar::Platform* platform, const radar::Radar* radar,
368 const KmlContext& ctx, const std::string& indent)
369 {
370 const antenna::Antenna* ant = radar->getAntenna();
371 if ((ant == nullptr) || platform->getMotionPath()->getCoords().empty())
372 {
373 return;
374 }
375
376 if (dynamic_cast<const antenna::Isotropic*>(ant) != nullptr)
377 {
378 const math::Vec3 initial_pos = platform->getMotionPath()->getCoords().front().pos;
379 generateIsotropicAntennaKml(out, initial_pos, ctx, indent);
380 }
381 else
382 {
383 std::optional<double> angle_3db_drop_deg;
384
385 std::optional<double> wavelength;
386 if (const auto* tx = dynamic_cast<const radar::Transmitter*>(radar))
387 {
388 if (tx->getSignal() != nullptr)
389 {
390 wavelength = ctx.parameters.c / tx->getSignal()->getCarrier();
391 }
392 }
393 else if (const auto* rx = dynamic_cast<const radar::Receiver*>(radar))
394 {
395 if (const auto* attached_tx = dynamic_cast<const radar::Transmitter*>(rx->getAttached()))
396 {
397 if (attached_tx->getSignal() != nullptr)
398 {
399 wavelength = ctx.parameters.c / attached_tx->getSignal()->getCarrier();
400 }
401 }
402 }
403
404 if (const auto* sinc_ant = dynamic_cast<const antenna::Sinc*>(ant))
405 {
406 angle_3db_drop_deg = find3DbDropAngle(sinc_ant->getAlpha(), sinc_ant->getBeta(), sinc_ant->getGamma());
407 }
408 else if (const auto* gaussian_ant = dynamic_cast<const antenna::Gaussian*>(ant))
409 {
410 angle_3db_drop_deg = findGaussian3DbDropAngle(gaussian_ant);
411 }
412 else if (const auto* parabolic_ant = dynamic_cast<const antenna::Parabolic*>(ant))
413 {
414 if (wavelength)
415 {
416 angle_3db_drop_deg = findParabolic3DbDropAngle(parabolic_ant, *wavelength);
417 }
418 }
419 else if (const auto* squarehorn_ant = dynamic_cast<const antenna::SquareHorn*>(ant))
420 {
421 if (wavelength)
422 {
423 angle_3db_drop_deg = findSquareHorn3DbDropAngle(squarehorn_ant, *wavelength);
424 }
425 }
426 else if ((dynamic_cast<const antenna::XmlAntenna*>(ant) != nullptr) ||
427 (dynamic_cast<const antenna::H5Antenna*>(ant) != nullptr))
428 {
430 "KML visualization for antenna '{}' ('{}') is symbolic. "
431 "Only the boresight direction is shown, as a 3dB beamwidth is not calculated from file-based "
432 "patterns.",
433 ant->getName(), dynamic_cast<const antenna::XmlAntenna*>(ant) ? "xml" : "file");
434 }
435
436 generateDirectionalAntennaKml(out, platform, ctx, angle_3db_drop_deg, indent);
437 }
438 }
439
440 void generateDynamicPathKml(std::ostream& out, const radar::Platform* platform, const std::string& styleUrl,
441 const double refAlt, const KmlContext& ctx, const std::string& indent)
442 {
443 const math::Path* path = platform->getMotionPath();
444 const auto& waypoints = path->getCoords();
445
446 double first_alt_abs;
447 {
448 double lat, lon;
449 ctx.converter(waypoints.front().pos, lat, lon, first_alt_abs);
450 }
451
452 out << indent << "<Placemark>\n";
453 out << indent << " <name>" << platform->getName() << " Path</name>\n";
454 out << indent << " <styleUrl>" << styleUrl << "</styleUrl>\n";
455 out << indent << " <gx:Track>\n";
456 out << indent << " <altitudeMode>absolute</altitudeMode>\n";
457 if (first_alt_abs > refAlt)
458 {
459 out << indent << " <extrude>1</extrude>\n";
460 }
461
462 const double start_time = waypoints.front().t;
463 const double end_time = waypoints.back().t;
464
465 if (const double time_diff = end_time - start_time; time_diff <= 0.0)
466 {
467 const math::Vec3 p_pos = path->getPosition(start_time);
468 double p_lon, p_lat, p_alt_abs;
469 ctx.converter(p_pos, p_lat, p_lon, p_alt_abs);
470 out << indent << " <when>" << start_time << "</when>\n";
471 out << indent << " <gx:coord>" << p_lon << " " << p_lat << " " << p_alt_abs << "</gx:coord>\n";
472 }
473 else
474 {
475 const double time_step = time_diff / TRACK_NUM_DIVISIONS;
476 for (int i = 0; i <= TRACK_NUM_DIVISIONS; ++i)
477 {
478 const double current_time = start_time + i * time_step;
479 const math::Vec3 p_pos = path->getPosition(current_time);
480 double p_lon, p_lat, p_alt_abs;
481 ctx.converter(p_pos, p_lat, p_lon, p_alt_abs);
482 out << indent << " <when>" << current_time << "</when>\n";
483 out << indent << " <gx:coord>" << p_lon << " " << p_lat << " " << p_alt_abs << "</gx:coord>\n";
484 }
485 }
486
487 out << indent << " </gx:Track>\n";
488 out << indent << "</Placemark>\n";
489 }
490
491 void generateTrackEndpointsKml(std::ostream& out, const radar::Platform* platform, const double refAlt,
492 const KmlContext& ctx, const std::string& indent)
493 {
494 const math::Path* path = platform->getMotionPath();
495 if (path->getCoords().size() <= 1)
496 {
497 return;
498 }
499
500 const auto& [start_wp_pos, start_wp_t] = path->getCoords().front();
501 const auto& [end_wp_pos, end_wp_t] = path->getCoords().back();
502
503 double start_lat, start_lon, start_alt_abs;
504 ctx.converter(start_wp_pos, start_lat, start_lon, start_alt_abs);
505 const std::string start_coordinates = formatCoordinates(start_lon, start_lat, start_alt_abs);
506
507 double end_lat, end_lon, end_alt_abs;
508 ctx.converter(end_wp_pos, end_lat, end_lon, end_alt_abs);
509 const std::string end_coordinates = formatCoordinates(end_lon, end_lat, end_alt_abs);
510
511 writePoint(out, indent, "Start: " + platform->getName(), "#target", start_coordinates, start_alt_abs, refAlt);
512 writePoint(out, indent, "End: " + platform->getName(), "#target", end_coordinates, end_alt_abs, refAlt);
513 }
514
515 void generateStaticPlacemarkKml(std::ostream& out, const radar::Platform* platform, const std::string& styleUrl,
516 const double refAlt, const KmlContext& ctx, const std::string& indent)
517 {
518 const auto& [first_wp_pos, first_wp_t] = platform->getMotionPath()->getCoords().front();
519 double lat, lon, alt_abs;
520 ctx.converter(first_wp_pos, lat, lon, alt_abs);
521 const std::string coordinates = formatCoordinates(lon, lat, alt_abs);
522
523 out << indent << "<Placemark>\n";
524 out << indent << " <name>" << platform->getName() << "</name>\n";
525 out << indent << " <styleUrl>" << styleUrl << "</styleUrl>\n";
526 out << indent << " <LookAt>\n";
527 out << indent << " <longitude>" << lon << "</longitude>\n";
528 out << indent << " <latitude>" << lat << "</latitude>\n";
529 out << indent << " <altitude>" << alt_abs << "</altitude>\n";
530 out << indent << " <heading>-148.41</heading><tilt>40.55</tilt><range>500.65</range>\n";
531 out << indent << " </LookAt>\n";
532 out << indent << " <Point>\n";
533 out << indent << " <coordinates>" << coordinates << "</coordinates>\n";
534 out << indent << " <altitudeMode>absolute</altitudeMode>\n";
535 if (alt_abs > refAlt)
536 {
537 out << indent << " <extrude>1</extrude>\n";
538 }
539 out << indent << " </Point>\n";
540 out << indent << "</Placemark>\n";
541 }
542
543 void generatePlatformPathKml(std::ostream& out, const radar::Platform* platform, const std::string& style,
544 const double refAlt, const KmlContext& ctx, const std::string& indent)
545 {
546 const auto path_type = platform->getMotionPath()->getType();
547 const bool is_dynamic =
549
550 if (is_dynamic)
551 {
552 generateDynamicPathKml(out, platform, style, refAlt, ctx, indent);
553 generateTrackEndpointsKml(out, platform, refAlt, ctx, indent);
554 }
555 else
556 {
557 generateStaticPlacemarkKml(out, platform, style, refAlt, ctx, indent);
558 }
559 }
560
561 void processPlatform(std::ostream& out, const radar::Platform* platform,
562 const std::vector<const radar::Object*>& objects, const KmlContext& ctx,
563 const double referenceAltitude, const std::string& indent)
564 {
565 if (platform->getMotionPath()->getCoords().empty())
566 {
567 return;
568 }
569
570 out << indent << "<Folder>\n";
571 out << indent << " <name>" << platform->getName() << "</name>\n";
572
573 const std::string inner_indent = indent + " ";
574 const auto placemark_style = getPlacemarkStyleForPlatform(objects);
575
576 if (const auto* radar_obj = getPrimaryRadar(objects))
577 {
578 generateAntennaKml(out, platform, radar_obj, ctx, inner_indent);
579 }
580
581 generatePlatformPathKml(out, platform, placemark_style, referenceAltitude, ctx, inner_indent);
582
583 out << indent << "</Folder>\n";
584 }
585
586 void generateKmlToStream(std::ostream& out, const core::World& world, const KmlContext& ctx)
587 {
588 std::map<const radar::Platform*, std::vector<const radar::Object*>> platform_to_objects;
589 const auto group_objects = [&](const auto& objectCollection)
590 {
591 for (const auto& obj_ptr : objectCollection)
592 {
593 platform_to_objects[obj_ptr->getPlatform()].push_back(obj_ptr.get());
594 }
595 };
596
597 group_objects(world.getReceivers());
598 group_objects(world.getTransmitters());
599 group_objects(world.getTargets());
600
601 double reference_latitude = ctx.parameters.origin_latitude;
602 double reference_longitude = ctx.parameters.origin_longitude;
603 double reference_altitude = ctx.parameters.origin_altitude;
604
606 {
607 bool ref_set = false;
608 for (const auto& platform : platform_to_objects | std::views::keys)
609 {
610 if (!platform->getMotionPath()->getCoords().empty())
611 {
612 const math::Vec3& first_pos = platform->getMotionPath()->getCoords().front().pos;
613 ctx.converter(first_pos, reference_latitude, reference_longitude, reference_altitude);
614 ref_set = true;
615 break;
616 }
617 }
618 if (!ref_set)
619 {
620 reference_latitude = ctx.parameters.origin_latitude;
621 reference_longitude = ctx.parameters.origin_longitude;
622 reference_altitude = ctx.parameters.origin_altitude;
623 }
624 }
625
626 writeKmlHeaderAndStyles(out, ctx);
627
628 out << " <Folder>\n";
629 out << " <name>Reference Coordinate</name>\n";
630 out << " <description>Placemarks for various elements in the FERSXML file. All Placemarks are "
631 "situated relative to this reference point.</description>\n";
632 out << " <LookAt>\n";
633 out << " <longitude>" << reference_longitude << "</longitude>\n";
634 out << " <latitude>" << reference_latitude << "</latitude>\n";
635 out << " <altitude>" << reference_altitude << "</altitude>\n";
636 out << " <heading>-148.41</heading><tilt>40.55</tilt><range>10000</range>\n";
637 out << " </LookAt>\n";
638
639 const std::string platform_indent = " ";
640 for (const auto& [platform, objects] : platform_to_objects)
641 {
642 processPlatform(out, platform, objects, ctx, reference_altitude, platform_indent);
643 }
644
645 out << " </Folder>\n";
646 out << "</Document>\n";
647 out << "</kml>\n";
648 }
649}
Header file defining various types of antennas and their gain patterns.
Abstract base class representing an antenna.
std::string getName() const noexcept
Retrieves the name of the antenna.
Represents a Gaussian-shaped antenna gain pattern.
RealType getAzimuthScale() const noexcept
Gets the azimuth scale factor.
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.
RealType getDiameter() const noexcept
Gets the diameter of the parabolic reflector.
Represents a sinc function-based antenna gain pattern.
Represents a square horn antenna.
RealType getDimension() const noexcept
Gets the dimension of the square horn.
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:200
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
Represents a path with coordinates and allows for various interpolation methods.
Definition path.h:30
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:83
InterpType getType() const noexcept
Retrieves the current interpolation type of the path.
Definition path.h:76
SVec3 getPosition(RealType t) const
Gets the rotational position at a given time.
A class representing a vector in spherical coordinates.
RealType elevation
The elevation angle of the vector.
RealType azimuth
The azimuth angle of the vector.
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
Represents a radar system on a platform.
Definition radar_obj.h:47
Manages radar signal reception and response processing.
Definition receiver.h:37
Represents a radar transmitter system.
Definition transmitter.h:33
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
Definition parameters.h:294
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.
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.
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 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
RealType internal_azimuth_to_external(const RealType azimuth, const params::RotationAngleUnit unit) noexcept
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.
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
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
double origin_latitude
Geodetic origin latitude.
Definition parameters.h:62
Context data required during KML generation.
params::Parameters parameters
A copy of the global simulation parameters.
ConverterFunc converter
Function used to translate simulation Cartesian space into geographic coords.
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.