1 // Copyright (C) 2009 - 2012  Mathias Froehlich
2 //
3 // This program is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU General Public License as
5 // published by the Free Software Foundation; either version 2 of the
6 // License, or (at your option) any later version.
7 //
8 // This program is distributed in the hope that it will be useful, but
9 // WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16 
17 #ifdef HAVE_CONFIG_H
18 #include <config.h>
19 #endif
20 
21 #include <cstdio>
22 
23 #include <simgear/misc/sg_path.hxx>
24 #include <simgear/debug/logstream.hxx>
25 
26 #include "AIObject.hxx"
27 #include "AIManager.hxx"
28 
29 #include "HLAMPAircraft.hxx"
30 #include "HLAMPAircraftClass.hxx"
31 #include "AIPhysics.hxx"
32 
33 namespace fgai {
34 
35 #if 0
36 class AIVehiclePhysics : public AIPhysics {
37 public:
38     AIVehiclePhysics(const SGLocationd& location, const SGVec3d& linearBodyVelocity = SGVec3d::zeros(),
39                      const SGVec3d& angularBodyVelocity = SGVec3d::zeros()) :
40         AIPhysics(location, linearBodyVelocity, angularBodyVelocity)
41     {
42         setMass(1);
43         setInertia(1, 1, 1);
44     }
45     virtual ~AIVehiclePhysics()
46     { }
47 
48     double getMass() const
49     { return _mass; }
50     void setMass(double mass)
51     { _mass = mass; }
52 
53     void setInertia(double ixx, double iyy, double izz, double ixy = 0, double ixz = 0, double iyz = 0)
54     {
55         _inertia = SGMatrixd(ixx, ixy, ixz, 0,
56                              ixy, iyy, iyz, 0,
57                              ixz, iyz, izz, 0,
58                              0,   0,   0, 1);
59         invert(_inertiaInverse, _inertia);
60     }
61 
62 protected:
63     void advanceByBodyForce(const double& dt,
64                             const SGVec3d& force,
65                             const SGVec3d& torque)
66     {
67         SGVec3d linearVelocity = getLinearBodyVelocity();
68         SGVec3d angularVelocity = getAngularBodyVelocity();
69 
70         SGVec3d linearAcceleration = (1/_mass)*(force - cross(angularVelocity, linearVelocity));
71         SGVec3d angularAcceleration = _inertiaInverse.xformVec(torque - cross(angularVelocity, _inertia.xformVec(angularVelocity)));
72 
73         advanceByBodyAcceleration(dt, linearAcceleration, angularAcceleration);
74     }
75 
76     SGVec3d getGravityAcceleration() const
77     {
78         return SGQuatd::fromLonLat(getGeodPosition()).backTransform(SGVec3d(0, 0, 9.81));
79     }
80 
81 private:
82     // unsimulated motion, hide this for this kind of class here
83     using AIPhysics::advanceByBodyAcceleration;
84     using AIPhysics::advanceByBodyVelocity;
85     using AIPhysics::advanceToCartPosition;
86 
87     double _mass;
88     // FIXME this is a symmetric 3x3 matrix ...
89     SGMatrixd _inertia;
90     SGMatrixd _inertiaInverse;
91 };
92 
93 // FIXME Totally unfinished simple aerodynamics model for an ai aircraft
94 // also just here for a sketch of an idea
95 class AIAircraftPhysics : public AIVehiclePhysics {
96 public:
97     AIAircraftPhysics(const SGLocationd& location, const SGVec3d& linearBodyVelocity = SGVec3d::zeros(),
98                       const SGVec3d& angularBodyVelocity = SGVec3d::zeros()) :
99         AIVehiclePhysics(location, linearBodyVelocity, angularBodyVelocity)
100     { }
101     virtual ~AIAircraftPhysics()
102     { }
103 
104     double getElevatorPosition() const
105     { return _elevatorPosition; }
106     void setElevatorPosition(double elevatorPosition)
107     { _elevatorPosition = elevatorPosition; }
108 
109     double getAileronPosition() const
110     { return _aileronPosition; }
111     void setAileronPosition(double aileronPosition)
112     { _aileronPosition = aileronPosition; }
113 
114     double getRudderPosition() const
115     { return _rudderPosition; }
116     void setRudderPosition(double rudderPosition)
117     { _rudderPosition = rudderPosition; }
118 
119     // double getFlapsPosition() const
120     // { return _flapsPosition; }
121     // void setFlapsPosition(double flapsPosition)
122     // { _flapsPosition = flapsPosition; }
123 
124     double getThrust() const
125     { return _thrust; }
126     void setThrust(double thrust)
127     { _thrust = thrust; }
128 
129     virtual void update(AIObject& object, const SGTimeStamp& dt)
130     {
131         // const AIEnvironment& environment = object.getEnvironment();
132         const AIEnvironment environment;
133 
134         /// Compute the forces on the aircraft. This is a very simple fdm.
135 
136         // The velocity of the aircraft wrt the surrounding fluid
137         SGVec3d windVelocity = getLinearBodyVelocity();
138         windVelocity -= getOrientation().transform(environment.getWindVelocity());
139 
140         // The true airspeed of the bird
141         double airSpeed = norm(windVelocity);
142         // simple density with(out FIXME) altitude
143         double density = environment.getDensity();
144         // The dynaimc pressure - most important value in aerodynamics
145         double dynamicPressure = 0.5*density*airSpeed*airSpeed;
146 
147         // The angle of attack and sideslip angle
148         double alpha = 0, beta = 0;
149         if (1e-3 < SGMiscd::max(fabs(windVelocity[0]), fabs(windVelocity[2])))
150             alpha = atan2(windVelocity[2], windVelocity[0]);
151         double uw = sqrt(windVelocity[0]*windVelocity[0] + windVelocity[2]*windVelocity[2]);
152         if (1e-3 < SGMiscd::max(fabs(windVelocity[1]), fabs(uw)))
153             beta = atan2(windVelocity[1], uw);
154         // Transform from the stability axis to body axis
155         SGQuatd stabilityToBody = SGQuatd::fromEulerRad(beta, alpha, 0);
156 
157         // We assume a simple angular dependency for the
158         // lift, drag and side force coefficients.
159 
160         // lift for alpha = 0
161         double _Cl0 = 0.5;
162         // lift at stall angle of attack
163         double _ClStall = 2;
164         // stall angle of attack
165         double _alphaStall = 18;
166         // Drag for alpha = 0
167         double _Cd0 = 0.05;
168         // Drag for alpha = 90
169         double _Cd90 = 1.05;
170         // Side force coefficient for maximum side angle
171         double _Cs90 = 1.05;
172 
173         /// FIXME
174         double _area = 1;
175         SGVec3d _aerodynamicReferencePoint(0, 0, 0);
176         SGVec3d _centerOfGravity(0, 0, 0);
177 
178         // So compute the lift drag and side force coefficient for the
179         // current stream conditions.
180         double Cl = _Cl0 + (_ClStall - _Cl0)*sin(SGMiscd::clip(90/_alphaStall*alpha, -0.5*SGMiscd::pi(), SGMiscd::pi()));
181         double Cd = _Cd0 + (_Cd90 - _Cd0)*(0.5 - 0.5*cos(2*alpha));
182         double Cs = _Cs90*sin(beta);
183 
184         // Forces in the stability axes
185         double lift = Cl*dynamicPressure*_area;
186         double drag = Cd*dynamicPressure*_area;
187         double side = Cs*dynamicPressure*_area;
188 
189         // Torque in body axes
190         double p = 0;
191         double q = 0;
192         double r = 0;
193 
194         // Compute the force in stability coordinates ...
195         SGVec3d stabilityForce(-drag, side, -lift);
196         // ... and torque in body coordinates
197         SGVec3d torque(p, q, r);
198 
199         SGVec3d force = stabilityToBody.transform(stabilityForce);
200         torque += cross(force, _aerodynamicReferencePoint);
201 
202         std::pair<SGVec3d, SGVec3d> velocity;
203         for (_GearVector::iterator i = _gearVector.begin(); i != _gearVector.end(); ++i) {
204             std::pair<SGVec3d, SGVec3d> torqueForce;
205             torqueForce = i->force(getLocation(), velocity, object);
206             torque += torqueForce.first;
207             force += torqueForce.second;
208         }
209 
210         // Transform the torque back to the center of gravity
211         torque -= cross(force, _centerOfGravity);
212 
213         // Advance the equations of motion.
214         advanceByBodyForce(dt.toSecs(), force, torque);
215     }
216 
217     /// The normalized elevator position
218     double _elevatorPosition;
219     /// The normalized aileron position
220     double _aileronPosition;
221     /// The normalized rudder position
222     double _rudderPosition;
223     // /// The normalized flaps position
224     // double _flapsPosition;
225     /// Normalized thrust
226     double _thrust;
227 
228     struct _Gear {
229         SGVec3d _position;
230         SGVec3d _direction;
231         double _spring;
232         double _damping;
233 
234         std::pair<SGVec3d, SGVec3d>
235         force(const SGLocationd& location, const std::pair<SGVec3d, SGVec3d>& velocity, AIObject& object)
236         {
237             SGVec3d start = location.getAbsolutePosition(_position - _direction);
238             SGVec3d end = location.getAbsolutePosition(_position);
239             SGLineSegmentd lineSegment(start, end);
240 
241             SGVec3d point;
242             SGVec3d normal;
243             // if (!object.getGroundIntersection(point, normal, lineSegment))
244             //     return std::pair<SGVec3d, SGVec3d>(SGVec3d(0, 0, 0), SGVec3d(0, 0, 0));
245 
246             // FIXME just now only the spring force ...
247 
248             // The compression length
249             double compressLength = norm(point - start);
250             SGVec3d springForce = -_spring*compressLength*_direction;
251 
252             SGVec3d dampingForce(0, 0, 0);
253 
254             SGVec3d force = springForce + dampingForce;
255             SGVec3d torque(0, 0, 0);
256 
257             return std::pair<SGVec3d, SGVec3d>(torque, force);
258         }
259     };
260 
261     typedef std::vector<_Gear> _GearVector;
262     _GearVector _gearVector;
263 
264     /// The total mass of the bird in kg. No fluel is burned.
265     /// Some sensible inertia values are derived from the mass.
266     // double _mass;
267     /// The thrust to mass ratio which tells us someting about
268     /// the possible accelerations.
269     // double _thrustMassRatio;
270 
271     /// The stall speed
272     // double _stallSpeed;
273     // double _maximumSpeed;
274     // // double _approachSpeed;
275     // // double _takeoffSpeed;
276     // // double _cruiseSpeed;
277     /// The maximum density altitude this aircraft can fly
278     // double _densityAltitudeLimit;
279 
280     /// statistical evaluation shows:
281     /// wingarea = C*wingspan^2, C in [0.1, 0.4], say 0.2
282     /// ixx = C*wingarea*mass, C in [1e-3, 1e-2]
283     /// iyy = C*wingarea*mass, C in [1e-2, 0.02]
284     /// izz = C*wingarea*mass, C in [1e-2, 0.02]
285     /// Hmm, let's say, weight relates to wingarea?
286     /// probably, since lift is linear dependent on wingarea
287 
288     /// So, define a 'size' in meters.
289     /// the derive
290     ///  wingspan = size
291     ///  wingarea = 0.2*size*size
292     ///  mass = C*wingarea
293     ///  ixx = 0.005*wingarea*mass
294     ///  iyy = 0.05*wingarea*mass
295     ///  izz = 0.05*wingarea*mass
296 
297 
298     /// an other idea:
299     /// define a bird of some weight class. That means mass given.
300     /// Then derive
301     ///  i* ??? must be mass^2 accodring to the thoughts above
302     /// Then do Cl, Cd, Cs.
303     ///  according to approach speed at sea level with 5 deg aoa and 2,5 deg glideslope and 25 % thrust.
304     ///  according to cruise altitude and cruise speed at 75% thrust compute this at altitude
305     ///  interpolate between these two sets of C*'s based on altitude.
306 };
307 #endif
308 
309 /// An automated lego aircraft, constant linear and angular speed
310 class AIOgel : public AIObject {
311 public:
AIOgel(const SGGeod & geod)312     AIOgel(const SGGeod& geod) :
313         _geod(geod),
314         _radius(500),
315         _turnaroundTime(3*60),
316         _velocity(10)
317     { }
~AIOgel()318     virtual ~AIOgel()
319     { }
320 
init(AIManager & manager)321     virtual void init(AIManager& manager)
322     {
323         AIObject::init(manager);
324 
325         SGLocationd location(SGVec3d::fromGeod(_geod), SGQuatd::fromLonLat(_geod));
326         SGVec3d linearVelocity(_velocity, 0, 0);
327         SGVec3d angularVelocity(0, 0, SGMiscd::twopi()/_turnaroundTime);
328         setPhysics(new AIPhysics(location, linearVelocity, angularVelocity));
329 
330         HLAMPAircraftClass* objectClass = dynamic_cast<HLAMPAircraftClass*>(manager.getObjectClass("MPAircraft"));
331         if (!objectClass)
332             return;
333         _objectInstance = new HLAMPAircraft(objectClass);
334         if (!_objectInstance.valid())
335             return;
336         _objectInstance->registerInstance();
337         _objectInstance->setModelPath("Aircraft/ogel/Models/SinglePiston.xml");
338 
339         manager.schedule(*this, getSimTime() + SGTimeStamp::fromSecMSec(0, 1));
340     }
341 
update(AIManager & manager,const SGTimeStamp & simTime)342     virtual void update(AIManager& manager, const SGTimeStamp& simTime)
343     {
344         SGTimeStamp dt = simTime - getSimTime();
345 
346         setGroundCache(getPhysics(), manager.getPager(), dt);
347         getEnvironment().update(*this, dt);
348         getSubsystemGroup().update(*this, dt);
349         getPhysics().update(*this, dt);
350 
351         AIObject::update(manager, simTime);
352 
353         if (!_objectInstance.valid())
354             return;
355 
356         _objectInstance->setLocation(getPhysics());
357         _objectInstance->setSimTime(getSimTime().toSecs());
358         _objectInstance->updateAttributeValues(getSimTime(), simgear::RTIData("update"));
359 
360         manager.schedule(*this, getSimTime() + SGTimeStamp::fromSecMSec(0, 100));
361     }
362 
shutdown(AIManager & manager)363     virtual void shutdown(AIManager& manager)
364     {
365         if (_objectInstance.valid())
366             _objectInstance->removeInstance(simgear::RTIData("shutdown"));
367         _objectInstance = 0;
368         AIObject::shutdown(manager);
369     }
370 
371 private:
372     SGGeod _geod;
373     double _radius;
374     double _turnaroundTime;
375     double _velocity;
376     SGSharedPtr<HLAMPAircraft> _objectInstance;
377 };
378 
379 /// an ogle in a traffic circuit at lowi
380 class AIOgelInTrafficCircuit : public AIObject {
381 public:
382     /// Also nothing to really use for a long time, but to demonstrate how it basically works
383     class Physics : public AIPhysics {
384     public:
Physics(const SGLocationd & location,const SGVec3d & linearBodyVelocity=SGVec3d::zeros (),const SGVec3d & angularBodyVelocity=SGVec3d::zeros ())385         Physics(const SGLocationd& location, const SGVec3d& linearBodyVelocity = SGVec3d::zeros(),
386                 const SGVec3d& angularBodyVelocity = SGVec3d::zeros()) :
387             AIPhysics(location, linearBodyVelocity, angularBodyVelocity),
388             _targetVelocity(30),
389             _gearOffset(2.5)
390         { }
~Physics()391         virtual ~Physics()
392         { }
393 
update(AIObject & object,const SGTimeStamp & dt)394         virtual void update(AIObject& object, const SGTimeStamp& dt)
395         {
396             SGVec3d down = getHorizontalLocalOrientation().backTransform(SGVec3d(0, 0, 1));
397             SGVec3d distToAimingPoint = getAimingPoint() - getPosition();
398             if (norm(distToAimingPoint - down*dot(down, distToAimingPoint)) <= 10*dt.toSecs()*norm(getLinearVelocity()))
399                 rotateAimingPoint();
400 
401             SGVec3d aimingVector = normalize(getAimingPoint() - getPosition());
402             SGVec3d bodyAimingVector = getLocation().getOrientation().transform(aimingVector);
403 
404             SGVec3d angularVelocity = 0.2*cross(SGVec3d(1, 0, 0), bodyAimingVector);
405             SGVec3d bodyDownVector = getLocation().getOrientation().transform(down);
406             // keep an upward orientation
407             angularVelocity += cross(SGVec3d(0, 0, 1), SGVec3d(0, bodyDownVector[1], bodyDownVector[2]));
408 
409             SGVec3d linearVelocity(_targetVelocity, 0, 0);
410 
411             SGVec3d gearPosition = getPosition() + _gearOffset*down;
412             SGLineSegmentd lineSegment(gearPosition - 10*down, gearPosition + 10*down);
413             SGVec3d point, normal;
414             if (object.getGroundIntersection(point, normal, lineSegment)) {
415                 double agl = dot(down, point - gearPosition);
416                 if (agl < 0)
417                     linearVelocity -= down*(0.5*agl/dt.toSecs());
418             }
419 
420             advanceByBodyVelocity(dt.toSecs(), linearVelocity, angularVelocity);
421         }
422 
getAimingPoint() const423         const SGVec3d& getAimingPoint() const
424         { return _waypoints.front(); }
rotateAimingPoint()425         void rotateAimingPoint()
426         { _waypoints.splice(_waypoints.end(), _waypoints, _waypoints.begin()); }
427 
428         std::list<SGVec3d> _waypoints;
429         double _targetVelocity;
430         double _gearOffset;
431     };
432 
AIOgelInTrafficCircuit()433     AIOgelInTrafficCircuit()
434     { }
~AIOgelInTrafficCircuit()435     virtual ~AIOgelInTrafficCircuit()
436     { }
437 
init(AIManager & manager)438     virtual void init(AIManager& manager)
439     {
440         AIObject::init(manager);
441 
442         /// Put together somw waypoints
443         /// This needs to be replaced by something generic
444         SGGeod rwyStart = SGGeod::fromDegM(11.35203755, 47.26109606, 574);
445         SGGeod rwyEnd = SGGeod::fromDegM(11.33741688, 47.25951967, 576);
446         SGQuatd hl = SGQuatd::fromLonLat(rwyStart);
447         SGVec3d down = hl.backTransform(SGVec3d(0, 0, 1));
448 
449         SGVec3d cartRwyStart = SGVec3d::fromGeod(rwyStart);
450         SGVec3d cartRwyEnd = SGVec3d::fromGeod(rwyEnd);
451 
452         SGVec3d centerline = normalize(cartRwyEnd - cartRwyStart);
453         SGVec3d left = normalize(cross(centerline, down));
454 
455         SGGeod endClimb = SGGeod::fromGeodM(SGGeod::fromCart(cartRwyEnd + 500*centerline), 700);
456         SGGeod startDescend = SGGeod::fromGeodM(SGGeod::fromCart(cartRwyStart - 500*centerline + 150*left), 650);
457 
458         SGGeod startDownwind = SGGeod::fromGeodM(SGGeod::fromCart(cartRwyEnd + 500*centerline + 800*left), 750);
459         SGGeod endDownwind = SGGeod::fromGeodM(SGGeod::fromCart(cartRwyStart - 500*centerline + 800*left), 750);
460 
461         SGLocationd location(SGVec3d::fromGeod(rwyStart), SGQuatd::fromLonLat(rwyStart)*SGQuatd::fromEulerDeg(-100, 0, 0));
462         Physics* physics = new Physics(location, SGVec3d(0, 0, 0), SGVec3d(0, 0, 0));
463         physics->_waypoints.push_back(SGVec3d::fromGeod(rwyStart));
464         physics->_waypoints.push_back(SGVec3d::fromGeod(rwyEnd));
465         physics->_waypoints.push_back(SGVec3d::fromGeod(endClimb));
466         physics->_waypoints.push_back(SGVec3d::fromGeod(startDownwind));
467         physics->_waypoints.push_back(SGVec3d::fromGeod(endDownwind));
468         physics->_waypoints.push_back(SGVec3d::fromGeod(startDescend));
469         setPhysics(physics);
470 
471         /// Ok, this is part of the official sketch
472         HLAMPAircraftClass* objectClass = dynamic_cast<HLAMPAircraftClass*>(manager.getObjectClass("MPAircraft"));
473         if (!objectClass)
474             return;
475         _objectInstance = new HLAMPAircraft(objectClass);
476         if (!_objectInstance.valid())
477             return;
478         _objectInstance->registerInstance();
479         _objectInstance->setModelPath("Aircraft/ogel/Models/SinglePiston.xml");
480 
481         /// Need to schedule something else we get deleted
482         manager.schedule(*this, getSimTime() + SGTimeStamp::fromSecMSec(0, 100));
483     }
484 
update(AIManager & manager,const SGTimeStamp & simTime)485     virtual void update(AIManager& manager, const SGTimeStamp& simTime)
486     {
487         SGTimeStamp dt = simTime - getSimTime();
488 
489         setGroundCache(getPhysics(), manager.getPager(), dt);
490         getEnvironment().update(*this, dt);
491         getSubsystemGroup().update(*this, dt);
492         getPhysics().update(*this, dt);
493 
494         AIObject::update(manager, simTime);
495 
496         if (!_objectInstance.valid())
497             return;
498 
499         _objectInstance->setLocation(getPhysics());
500         _objectInstance->setSimTime(getSimTime().toSecs());
501         _objectInstance->updateAttributeValues(getSimTime(), simgear::RTIData("update"));
502 
503         /// Need to schedule something else we get deleted
504         manager.schedule(*this, getSimTime() + SGTimeStamp::fromSecMSec(0, 100));
505     }
506 
shutdown(AIManager & manager)507     virtual void shutdown(AIManager& manager)
508     {
509         if (_objectInstance.valid())
510             _objectInstance->removeInstance(simgear::RTIData("shutdown"));
511         _objectInstance = 0;
512 
513         AIObject::shutdown(manager);
514     }
515 
516 private:
517     SGSharedPtr<HLAMPAircraft> _objectInstance;
518 };
519 
520 } // namespace fgai
521 
522 // getopt
523 #include <unistd.h>
524 
525 int
main(int argc,char * argv[])526 main(int argc, char* argv[])
527 {
528     SGSharedPtr<fgai::AIManager> manager = new fgai::AIManager;
529 
530     /// FIXME include some argument parsing stuff
531     std::string fg_root;
532     std::string fg_scenery;
533 
534     int c;
535     while ((c = getopt(argc, argv, "cCf:F:n:O:p:RsS")) != EOF) {
536         switch (c) {
537         case 'c':
538             manager->setCreateFederationExecution(true);
539             break;
540         case 'C':
541             manager->setTimeConstrained(true);
542             break;
543         case 'f':
544             manager->setFederateType(optarg);
545             break;
546         case 'F':
547             manager->setFederationExecutionName(optarg);
548             break;
549         case 'O':
550             manager->setFederationObjectModel(optarg);
551             break;
552         case 'p':
553             sglog().set_log_classes(SG_ALL);
554             sglog().set_log_priority(sgDebugPriority(atoi(optarg)));
555             break;
556         case 'R':
557             manager->setTimeRegulating(true);
558             break;
559         case 's':
560             manager->setTimeConstrainedByLocalClock(false);
561             break;
562         case 'S':
563             manager->setTimeConstrainedByLocalClock(true);
564             break;
565         case 'r':
566             fg_root = optarg;
567             break;
568         case 'y':
569             fg_scenery = optarg;
570             break;
571         }
572     }
573 
574     if (fg_root.empty()) {
575         if (const char *fg_root_env = std::getenv("FG_ROOT")) {
576             fg_root = fg_root_env;
577         } else {
578             fg_root = PKGLIBDIR;
579         }
580     }
581     if (fg_scenery.empty()) {
582         if (const char *fg_scenery_env = std::getenv("FG_SCENERY")) {
583             fg_scenery = fg_scenery_env;
584         } else if (!fg_root.empty()) {
585             SGPath path(fg_root);
586             path.append("Scenery");
587             fg_scenery = path.str();
588         }
589     }
590 
591     manager->getPager().setScenery(fg_root, fg_scenery);
592 
593     if (manager->getFederationObjectModel().empty()) {
594         SGPath path(fg_root);
595         path.append("HLA");
596         path.append("fg-local-fom.xml");
597         manager->setFederationObjectModel(path.local8BitStr());
598     }
599 
600     /// EDDS
601     manager->insert(new fgai::AIOgel(SGGeod::fromDegFt(9.19298, 48.6895, 2000)));
602     /// LOWI
603     manager->insert(new fgai::AIOgel(SGGeod::fromDegFt(11.344, 47.260, 2500)));
604     /// LOWI traffic circuit
605     manager->insert(new fgai::AIOgelInTrafficCircuit);
606 
607     return manager->exec();
608 }
609